summaryrefslogtreecommitdiff
path: root/gfx/gl
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/gl')
-rw-r--r--gfx/gl/program.cpp19
-rw-r--r--gfx/gl/program.h11
-rw-r--r--gfx/gl/sceneProvider.cpp2
-rw-r--r--gfx/gl/sceneRenderer.cpp28
-rw-r--r--gfx/gl/sceneRenderer.h3
-rw-r--r--gfx/gl/sceneShader.cpp2
-rw-r--r--gfx/gl/shader.cpp69
-rw-r--r--gfx/gl/shader.h8
-rw-r--r--gfx/gl/shaders/commonPoint.glsl15
-rw-r--r--gfx/gl/shaders/commonShadowPoint.glsl5
-rw-r--r--gfx/gl/shaders/commonShadowPoint.gs8
-rw-r--r--gfx/gl/shaders/directionalLight.fs29
-rw-r--r--gfx/gl/shaders/dynamicPoint.vs2
-rw-r--r--gfx/gl/shaders/dynamicPointInst.vs2
-rw-r--r--gfx/gl/shaders/fixedPoint.vs2
-rw-r--r--gfx/gl/shaders/getMaterialDetail.glsl12
-rw-r--r--gfx/gl/shaders/material.fs36
-rw-r--r--gfx/gl/shaders/materialCommon.glsl33
-rw-r--r--gfx/gl/shaders/materialDetail.glsl5
-rw-r--r--gfx/gl/shaders/materialInterface.glsl20
-rw-r--r--gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs8
-rw-r--r--gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs14
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.fs16
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.gs36
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.vs17
-rw-r--r--gfx/gl/shaders/shadowStencil.fs18
-rw-r--r--gfx/gl/shaders/shadowStencil.gs28
-rw-r--r--gfx/gl/shaders/shadowStencil.vs20
-rw-r--r--gfx/gl/shadowMapper.cpp80
-rw-r--r--gfx/gl/shadowMapper.h21
-rw-r--r--gfx/gl/shadowStenciller.cpp74
-rw-r--r--gfx/gl/shadowStenciller.h27
32 files changed, 534 insertions, 136 deletions
diff --git a/gfx/gl/program.cpp b/gfx/gl/program.cpp
index 7287fde..fdd4c6f 100644
--- a/gfx/gl/program.cpp
+++ b/gfx/gl/program.cpp
@@ -10,10 +10,10 @@ void
Program::linkAndValidate() const
{
glLinkProgram(m_program);
- Shader::CheckShaderError(m_program, GL_LINK_STATUS, true, "Error linking shader program");
+ checkProgramError(m_program, GL_LINK_STATUS, "Error linking shader program");
glValidateProgram(m_program);
- Shader::CheckShaderError(m_program, GL_VALIDATE_STATUS, true, "Invalid shader program");
+ checkProgramError(m_program, GL_VALIDATE_STATUS, "Invalid shader program");
}
void
@@ -22,6 +22,21 @@ Program::use() const
glUseProgram(m_program);
}
+void
+Program::checkProgramError(GLuint program, GLuint flag, std::string_view errorMessage) const
+{
+ GLint success = 0;
+
+ glGetProgramiv(program, flag, &success);
+
+ if (success == GL_FALSE) {
+ std::array<GLchar, 1024> error {};
+ glGetProgramInfoLog(program, error.size(), nullptr, error.data());
+
+ throw std::runtime_error {std::format("{}: '{}'", errorMessage, error.data())};
+ }
+}
+
Program::UniformLocation::UniformLocation(GLuint program, const char * name) :
location {glGetUniformLocation(program, name)}
{
diff --git a/gfx/gl/program.h b/gfx/gl/program.h
index 1a1c306..20be1aa 100644
--- a/gfx/gl/program.h
+++ b/gfx/gl/program.h
@@ -1,6 +1,6 @@
#pragma once
-#include "shader.h"
+#include "shader.h" // IWYU pragma: export
#include <glRef.h>
#include <glad/gl.h>
#include <glm/mat4x4.hpp>
@@ -12,6 +12,8 @@ using ProgramRef = glRef<GLuint, &glCreateProgram, &glDeleteProgram>;
class Program {
public:
+ Program() = delete;
+
template<typename... S> explicit Program(const S &... srcs)
{
(glAttachShader(m_program, srcs.compile()), ...);
@@ -31,6 +33,12 @@ public:
return location;
}
+ explicit
+ operator bool() const
+ {
+ return location >= 0;
+ }
+
protected:
GLint location;
};
@@ -47,6 +55,7 @@ public:
}
protected:
+ void checkProgramError(GLuint program, GLuint flag, std::string_view errorMessage) const;
void use() const;
void linkAndValidate() const;
ProgramRef m_program;
diff --git a/gfx/gl/sceneProvider.cpp b/gfx/gl/sceneProvider.cpp
index 2e8604c..4e271db 100644
--- a/gfx/gl/sceneProvider.cpp
+++ b/gfx/gl/sceneProvider.cpp
@@ -5,7 +5,7 @@ void
SceneProvider::environment(const SceneShader &, const SceneRenderer & renderer) const
{
renderer.setAmbientLight({0.5F, 0.5F, 0.5F});
- renderer.setDirectionalLight({0.6F, 0.6F, 0.6F}, {-1, 1, -1}, *this);
+ renderer.setDirectionalLight({0.6F, 0.6F, 0.6F}, {{-quarter_pi, -quarter_pi}}, *this);
}
void
diff --git a/gfx/gl/sceneRenderer.cpp b/gfx/gl/sceneRenderer.cpp
index e0938f2..b2a7d78 100644
--- a/gfx/gl/sceneRenderer.cpp
+++ b/gfx/gl/sceneRenderer.cpp
@@ -62,7 +62,7 @@ SceneRenderer::render(const SceneProvider & scene) const
shader.setViewProjection(camera.getPosition(), camera.getViewProjection());
glViewport(0, 0, size.x, size.y);
- // Geometry pass
+ // Geometry/colour pass - writes albedo, normal and position textures
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
@@ -73,7 +73,13 @@ SceneRenderer::render(const SceneProvider & scene) const
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
scene.content(shader);
- // Illumination pass
+ // Environment pass -
+ // * ambient - clears illumination texture - see setAmbientLight
+ // * directional - updates shadowMapper, reads normal and position, writes illumination - see setDirectionalLight
+ scene.environment(shader, *this);
+
+ // Scene lights pass -
+ // * per light - reads normal and position, writes illumination
glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll);
glBlendFunc(GL_ONE, GL_ONE);
glActiveTexture(GL_TEXTURE0);
@@ -82,11 +88,10 @@ SceneRenderer::render(const SceneProvider & scene) const
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D_ARRAY, shadowMapper);
- scene.environment(shader, *this);
glDisable(GL_DEPTH_TEST);
scene.lights(shader);
- // Lighting pass
+ // Composition pass - reads albedo and illumination, writes output
glBindFramebuffer(GL_FRAMEBUFFER, output);
glViewport(0, 0, size.x, size.y);
glCullFace(GL_BACK);
@@ -109,14 +114,22 @@ SceneRenderer::setAmbientLight(const RGB & colour) const
}
void
-SceneRenderer::setDirectionalLight(const RGB & colour, const Direction3D & direction, const SceneProvider & scene) const
+SceneRenderer::setDirectionalLight(
+ const RGB & colour, const LightDirection & direction, const SceneProvider & scene) const
{
if (colour.r > 0 || colour.g > 0 || colour.b > 0) {
const auto lvp = shadowMapper.update(scene, direction, camera);
glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll);
+ glBlendFunc(GL_ONE, GL_ONE);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, gPosition);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, gNormal);
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, shadowMapper);
glViewport(0, 0, size.x, size.y);
dirLight.use();
- dirLight.setDirectionalLight(colour, direction, camera.getPosition(), lvp);
+ dirLight.setDirectionalLight(colour, direction.vector(), camera.getPosition(), lvp);
renderQuad();
}
}
@@ -142,8 +155,7 @@ SceneRenderer::DirectionalLightProgram::setDirectionalLight(
return toTextureSpaceMat * m;
};
glUniform(colourLoc, c);
- const auto nd = glm::normalize(d);
- glUniform(directionLoc, nd);
+ glUniform(directionLoc, d);
glUniform(lightPointLoc, p);
glUniform(lightViewProjectionCountLoc, static_cast<GLuint>(lvp.size()));
glUniform(lightViewProjectionLoc, std::span<const glm::mat4> {lvp * toTextureSpace});
diff --git a/gfx/gl/sceneRenderer.h b/gfx/gl/sceneRenderer.h
index 4195bcf..93470f5 100644
--- a/gfx/gl/sceneRenderer.h
+++ b/gfx/gl/sceneRenderer.h
@@ -1,6 +1,7 @@
#pragma once
#include "camera.h"
+#include "gfx/lightDirection.h"
#include "glArrays.h"
#include "program.h"
#include "sceneProvider.h"
@@ -14,7 +15,7 @@ public:
void render(const SceneProvider &) const;
void setAmbientLight(const RGB & colour) const;
- void setDirectionalLight(const RGB & colour, const Direction3D & direction, const SceneProvider &) const;
+ void setDirectionalLight(const RGB & colour, const LightDirection & direction, const SceneProvider &) const;
Camera camera;
diff --git a/gfx/gl/sceneShader.cpp b/gfx/gl/sceneShader.cpp
index 4cbccb3..571538a 100644
--- a/gfx/gl/sceneShader.cpp
+++ b/gfx/gl/sceneShader.cpp
@@ -65,7 +65,7 @@ SceneShader::SceneProgram::setViewProjection(const GlobalPosition3D & viewPoint,
void
SceneShader::SceneProgram::setViewPort(const ViewPort & viewPort) const
{
- if (viewPortLoc >= 0) {
+ if (viewPortLoc) {
glUseProgram(*this);
glUniform(viewPortLoc, viewPort);
}
diff --git a/gfx/gl/shader.cpp b/gfx/gl/shader.cpp
index 0bc127a..9a4c270 100644
--- a/gfx/gl/shader.cpp
+++ b/gfx/gl/shader.cpp
@@ -1,4 +1,5 @@
#include "shader.h"
+#include "msgException.h"
#include <algorithm>
#include <array>
#include <format>
@@ -18,6 +19,39 @@ namespace {
constexpr std::array<std::tuple<std::string_view, GLenum, LookUpFunction>, 1> LOOKUPS {{
{"GL_MAX_GEOMETRY_OUTPUT_VERTICES", GL_MAX_GEOMETRY_OUTPUT_VERTICES, getInt},
}};
+
+ struct ShaderCompileError : public MsgException<std::invalid_argument> {
+ explicit ShaderCompileError(GLuint shader, Shader::Source src) :
+ MsgException<std::invalid_argument> {"Error compiling shader"}, shader {shader}, source {src},
+ msg {getShaderText(GL_INFO_LOG_LENGTH, glGetShaderInfoLog)}
+ {
+ }
+
+ [[nodiscard]] std::string
+ getMsg() const noexcept override
+ {
+ return std::format("Error compiling shader: '{}'\nSource:\n{}",
+ getShaderText(GL_INFO_LOG_LENGTH, glGetShaderInfoLog), source);
+ }
+
+ private:
+ std::string
+ getShaderText(GLenum param, auto getTextFunc) const
+ {
+ std::string text;
+ text.resize_and_overwrite(static_cast<size_t>(Shader::getShaderParam(shader, param)),
+ [this, getTextFunc](auto buf, auto len) {
+ GLsizei outLen {};
+ getTextFunc(shader, static_cast<GLsizei>(len), &outLen, buf);
+ return outLen;
+ });
+ return text;
+ }
+
+ const GLuint shader;
+ const Shader::Source source;
+ const std::string msg;
+ };
}
Shader::ShaderRef
@@ -29,8 +63,8 @@ Shader::compile() const
};
if (lookups) {
std::basic_string<GLchar> textMod {text};
- for (const auto & match : ctre::range<R"(\bGL_[A-Z_]+\b)">(textMod)) {
- if (const auto lookup = std::find_if(LOOKUPS.begin(), LOOKUPS.end(),
+ for (const auto & match : ctre::search_all<R"(\bGL_[A-Z_]+\b)">(textMod)) {
+ if (const auto * const lookup = std::find_if(LOOKUPS.begin(), LOOKUPS.end(),
[&match](const auto & lookup) {
return std::get<std::string_view>(lookup) == match;
});
@@ -46,31 +80,22 @@ Shader::compile() const
}
glCompileShader(shader);
- CheckShaderError(shader, GL_COMPILE_STATUS, false, "Error compiling shader!");
+ checkShaderError(shader);
return shader;
}
void
-Shader::CheckShaderError(GLuint shader, GLuint flag, bool isProgram, std::string_view errorMessage)
+Shader::checkShaderError(GLuint shader) const
{
- GLint success = 0;
-
- if (isProgram) {
- glGetProgramiv(shader, flag, &success);
+ if (getShaderParam(shader, GL_COMPILE_STATUS) == GL_FALSE) {
+ throw ShaderCompileError {shader, text};
}
- else {
- glGetShaderiv(shader, flag, &success);
- }
-
- if (success == GL_FALSE) {
- std::array<GLchar, 1024> error {};
- if (isProgram) {
- glGetProgramInfoLog(shader, error.size(), nullptr, error.data());
- }
- else {
- glGetShaderInfoLog(shader, error.size(), nullptr, error.data());
- }
+}
- throw std::runtime_error {std::format("{}: '{}'", errorMessage, error.data())};
- }
+GLint
+Shader::getShaderParam(GLuint shader, GLenum pname)
+{
+ GLint pvalue {};
+ glGetShaderiv(shader, pname, &pvalue);
+ return pvalue;
}
diff --git a/gfx/gl/shader.h b/gfx/gl/shader.h
index c6b45af..ce97734 100644
--- a/gfx/gl/shader.h
+++ b/gfx/gl/shader.h
@@ -7,6 +7,7 @@
class Shader {
public:
+ using Source = std::basic_string_view<GLchar>;
using ShaderRef = glRef<GLuint, &glCreateShader, &glDeleteShader>;
constexpr Shader(const GLchar * text, GLuint type) :
@@ -15,10 +16,13 @@ public:
}
[[nodiscard]] ShaderRef compile() const;
- static void CheckShaderError(GLuint shader, GLuint flag, bool isProgram, std::string_view errorMessage);
+
+ [[nodiscard]] static GLint getShaderParam(GLuint shader, GLenum pname);
private:
- const std::basic_string_view<GLchar> text;
+ void checkShaderError(GLuint shader) const;
+
+ const Source text;
GLuint type;
bool lookups;
};
diff --git a/gfx/gl/shaders/commonPoint.glsl b/gfx/gl/shaders/commonPoint.glsl
index 2d9e388..dc534d5 100644
--- a/gfx/gl/shaders/commonPoint.glsl
+++ b/gfx/gl/shaders/commonPoint.glsl
@@ -1,17 +1,4 @@
-layout(binding = 1) uniform usampler2DRect materialData;
-
-MaterialDetail
-getMaterialDetail(uint midx)
-{
- if (midx > 0u) {
- const vec4 sPosSize = texture(materialData, uvec2(0, midx - 1u));
- const uvec4 sMode = texture(materialData, uvec2(1, midx - 1u));
- const uint mapmodeU = sMode.x & 0xFu;
- const uint mapmodeV = (sMode.x & 0xF0u) >> 1;
- return MaterialDetail(sPosSize.xy, sPosSize.zw, uvec2(mapmodeU, mapmodeV));
- }
- return MaterialDetail(vec2(0, 0), vec2(0, 0), uvec2(0, 0));
-}
+include(`getMaterialDetail.glsl')
void
main()
diff --git a/gfx/gl/shaders/commonShadowPoint.glsl b/gfx/gl/shaders/commonShadowPoint.glsl
index c4ea827..9910d46 100644
--- a/gfx/gl/shaders/commonShadowPoint.glsl
+++ b/gfx/gl/shaders/commonShadowPoint.glsl
@@ -1,11 +1,10 @@
out vec4 vworldPos;
-ifdef(`TEXTURES', out vec2 vtexCoord;);
-
void
main()
{
vec3 worldPos = model * position;
vworldPos = vec4(worldPos - viewPoint + modelPos, 1);
- ifdef(`TEXTURES', vtexCoord = texCoord;);
+ ifdef(`TEXTURES', TexCoords = texCoord;);
+ ifdef(`TEXTURES', Material = getMaterialDetail(material););
}
diff --git a/gfx/gl/shaders/commonShadowPoint.gs b/gfx/gl/shaders/commonShadowPoint.gs
index b99bd20..2413cc0 100644
--- a/gfx/gl/shaders/commonShadowPoint.gs
+++ b/gfx/gl/shaders/commonShadowPoint.gs
@@ -1,13 +1,16 @@
#version 330 core
#extension GL_ARB_viewport_array : enable
+ifdef(`TEXTURES', include(`materialDetail.glsl'))
+
uniform mat4 viewProjection[4];
uniform int viewProjections;
in vec4 vworldPos[];
layout(triangles) in;
layout(triangle_strip, max_vertices = 12) out;
-ifdef(`TEXTURES', in vec2 vtexCoord[]; out vec2 texCoord;);
+ifdef(`TEXTURES', in vec2 TexCoords[]; out vec2 texCoord;)
+ifdef(`TEXTURES', flat in MaterialDetail Material[]; flat out MaterialDetail material;)
void
main()
@@ -17,7 +20,8 @@ main()
gl_Position = viewProjection[vp] * vworldPos[v];
gl_Position.z = max(gl_Position.z, -1);
gl_Layer = vp;
- ifdef(`TEXTURES', texCoord = vtexCoord[v];);
+ ifdef(`TEXTURES', texCoord = TexCoords[v];)
+ ifdef(`TEXTURES', material = Material[v];)
EmitVertex();
}
EndPrimitive();
diff --git a/gfx/gl/shaders/directionalLight.fs b/gfx/gl/shaders/directionalLight.fs
index 24457b8..cdf0389 100644
--- a/gfx/gl/shaders/directionalLight.fs
+++ b/gfx/gl/shaders/directionalLight.fs
@@ -17,27 +17,38 @@ uniform ivec3 lightPoint;
uniform mat4 lightViewProjection[MAX_MAPS];
uniform uint lightViewProjectionCount;
-const vec3 e1 = vec3(0, 0, 0), e2 = vec3(1, 1, 1);
+float
+getShadow(vec3 positionInLightSpace, float m, vec2 texelSize)
+{
+ float shadow = 0.0;
+ for (float x = -texelSize.x; x <= texelSize.x; x += texelSize.x) {
+ for (float y = -texelSize.y; y <= texelSize.y; y += texelSize.y) {
+ const float lightSpaceDepth = texture(shadowMap, vec3(positionInLightSpace.xy + vec2(x, y), m)).r;
+ shadow += step(positionInLightSpace.z, lightSpaceDepth + 0.001);
+ }
+ }
+ return shadow / 9.0;
+}
float
-insideShadowCube(vec3 v)
+insideShadowCube(vec3 v, vec2 texelSize)
{
- const vec3 s = step(e1, v) - step(e2, v);
+ const vec3 s = step(vec3(texelSize, 0), v) - step(vec3(1 - texelSize, 1), v);
return s.x * s.y * s.z;
}
float
isShaded(vec4 Position)
{
+ const vec2 texelSize = 1.0 / textureSize(shadowMap, 0).xy;
for (uint m = 0u; m < lightViewProjectionCount; m++) {
- const vec3 PositionInLightSpace = (lightViewProjection[m] * Position).xyz;
- const float inside = insideShadowCube(PositionInLightSpace);
+ const vec3 positionInLightSpace = (lightViewProjection[m] * Position).xyz;
+ const float inside = insideShadowCube(positionInLightSpace, texelSize);
if (inside > 0) {
- const float lightSpaceDepth = texture(shadowMap, vec3(PositionInLightSpace.xy, m)).r;
- return step(lightSpaceDepth, PositionInLightSpace.z);
+ return getShadow(positionInLightSpace, m, texelSize);
}
}
- return 0;
+ return 1;
}
void
@@ -46,5 +57,5 @@ main()
const vec4 Position = vec4(texture(gPosition, TexCoords).xyz - lightPoint, 1);
const vec3 Normal = texture(gNormal, TexCoords).rgb;
const float shaded = isShaded(Position);
- FragColor = (1 - shaded) * max(dot(-lightDirection, Normal) * lightColour, 0);
+ FragColor = shaded * max(dot(-lightDirection, Normal) * lightColour, 0);
}
diff --git a/gfx/gl/shaders/dynamicPoint.vs b/gfx/gl/shaders/dynamicPoint.vs
index 7551688..97d2983 100644
--- a/gfx/gl/shaders/dynamicPoint.vs
+++ b/gfx/gl/shaders/dynamicPoint.vs
@@ -1,6 +1,8 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
+layout(binding = 1) uniform usampler2DRect materialData;
+
include(`meshIn.glsl')
include(`materialInterface.glsl')
diff --git a/gfx/gl/shaders/dynamicPointInst.vs b/gfx/gl/shaders/dynamicPointInst.vs
index 69eab0c..a85f9c9 100644
--- a/gfx/gl/shaders/dynamicPointInst.vs
+++ b/gfx/gl/shaders/dynamicPointInst.vs
@@ -1,6 +1,8 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
+layout(binding = 1) uniform usampler2DRect materialData;
+
include(`meshIn.glsl')
include(`materialInterface.glsl')
diff --git a/gfx/gl/shaders/fixedPoint.vs b/gfx/gl/shaders/fixedPoint.vs
index 5cfe9b3..435b3d1 100644
--- a/gfx/gl/shaders/fixedPoint.vs
+++ b/gfx/gl/shaders/fixedPoint.vs
@@ -1,6 +1,8 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
+layout(binding = 1) uniform usampler2DRect materialData;
+
include(`meshIn.glsl')
include(`materialInterface.glsl')
diff --git a/gfx/gl/shaders/getMaterialDetail.glsl b/gfx/gl/shaders/getMaterialDetail.glsl
new file mode 100644
index 0000000..f819fb2
--- /dev/null
+++ b/gfx/gl/shaders/getMaterialDetail.glsl
@@ -0,0 +1,12 @@
+MaterialDetail
+getMaterialDetail(uint midx)
+{
+ if (midx > 0u) {
+ const vec4 sPosSize = texture(materialData, uvec2(0, midx - 1u));
+ const uvec4 sMode = texture(materialData, uvec2(1, midx - 1u));
+ const uint mapmodeU = sMode.x & 0xFu;
+ const uint mapmodeV = (sMode.x & 0xF0u) >> 1;
+ return MaterialDetail(sPosSize.xy, sPosSize.zw, uvec2(mapmodeU, mapmodeV));
+ }
+ return MaterialDetail(vec2(0, 0), vec2(0, 0), uvec2(0, 0));
+}
diff --git a/gfx/gl/shaders/material.fs b/gfx/gl/shaders/material.fs
index 5b93707..37f2e27 100644
--- a/gfx/gl/shaders/material.fs
+++ b/gfx/gl/shaders/material.fs
@@ -1,41 +1,11 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
+layout(binding = 0) uniform sampler2D textureAlbedo;
+
include(`materialInterface.glsl')
include(`materialOut.glsl')
-
-layout(binding = 0) uniform sampler2D texture0;
-
-float map(uint mapmode, float value)
-{
- switch (mapmode) {
- case 0u: // Repeat
- return fract(value);
- case 1u: // Clamp to edge
- return clamp(0.0, 1.0, value);
- case 2u: // Mirror
- discard;
- case 3u: // Decal
- if (value != clamp(0.0, 1.0, value)) {
- discard;
- }
- }
- return 0;
-}
-
-vec2 map(uvec2 mapmode, vec2 value)
-{
- return vec2(map(mapmode.x, value.x), map(mapmode.y, value.y));
-}
-
-vec4 getTextureColour(MaterialDetail mat, vec2 uv)
-{
- if (mat.textureSize.x > 0) {
- const vec2 tSize = textureSize(texture0, 0);
- uv = (mat.textureOrigin + mat.textureSize * map(mat.mapmode, uv)) / tSize;
- }
- return texture(texture0, uv);
-}
+include(`materialCommon.glsl')
void
main()
diff --git a/gfx/gl/shaders/materialCommon.glsl b/gfx/gl/shaders/materialCommon.glsl
new file mode 100644
index 0000000..3fe2237
--- /dev/null
+++ b/gfx/gl/shaders/materialCommon.glsl
@@ -0,0 +1,33 @@
+float
+map(uint mapmode, float value)
+{
+ switch (mapmode) {
+ case 0u: // Repeat
+ return fract(value);
+ case 1u: // Clamp to edge
+ return clamp(0.0, 1.0, value);
+ case 2u: // Mirror
+ discard;
+ case 3u: // Decal
+ if (value != clamp(0.0, 1.0, value)) {
+ discard;
+ }
+ }
+ return 0;
+}
+
+vec2
+map(uvec2 mapmode, vec2 value)
+{
+ return vec2(map(mapmode.x, value.x), map(mapmode.y, value.y));
+}
+
+vec4
+getTextureColour(MaterialDetail mat, vec2 uv)
+{
+ if (mat.textureSize.x > 0) {
+ const vec2 tSize = textureSize(textureAlbedo, 0);
+ uv = (mat.textureOrigin + mat.textureSize * map(mat.mapmode, uv)) / tSize;
+ }
+ return texture(textureAlbedo, uv);
+}
diff --git a/gfx/gl/shaders/materialDetail.glsl b/gfx/gl/shaders/materialDetail.glsl
new file mode 100644
index 0000000..a208d50
--- /dev/null
+++ b/gfx/gl/shaders/materialDetail.glsl
@@ -0,0 +1,5 @@
+struct MaterialDetail {
+ vec2 textureOrigin;
+ vec2 textureSize;
+ uvec2 mapmode;
+};
diff --git a/gfx/gl/shaders/materialInterface.glsl b/gfx/gl/shaders/materialInterface.glsl
index 3a4796b..f2ca297 100644
--- a/gfx/gl/shaders/materialInterface.glsl
+++ b/gfx/gl/shaders/materialInterface.glsl
@@ -1,13 +1,9 @@
-struct MaterialDetail {
- vec2 textureOrigin;
- vec2 textureSize;
- uvec2 mapmode;
-};
+include(`materialDetail.glsl')
-ifelse(TYPE, .fs, in, out) vec3 FragPos;
-ifelse(TYPE, .fs, in, out) vec2 TexCoords;
-ifelse(TYPE, .fs, in, out) vec3 Normal;
-ifelse(TYPE, .fs, in, out) vec4 Colour;
-flat
-ifelse(TYPE, .fs, in, out)
-MaterialDetail Material;
+define(INOUT, ifelse(TYPE, .fs, in, out));
+
+INOUT vec3 FragPos;
+INOUT vec2 TexCoords;
+INOUT vec3 Normal;
+INOUT vec4 Colour;
+flat INOUT MaterialDetail Material;
diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs
index 90519e3..47ce9c0 100644
--- a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs
+++ b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs
@@ -1,14 +1,18 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
-layout(binding = 3) uniform sampler2D texture0;
+layout(binding = 3) uniform sampler2D textureAlbedo;
+
+include(`materialInterface.glsl')
+include(`materialCommon.glsl')
in vec2 texCoord;
+flat in MaterialDetail material;
void
main()
{
- if (texture(texture0, texCoord).a < 0.5) {
+ if (getTextureColour(material, texCoord).a < 0.5) {
discard;
}
gl_FragDepth = gl_FragCoord.z;
diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs
index 27ad9d7..a76c87f 100644
--- a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs
+++ b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs
@@ -1,3 +1,15 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 4) uniform usampler2DRect materialData;
+
define(`TEXTURES', 1)
+include(`materialInterface.glsl')
+include(`getMaterialDetail.glsl')
+include(`meshIn.glsl')
+
+uniform ivec3 viewPoint;
+layout(location = 5) in mat3 model;
+layout(location = 8) in ivec3 modelPos;
-include(`shadowDynamicPointInst.vs')
+include(`commonShadowPoint.glsl')
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.fs b/gfx/gl/shaders/shadowDynamicPointStencil.fs
new file mode 100644
index 0000000..fe91b07
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.fs
@@ -0,0 +1,16 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 0) uniform sampler2DArray stencilDepth;
+flat in vec3 scale;
+in vec3 texCoord;
+
+void
+main()
+{
+ float stDepth = texture(stencilDepth, texCoord).r;
+ if (stDepth >= 1) {
+ discard;
+ }
+ gl_FragDepth = gl_FragCoord.z + ((stDepth - 0.5) * scale.z);
+}
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.gs b/gfx/gl/shaders/shadowDynamicPointStencil.gs
new file mode 100644
index 0000000..7e81d97
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.gs
@@ -0,0 +1,36 @@
+#version 330 core
+#extension GL_ARB_viewport_array : enable
+
+const vec2[] corners = vec2[4](vec2(-1, -1), vec2(-1, 1), vec2(1, -1), vec2(1, 1));
+const float tau = 6.28318531;
+
+uniform mat4 viewProjection[4];
+uniform int viewProjections;
+uniform vec3 sizes[4];
+uniform float size;
+
+in float vmodelYaw[];
+in ivec3 vworldPos[];
+
+flat out vec3 scale;
+out vec3 texCoord;
+
+layout(points) in;
+layout(triangle_strip, max_vertices = 16) out;
+
+void
+main()
+{
+ int viewAngle = int(round(4.0 + (vmodelYaw[0] / tau))) % 8;
+ for (gl_Layer = 0; gl_Layer < viewProjections; ++gl_Layer) {
+ scale = 2.0 * size / sizes[gl_Layer];
+ vec4 pos = viewProjection[gl_Layer] * vec4(vworldPos[0], 1);
+ for (int c = 0; c < corners.length(); ++c) {
+ gl_Position = pos + vec4(scale.xy * corners[c], 0, 0);
+ gl_Position.z = max(gl_Position.z, -1);
+ texCoord = vec3((corners[c] * 0.5) + 0.5, viewAngle);
+ EmitVertex();
+ }
+ EndPrimitive();
+ }
+}
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.vs b/gfx/gl/shaders/shadowDynamicPointStencil.vs
new file mode 100644
index 0000000..0dd2d79
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.vs
@@ -0,0 +1,17 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(location = 0) in ivec3 worldPos;
+layout(location = 1) in float modelYaw;
+uniform ivec3 viewPoint;
+uniform vec3 centre;
+
+out float vmodelYaw;
+out ivec3 vworldPos;
+
+void
+main()
+{
+ vmodelYaw = modelYaw;
+ vworldPos = worldPos - viewPoint + ivec3(centre);
+}
diff --git a/gfx/gl/shaders/shadowStencil.fs b/gfx/gl/shaders/shadowStencil.fs
new file mode 100644
index 0000000..1164cc9
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.fs
@@ -0,0 +1,18 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 0) uniform sampler2D textureAlbedo;
+
+include(`materialDetail.glsl')
+include(`materialCommon.glsl')
+in vec2 gTexCoords;
+flat in MaterialDetail gMaterial;
+
+void
+main()
+{
+ if (getTextureColour(gMaterial, gTexCoords).a < 0.5) {
+ discard;
+ }
+ gl_FragDepth = gl_FragCoord.z;
+}
diff --git a/gfx/gl/shaders/shadowStencil.gs b/gfx/gl/shaders/shadowStencil.gs
new file mode 100644
index 0000000..2c3f9bd
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.gs
@@ -0,0 +1,28 @@
+#version 330 core
+#extension GL_ARB_viewport_array : enable
+
+include(`materialDetail.glsl')
+
+layout(triangles) in;
+layout(triangle_strip, max_vertices = 24) out;
+
+uniform mat4 viewProjection[8];
+in vec3 FragPos[];
+in vec2 TexCoords[];
+flat in MaterialDetail Material[];
+out vec2 gTexCoords;
+flat out MaterialDetail gMaterial;
+
+void
+main()
+{
+ for (gl_Layer = 0; gl_Layer < viewProjection.length(); ++gl_Layer) {
+ for (int v = 0; v < FragPos.length(); ++v) {
+ gl_Position = viewProjection[gl_Layer] * vec4(FragPos[v], 1);
+ gTexCoords = TexCoords[v];
+ gMaterial = Material[v];
+ EmitVertex();
+ }
+ EndPrimitive();
+ }
+}
diff --git a/gfx/gl/shaders/shadowStencil.vs b/gfx/gl/shaders/shadowStencil.vs
new file mode 100644
index 0000000..a15c4fb
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.vs
@@ -0,0 +1,20 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 1) uniform usampler2DRect materialData;
+
+include(`meshIn.glsl')
+include(`materialDetail.glsl')
+include(`getMaterialDetail.glsl')
+
+out vec3 FragPos;
+out vec2 TexCoords;
+flat out MaterialDetail Material;
+
+void
+main()
+{
+ TexCoords = texCoord;
+ Material = getMaterialDetail(material);
+ FragPos = position;
+}
diff --git a/gfx/gl/shadowMapper.cpp b/gfx/gl/shadowMapper.cpp
index a846a3d..1b95aa3 100644
--- a/gfx/gl/shadowMapper.cpp
+++ b/gfx/gl/shadowMapper.cpp
@@ -1,13 +1,20 @@
#include "shadowMapper.h"
#include "camera.h"
#include "collections.h"
+#include "game/gamestate.h"
#include "gfx/gl/shaders/fs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/fs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/gs-commonShadowPoint.h"
#include "gfx/gl/shaders/gs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/gs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/vs-shadowDynamicPoint.h"
#include "gfx/gl/shaders/vs-shadowDynamicPointInst.h"
#include "gfx/gl/shaders/vs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/vs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/vs-shadowLandmass.h"
+#include "gfx/gl/shadowStenciller.h"
+#include "gfx/lightDirection.h"
+#include "gfx/renderable.h"
#include "gl_traits.h"
#include "location.h"
#include "maths.h"
@@ -45,13 +52,15 @@ ShadowMapper::ShadowMapper(const TextureAbsCoord & s) :
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
-constexpr std::array<GlobalDistance, ShadowMapper::SHADOW_BANDS + 1> shadowBands {
- 1000,
- 250000,
- 750000,
- 2500000,
- 10000000,
-};
+constexpr auto shadowBands
+ = []<GlobalDistance... ints>(const float scaleFactor, std::integer_sequence<GlobalDistance, ints...>) {
+ const auto base = 10'000'000 / pow(scaleFactor, sizeof...(ints) - 1);
+ return std::array {1, static_cast<GlobalDistance>((base * pow(scaleFactor, ints)))...};
+ }(6.6F, std::make_integer_sequence<GlobalDistance, ShadowMapper::SHADOW_BANDS>());
+
+static_assert(shadowBands.front() == 1);
+static_assert(shadowBands.back() == 10'000'000);
+static_assert(shadowBands.size() == ShadowMapper::SHADOW_BANDS + 1);
std::vector<std::array<RelativePosition3D, 4>>
ShadowMapper::getBandViewExtents(const Camera & camera, const glm::mat4 & lightViewDir)
@@ -72,34 +81,49 @@ ShadowMapper::getBandViewExtents(const Camera & camera, const glm::mat4 & lightV
}
ShadowMapper::Definitions
-ShadowMapper::update(const SceneProvider & scene, const Direction3D & dir, const Camera & camera) const
+ShadowMapper::update(const SceneProvider & scene, const LightDirection & dir, const Camera & camera) const
{
+ glCullFace(GL_FRONT);
+ glEnable(GL_DEPTH_TEST);
+
+ shadowStenciller.setLightDirection(dir);
+ for (const auto & [id, asset] : gameState->assets) {
+ if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) {
+ r->updateStencil(shadowStenciller);
+ }
+ }
+
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
- glCullFace(GL_FRONT);
glViewport(0, 0, size.x, size.y);
- const auto lightViewDir = glm::lookAt({}, dir, up);
+ const auto lightViewDir = glm::lookAt({}, dir.vector(), up);
const auto lightViewPoint = camera.getPosition();
const auto bandViewExtents = getBandViewExtents(camera, lightViewDir);
Definitions out;
+ Sizes sizes;
std::transform(bandViewExtents.begin(), std::prev(bandViewExtents.end()), std::next(bandViewExtents.begin()),
std::back_inserter(out),
- [bands = bandViewExtents.size() - 2, &lightViewDir](const auto & near, const auto & far) mutable {
- const auto extents_minmax = [extents = std::span {near.begin(), far.end()}](auto && comp) {
- const auto mm = std::minmax_element(extents.begin(), extents.end(), comp);
- return std::make_pair(comp.get(*mm.first), comp.get(*mm.second));
- };
+ [bands = bandViewExtents.size() - 2, &lightViewDir, &sizes](const auto & near, const auto & far) mutable {
+ const auto extents_minmax
+ = [extents = std::span {near.begin(), far.end()}](auto && comp, RelativeDistance extra) {
+ const auto mm = std::minmax_element(extents.begin(), extents.end(), comp);
+ return std::make_pair(comp.get(*mm.first) - extra, comp.get(*mm.second) + extra);
+ };
+ const std::array extents = {extents_minmax(CompareBy {0}, 0), extents_minmax(CompareBy {1}, 0),
+ extents_minmax(CompareBy {2}, 10'000)};
const auto lightProjection = [](const auto & x, const auto & y, const auto & z) {
return glm::ortho(x.first, x.second, y.first, y.second, -z.second, -z.first);
- }(extents_minmax(CompareBy {0}), extents_minmax(CompareBy {1}), extents_minmax(CompareBy {2}));
+ }(extents[0], extents[1], extents[2]);
+ sizes.emplace_back(extents[0].second - extents[0].first, extents[1].second - extents[1].first,
+ extents[2].second - extents[2].first);
return lightProjection * lightViewDir;
});
for (const auto p : std::initializer_list<const ShadowProgram *> {
- &landmess, &dynamicPoint, &dynamicPointInst, &dynamicPointInstWithTextures}) {
- p->setView(out, lightViewPoint);
+ &landmess, &dynamicPoint, &dynamicPointInst, &dynamicPointInstWithTextures, &stencilShadowProgram}) {
+ p->setView(out, sizes, lightViewPoint);
}
scene.shadows(*this);
@@ -116,12 +140,15 @@ ShadowMapper::ShadowProgram::ShadowProgram(const Shader & vs, const Shader & gs,
}
void
-ShadowMapper::ShadowProgram::setView(
- const std::span<const glm::mat4> viewProjection, const GlobalPosition3D viewPoint) const
+ShadowMapper::ShadowProgram::setView(const std::span<const glm::mat4x4> viewProjection,
+ const std::span<const RelativePosition3D> sizes, const GlobalPosition3D viewPoint) const
{
use();
glUniform(viewPointLoc, viewPoint);
glUniform(viewProjectionLoc, viewProjection);
+ if (sizesLoc) {
+ glUniform(sizesLoc, sizes);
+ }
glUniform(viewProjectionsLoc, static_cast<GLint>(viewProjection.size()));
}
@@ -146,3 +173,16 @@ ShadowMapper::DynamicPoint::setModel(const Location & location) const
glUniform(modelLoc, location.getRotationTransform());
glUniform(modelPosLoc, location.pos);
}
+
+ShadowMapper::StencilShadowProgram::StencilShadowProgram() :
+ ShadowProgram {shadowDynamicPointStencil_vs, shadowDynamicPointStencil_gs, shadowDynamicPointStencil_fs}
+{
+}
+
+void
+ShadowMapper::StencilShadowProgram::use(const RelativePosition3D & centre, const float size) const
+{
+ Program::use();
+ glUniform(centreLoc, centre);
+ glUniform(sizeLoc, size);
+}
diff --git a/gfx/gl/shadowMapper.h b/gfx/gl/shadowMapper.h
index 73dadd0..951e29c 100644
--- a/gfx/gl/shadowMapper.h
+++ b/gfx/gl/shadowMapper.h
@@ -1,6 +1,7 @@
#pragma once
#include "config/types.h"
+#include "gfx/gl/shadowStenciller.h"
#include "lib/glArrays.h"
#include "program.h"
#include <gfx/models/texture.h>
@@ -10,6 +11,7 @@
class SceneProvider;
class Camera;
+class LightDirection;
class ShadowMapper {
public:
@@ -18,20 +20,23 @@ public:
static constexpr std::size_t SHADOW_BANDS {4};
using Definitions = std::vector<glm::mat4x4>;
+ using Sizes = std::vector<RelativePosition3D>;
- [[nodiscard]] Definitions update(const SceneProvider &, const Direction3D & direction, const Camera &) const;
+ [[nodiscard]] Definitions update(const SceneProvider &, const LightDirection & direction, const Camera &) const;
class ShadowProgram : public Program {
public:
explicit ShadowProgram(const Shader & vs);
explicit ShadowProgram(const Shader & vs, const Shader & gs, const Shader & fs);
- void setView(const std::span<const glm::mat4>, const GlobalPosition3D) const;
+ void setView(const std::span<const glm::mat4x4>, const std::span<const RelativePosition3D>,
+ const GlobalPosition3D) const;
void use() const;
private:
RequiredUniformLocation viewProjectionLoc {*this, "viewProjection"};
RequiredUniformLocation viewProjectionsLoc {*this, "viewProjections"};
+ UniformLocation sizesLoc {*this, "sizes"};
RequiredUniformLocation viewPointLoc {*this, "viewPoint"};
};
@@ -46,8 +51,19 @@ public:
RequiredUniformLocation modelPosLoc {*this, "modelPos"};
};
+ class StencilShadowProgram : public ShadowProgram {
+ public:
+ StencilShadowProgram();
+ void use(const RelativePosition3D & centre, const float size) const;
+
+ private:
+ RequiredUniformLocation centreLoc {*this, "centre"};
+ RequiredUniformLocation sizeLoc {*this, "size"};
+ };
+
ShadowProgram landmess, dynamicPointInst, dynamicPointInstWithTextures;
DynamicPoint dynamicPoint;
+ StencilShadowProgram stencilShadowProgram;
// NOLINTNEXTLINE(hicpp-explicit-conversions)
operator GLuint() const
@@ -61,4 +77,5 @@ private:
glFrameBuffer depthMapFBO;
glTexture depthMap;
TextureAbsCoord size;
+ mutable ShadowStenciller shadowStenciller;
};
diff --git a/gfx/gl/shadowStenciller.cpp b/gfx/gl/shadowStenciller.cpp
new file mode 100644
index 0000000..86b77e4
--- /dev/null
+++ b/gfx/gl/shadowStenciller.cpp
@@ -0,0 +1,74 @@
+#include "shadowStenciller.h"
+#include "gfx/gl/program.h"
+#include "gfx/gl/shaders/fs-shadowStencil.h"
+#include "gfx/gl/shaders/gs-shadowStencil.h"
+#include "gfx/gl/shaders/vs-shadowStencil.h"
+#include "gfx/lightDirection.h"
+#include "gfx/models/mesh.h"
+#include "glArrays.h"
+#include "gl_traits.h"
+#include "maths.h"
+#include <stdexcept>
+
+ShadowStenciller::ShadowStenciller() :
+ shadowCaster {shadowStencil_vs, shadowStencil_gs, shadowStencil_fs}, viewProjections {}
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glDrawBuffer(GL_NONE);
+ glReadBuffer(GL_NONE);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+void
+ShadowStenciller::setLightDirection(const LightDirection & lightDir)
+{
+ viewProjections = [&lightDir]<GLint... Ep>(std::integer_sequence<GLint, Ep...>) {
+ constexpr float STEP = two_pi / STENCIL_ANGLES<decltype(two_pi)>;
+ return std::array {rotate_pitch<4>(half_pi - lightDir.position().y)
+ * rotate_yaw<4>((Ep * STEP) - lightDir.position().x)...};
+ }(std::make_integer_sequence<GLint, STENCIL_ANGLES<GLint>>());
+}
+
+glTexture
+ShadowStenciller::createStencilTexture(GLsizei width, GLsizei height)
+{
+ glTexture stencil;
+ glBindTexture(GL_TEXTURE_2D_ARRAY, stencil);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT, width, height, STENCIL_ANGLES<GLint>, 0,
+ GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, nullptr);
+
+ return stencil;
+}
+
+void
+ShadowStenciller::renderStencil(const glTexture & stencil, const MeshBase & mesh, const Texture::AnyPtr texture) const
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, stencil, 0);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ throw std::runtime_error("Stencil framebuffer not complete!");
+ }
+ if (texture) {
+ texture->bind();
+ }
+ glUseProgram(shadowCaster);
+ glClear(GL_DEPTH_BUFFER_BIT);
+ const auto stencilSize = Texture::getSize(stencil);
+ glViewport(0, 0, stencilSize.x, stencilSize.y);
+ const auto & centre = mesh.getDimensions().centre;
+ const auto & size = mesh.getDimensions().size;
+ glUniform(viewProjectionLoc,
+ std::span<const glm::mat4> {viewProjections *
+ [extentsMat = glm::translate(glm::ortho(-size, size, -size, size, -size, size), -centre)](
+ const auto & viewProjection) {
+ return viewProjection * extentsMat;
+ }});
+ mesh.Draw();
+}
diff --git a/gfx/gl/shadowStenciller.h b/gfx/gl/shadowStenciller.h
new file mode 100644
index 0000000..f774ac7
--- /dev/null
+++ b/gfx/gl/shadowStenciller.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "gfx/gl/program.h"
+#include "gfx/models/mesh.h"
+#include "gfx/models/texture.h"
+#include "glArrays.h"
+
+class LightDirection;
+
+class ShadowStenciller {
+public:
+ template<typename T> static constexpr T STENCIL_ANGLES = 8;
+
+ ShadowStenciller();
+
+ [[nodiscard]]
+ static glTexture createStencilTexture(GLsizei width, GLsizei height);
+ void setLightDirection(const LightDirection & lightDir);
+ void renderStencil(const glTexture &, const MeshBase &, Texture::AnyPtr texture) const;
+
+private:
+ glFrameBuffer fbo;
+ Program shadowCaster;
+ Program::RequiredUniformLocation viewProjectionLoc {shadowCaster, "viewProjection"};
+
+ std::array<glm::mat4, STENCIL_ANGLES<size_t>> viewProjections;
+};