psemek/libs/gfx/source/renderer/deferred.cpp

1133 lines
29 KiB
C++

#include <psemek/gfx/renderer/deferred.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/framebuffer.hpp>
#include <psemek/gfx/texture.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/error.hpp>
#include <psemek/geom/homogeneous.hpp>
#include <psemek/geom/gram_schmidt.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/geom/mesh.hpp>
#include <psemek/cg/convex_hull_2d/graham.hpp>
#include <fstream>
namespace psemek::gfx
{
static char const g_buffer_pass_common[] =
R"(#version 330
const uint O_UNIFORM_COLOR = 1u << 0;
const uint O_TEXTURE_COLOR = 1u << 1;
const uint O_TRANSPARENT = 1u << 2;
const uint O_LIT = 1u << 3;
const uint O_CASTS_SHADOW = 1u << 4;
const uint O_PRE_TRANSFORM = 1u << 5;
const uint O_POST_TRANSFORM = 1u << 6;
const uint O_INSTANCED = 1u << 7;
uniform uint u_flag_mask;
)";
static char const g_buffer_pass_vs[] =
R"(
uniform mat4 u_camera_transform;
uniform mat4x3 u_pre_transform;
uniform mat4x3 u_post_transform;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec4 in_color;
layout (location = 2) in vec2 in_texcoord;
layout (location = 3) in vec3 in_normal;
layout (location = 4) in mat3x4 in_instance_transform;
out vec3 position;
out vec4 color;
out vec2 texcoord;
out vec3 normal;
void main()
{
vec4 pos = in_position;
vec3 n = in_normal;
if ((u_flag_mask & O_PRE_TRANSFORM) != 0u)
{
pos = vec4(u_pre_transform * pos, 1.0);
n = u_pre_transform * vec4(n, 0.0);
}
if ((u_flag_mask & O_INSTANCED) != 0u)
{
pos = vec4(transpose(in_instance_transform) * pos, 1.0);
n = transpose(in_instance_transform) * vec4(n, 0.0);
}
if ((u_flag_mask & O_POST_TRANSFORM) != 0u)
{
pos = vec4(u_post_transform * pos, 1.0);
n = u_post_transform * vec4(n, 0.0);
}
position = pos.xyz;
gl_Position = u_camera_transform * pos;
color = in_color;
texcoord = in_texcoord;
normal = n;
}
)";
static char const g_buffer_pass_fs[] =
R"(
uniform vec4 u_color;
uniform sampler2D u_texture;
uniform vec3 u_material;
uniform float u_max_intensity;
in vec3 position;
in vec4 color;
in vec2 texcoord;
in vec3 normal;
layout (location = 0) out vec3 out0;
layout (location = 1) out vec4 out1;
layout (location = 2) out uint out2;
layout (location = 3) out vec3 out3;
uint pack_normal(vec3 n)
{
uint face = 0u;
float a0, a1;
vec3 an = abs(n);
if (an.x > an.y)
{
if (an.x > an.z)
{
bool p = (n.x > 0.0);
face = p ? 1u : 6u;
a0 = (p ? n.y : n.z) / an.x;
a1 = (p ? n.z : n.y) / an.x;
}
else
{
bool p = (n.z > 0.0);
face = p ? 4u : 3u;
a0 = (p ? n.x : n.y) / an.z;
a1 = (p ? n.y : n.x) / an.z;
}
}
else
{
if (an.y > an.z)
{
bool p = (n.y > 0.0);
face = p ? 2u : 5u;
a0 = (p ? n.z : n.x) / an.y;
a1 = (p ? n.x : n.z) / an.y;
}
else
{
bool p = (n.z > 0.0);
face = p ? 4u : 3u;
a0 = (p ? n.x : n.y) / an.z;
a1 = (p ? n.y : n.x) / an.z;
}
}
uint v0 = uint((a0 * 0.5 + 0.5) * float(1 << 15));
uint v1 = uint((a1 * 0.5 + 0.5) * float(1 << 14));
return (face << 29u) | (v1 << 15u) | v0;
}
void main()
{
vec4 albedo;
if ((u_flag_mask & O_TEXTURE_COLOR) != 0u)
{
vec4 base_color = texture(u_texture, texcoord);
if ((u_flag_mask & O_UNIFORM_COLOR) != 0u)
albedo = u_color * base_color;
else
albedo = base_color;
}
else
{
if ((u_flag_mask & O_UNIFORM_COLOR) != 0u)
albedo = u_color;
else
albedo = color;
}
out0 = position;
out1 = vec4(albedo.rgb / u_max_intensity, (u_flag_mask & O_LIT) != 0u ? 1.f : 0.f);
out2 = pack_normal(normalize(normal));
out3 = u_material;
}
)";
static char const shadow_builder_vs[] =
R"(
uniform mat4 u_light_transform;
uniform mat4x3 u_pre_transform;
uniform mat4x3 u_post_transform;
layout (location = 0) in vec4 in_position;
layout (location = 4) in mat3x4 in_instance_transform;
void main()
{
vec4 pos = in_position;
if ((u_flag_mask & O_PRE_TRANSFORM) != 0u)
{
pos = vec4(u_pre_transform * pos, 1.0);
}
if ((u_flag_mask & O_INSTANCED) != 0u)
{
pos = vec4(transpose(in_instance_transform) * pos, 1.0);
}
if ((u_flag_mask & O_POST_TRANSFORM) != 0u)
{
pos = vec4(u_post_transform * pos, 1.0);
}
gl_Position = u_light_transform * pos;
}
)";
static char const shadow_builder_gs[] =
R"(#version 330
uniform mat4 u_layer_transform[6];
layout (triangles) in;
layout (triangle_strip, max_vertices = 18) out;
void main()
{
for (int face = 0; face < 6; ++face)
{
gl_Layer = face;
for (int i = 0; i < 3; ++i)
{
gl_Position = u_layer_transform[face] * gl_in[i].gl_Position;
EmitVertex();
}
EndPrimitive();
}
}
)";
static char const shadow_builder_fs[] =
R"(#version 330
void main(){}
)";
static char const fullscreen_vs[] =
R"(#version 330
const vec4 vertices[6] = vec4[6](
vec4(-1.0, -1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0),
vec4( 1.0, 1.0, 0.0, 1.0),
vec4(-1.0, -1.0, 0.0, 1.0),
vec4( 1.0, 1.0, 0.0, 1.0),
vec4(-1.0, 1.0, 0.0, 1.0)
);
out vec2 texcoord;
void main()
{
gl_Position = vertices[gl_VertexID];
texcoord = vertices[gl_VertexID].xy * 0.5 + vec2(0.5);
}
)";
static char const screen_vs[] =
R"(#version 330
layout (location = 0) in vec2 in_position;
out vec2 texcoord;
void main()
{
gl_Position = vec4(in_position, 0.0, 1.0);
texcoord = in_position * 0.5 + vec2(0.5);
}
)";
static char const light_common[] =
R"(#version 330
uniform sampler2D u_g0;
uniform sampler2D u_g1;
uniform usampler2D u_g2;
uniform sampler2D u_g3;
in vec2 texcoord;
out vec4 out_color;
vec3 unpack_normal(uint v)
{
uint v0 = v & ((1u << 15) - 1u);
uint v1 = (v >> 15) & ((1u << 14) - 1u);
uint face = (v >> 29) & 7u;
float a0 = 2.0 * float(v0) / float(1 << 15) - 1.0;
float a1 = 2.0 * float(v1) / float(1 << 14) - 1.0;
if (face == 1u)
{
return normalize(vec3(1.0, a0, a1));
}
else if (face == 2u)
{
return normalize(vec3(a1, 1.0, a0));
}
else if (face == 3u)
{
return normalize(vec3(a1, a0, -1.0));
}
else if (face == 4u)
{
return normalize(vec3(a0, a1, 1.0));
}
else if (face == 5u)
{
return normalize(vec3(a0, -1.0, a1));
}
else if (face == 6u)
{
return normalize(vec3(-1.0, a1, a0));
}
return vec3(0.0, 0.0, 0.0);
}
)";
static char const ambient_pass_fs[] =
R"(
uniform vec3 u_ambient;
uniform float u_max_intensity;
void main()
{
vec4 albedo = texture(u_g1, texcoord);
vec3 color;
if (albedo.a < 0.5)
color = albedo.rgb;
else
color = albedo.rgb * u_ambient;
out_color = vec4(color, 1.0);
}
)";
static char const directional_light_pass_fs[] =
R"(
uniform vec3 u_light_direction;
uniform vec3 u_light_color;
uniform mat4 u_light_transform;
uniform sampler2DShadow u_shadow;
uniform int u_shadowed;
uniform vec3 u_camera_position;
uniform float u_max_intensity;
void main()
{
vec4 albedo = texture(u_g1, texcoord);
if (albedo.a < 0.5)
{
out_color = vec4(0.0);
return;
}
vec3 position = texture(u_g0, texcoord).xyz;
vec3 normal = unpack_normal(texture(u_g2, texcoord).r);
vec3 material = texture(u_g3, texcoord).xyz;
vec3 view = normalize(u_camera_position - position);
float d = dot(u_light_direction, normal);
vec3 refl = 2.0 * normal * d - u_light_direction;
float l = max(0.0, d) * material.x + pow(max(0.0, dot(view, refl)), material.z) * material.y;
vec3 color = l * albedo.rgb * u_light_color;
if (u_shadowed != 0)
{
vec4 shadow_space = u_light_transform * vec4(position, 1.0);
vec3 tc = shadow_space.xyz / shadow_space.w;
tc = tc * 0.5 + vec3(0.5);
if (d > 0.0 && tc.x >= 0.0 && tc.x <= 1.0 && tc.y >= 0.0 && tc.y <= 1.0 && tc.z >= 0.0)
{
color *= texture(u_shadow, tc);
}
}
out_color = vec4(color, 1.0);
}
)";
static char const point_light_pass_fs[] =
R"(
uniform vec3 u_light_position;
uniform vec3 u_light_color;
uniform vec3 u_light_attenuation;
uniform samplerCubeShadow u_shadow;
uniform int u_shadowed;
uniform vec3 u_shadow_far_negative;
uniform vec3 u_shadow_far_positive;
uniform float u_shadow_near;
uniform vec3 u_camera_position;
uniform float u_max_intensity;
void main()
{
vec4 albedo = texture(u_g1, texcoord);
if (albedo.a < 0.5)
{
out_color = vec4(0.0);
return;
}
vec3 position = texture(u_g0, texcoord).xyz;
vec3 normal = unpack_normal(texture(u_g2, texcoord).r);
vec3 material = texture(u_g3, texcoord).xyz;
vec3 view = normalize(u_camera_position - position);
vec3 light = u_light_position - position;
float r = length(light);
vec3 light_n = light / r;
float d = dot(light_n, normal);
vec3 refl = 2.0 * normal * d - light_n;
float l = max(0.0, d) * material.x + pow(max(0.0, dot(view, refl)), material.z) * material.y;
vec3 color = l * albedo.rgb * u_light_color / (u_light_attenuation.x + r * (u_light_attenuation.y + r * u_light_attenuation.z));
if (u_shadowed != 0 && d > 0.0)
{
vec3 dir = -light;
float far;
float near = u_shadow_near;
vec3 adir = abs(dir);
float d = max(adir.x, max(adir.y, adir.z));
if (d == adir.x)
{
if (dir.x > 0.0)
far = u_shadow_far_positive.x;
else
far = u_shadow_far_negative.x;
}
else if (d == adir.y)
{
if (dir.y > 0.0)
far = u_shadow_far_positive.y;
else
far = u_shadow_far_negative.y;
}
else
{
if (dir.z > 0.0)
far = u_shadow_far_positive.z;
else
far = u_shadow_far_negative.z;
}
float A = (far + near) / (far - near);
float B = - 2.0 * far * near / (far - near);
d = (A * d + B) / d;
float v = texture(u_shadow, vec4(dir, d * 0.5 + 0.5));
color *= v;
}
out_color = vec4(color, 1.0);
}
)";
static std::size_t bbox_to_screen_fan(geom::matrix<float, 4, 4> const & camera_transform, geom::box<float, 3> const & b, geom::point<float, 2> * result)
{
geom::point<float, 2> bbox_corners_screen[8];
auto bbox_corners_screen_end = bbox_corners_screen;
bool need_clipping = false;
for (int z = 0; z < 2; ++z)
{
for (int y = 0; y < 2; ++y)
{
for (int x = 0; x < 2; ++x)
{
geom::point<float, 3> p;
p[0] = geom::lerp<float>(b[0], x);
p[1] = geom::lerp<float>(b[1], y);
p[2] = geom::lerp<float>(b[2], z);
auto q = camera_transform * geom::homogeneous(p);
if (q[2] < -q[3] || q[2] > q[3])
{
need_clipping = true;
break;
}
*bbox_corners_screen_end++ = {q[0] / q[3], q[1] / q[3]};
}
}
}
if (need_clipping)
{
*result++ = {-1.f, -1.f};
*result++ = { 1.f, -1.f};
*result++ = { 1.f, 1.f};
*result++ = {-1.f, 1.f};
return 4;
}
geom::point<float, 2> * bbox_hull_screen_it[8];
auto bbox_hull_size = cg::graham_convex_hull(bbox_corners_screen, bbox_corners_screen_end, bbox_hull_screen_it) - bbox_hull_screen_it;
for (std::size_t i = 0; i < bbox_hull_size; ++i)
result[i] = *bbox_hull_screen_it[i];
return bbox_hull_size;
}
struct deferred_renderer::impl
{
gfx::program g_buffer_pass_program{std::string(g_buffer_pass_common) + g_buffer_pass_vs, std::string(g_buffer_pass_common) + g_buffer_pass_fs};
gfx::program ambient_pass_program{fullscreen_vs, std::string(light_common) + ambient_pass_fs};
gfx::program directional_light_pass_program{screen_vs, std::string(light_common) + directional_light_pass_fs};
gfx::program point_light_pass_program{screen_vs, std::string(light_common) + point_light_pass_fs};
gfx::program shadow_builder_program{std::string(g_buffer_pass_common) + shadow_builder_vs, shadow_builder_fs};
gfx::program cubemap_shadow_builder_program{std::string(g_buffer_pass_common) + shadow_builder_vs, shadow_builder_gs, shadow_builder_fs};
// G-buffer attachments:
// 0 - position (rbg)
// 1 - albedo (rgb), lit (a)
// 2 - normal (packed)
// 3 - material.diffuse (r), material.specular (g), material.shininess (b)
gfx::framebuffer g_framebuffer;
gfx::texture_2d g_buffer_texture[4];
gfx::texture_2d g_buffer_depth;
std::optional<geom::vector<std::size_t, 2>> g_buffer_size;
gfx::framebuffer directional_shadow_framebuffer;
gfx::texture_2d directional_shadow_texture;
gfx::framebuffer point_shadow_framebuffer;
gfx::texture_cubemap point_shadow_texture;
gfx::mesh screen_mesh;
};
static int const shadow_map_size = 1024;
deferred_renderer::deferred_renderer()
: pimpl_{std::make_unique<struct impl>()}
{
impl().g_buffer_pass_program.bind();
impl().g_buffer_pass_program["u_texture"] = 0;
for (std::size_t i = 0; i < 4; ++i)
{
impl().g_buffer_texture[i].nearest_filter();
}
impl().ambient_pass_program.bind();
impl().ambient_pass_program["u_g0"] = 0;
impl().ambient_pass_program["u_g1"] = 1;
impl().ambient_pass_program["u_g2"] = 2;
impl().ambient_pass_program["u_g3"] = 3;
impl().directional_light_pass_program.bind();
impl().directional_light_pass_program["u_g0"] = 0;
impl().directional_light_pass_program["u_g1"] = 1;
impl().directional_light_pass_program["u_g2"] = 2;
impl().directional_light_pass_program["u_g3"] = 3;
impl().directional_light_pass_program["u_shadow"] = 4;
impl().point_light_pass_program.bind();
impl().point_light_pass_program["u_g0"] = 0;
impl().point_light_pass_program["u_g1"] = 1;
impl().point_light_pass_program["u_g2"] = 2;
impl().point_light_pass_program["u_g3"] = 3;
impl().point_light_pass_program["u_shadow"] = 4;
impl().directional_shadow_texture.load<gfx::depth24_pixel>({shadow_map_size, shadow_map_size});
impl().directional_shadow_texture.linear_filter();
impl().directional_shadow_texture.clamp();
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL);
impl().directional_shadow_framebuffer.depth(impl().directional_shadow_texture);
impl().directional_shadow_framebuffer.assert_complete();
impl().point_shadow_texture.linear_filter();
impl().point_shadow_texture.clamp();
gl::TexParameteri(gl::TEXTURE_CUBE_MAP, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE);
gl::TexParameteri(gl::TEXTURE_CUBE_MAP, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL);
for (int f = 0; f < 6; ++f)
{
impl().point_shadow_texture.load<gfx::depth24_pixel>(f, {shadow_map_size, shadow_map_size});
}
impl().point_shadow_framebuffer.depth(impl().point_shadow_texture);
impl().point_shadow_framebuffer.assert_complete();
impl().screen_mesh.setup<geom::point<float, 2>>();
}
deferred_renderer::~deferred_renderer() = default;
static std::uint32_t const O_UNIFORM_COLOR = 1 << 0;
static std::uint32_t const O_TEXTURE_COLOR = 1 << 1;
static std::uint32_t const O_TRANSPARENT = 1 << 2;
static std::uint32_t const O_LIT = 1 << 3;
static std::uint32_t const O_CASTS_SHADOW = 1 << 4;
static std::uint32_t const O_PRE_TRANSFORM = 1 << 5;
static std::uint32_t const O_POST_TRANSFORM = 1 << 6;
static std::uint32_t const O_INSTANCED = 1 << 7;
std::uint32_t mask(deferred_renderer::object const & o)
{
std::uint32_t m = 0;
if (o.mat.color) m |= O_UNIFORM_COLOR;
if (o.mat.texture) m |= O_TEXTURE_COLOR;
if (o.mat.transparent) m |= O_TRANSPARENT;
if (o.mat.lit) m |= O_LIT;
if (o.casts_shadow) m |= O_CASTS_SHADOW;
if (o.pre_transform) m |= O_PRE_TRANSFORM;
if (o.post_transform) m |= O_POST_TRANSFORM;
if (o.mesh->is_instanced()) m |= O_INSTANCED;
return m;
}
static std::optional<gfx::mesh> test_mesh;
void deferred_renderer::render(std::vector<object> const & objects, render_target const & target, options const & opts)
{
// Get camera info
assert(opts.camera);
auto const camera_transform = opts.camera->transform();
auto const camera_position = opts.camera->position();
// Sort objects by mask & compute bbox
std::unordered_map<std::uint32_t, std::vector<std::size_t>> objects_by_mask;
geom::box<float, 3> lit_bbox;
geom::box<float, 3> casts_shadow_bbox;
for (std::size_t i = 0; i < objects.size(); ++i)
{
auto const & o = objects[i];
assert(o.mesh);
if (o.mat.transparent) throw std::runtime_error("Transparency is not supported yet");
objects_by_mask[mask(objects[i])].push_back(i);
if (o.mat.lit) lit_bbox |= o.bbox;
if (o.casts_shadow) casts_shadow_bbox |= o.bbox;
}
// Resize g-buffer if needed
auto buffer_size = geom::cast<std::size_t>(target.viewport.dimensions());
if (!impl().g_buffer_size || *impl().g_buffer_size != buffer_size)
{
impl().g_buffer_texture[0].load<geom::vector<gfx::float16, 3>>(buffer_size);
impl().g_buffer_texture[1].load<geom::vector<std::uint16_t, 4>>(buffer_size);
impl().g_buffer_texture[2].load<gfx::integer<std::uint32_t>>(buffer_size);
impl().g_buffer_texture[3].load<geom::vector<gfx::float16, 3>>(buffer_size);
impl().g_buffer_depth.load<gfx::depth24_pixel>(buffer_size);
if (!impl().g_buffer_size)
{
for (std::size_t i = 0; i < 4; ++i)
{
impl().g_framebuffer.color(impl().g_buffer_texture[i], i);
}
impl().g_framebuffer.depth(impl().g_buffer_depth);
}
impl().g_framebuffer.assert_complete();
impl().g_buffer_size = buffer_size;
}
// Setup g-buffer
impl().g_framebuffer.bind();
gl::Viewport(0, 0, target.viewport[0].length(), target.viewport[1].length());
GLenum g_draw_buffers[4] { gl::COLOR_ATTACHMENT0, gl::COLOR_ATTACHMENT1, gl::COLOR_ATTACHMENT2, gl::COLOR_ATTACHMENT3 };
gl::DrawBuffers(4, g_draw_buffers);
float buffer_1_clear[4] { 0.f, 0.f, 0.f, 0.f };
if (opts.clear_color)
{
buffer_1_clear[0] = (*opts.clear_color)[0] / opts.max_intensity;
buffer_1_clear[1] = (*opts.clear_color)[1] / opts.max_intensity;
buffer_1_clear[2] = (*opts.clear_color)[2] / opts.max_intensity;
}
gl::ClearBufferfv(gl::COLOR, 1, buffer_1_clear);
gl::ClearDepth(1.f);
gl::Clear(gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Disable(gl::BLEND);
gl::Enable(gl::CULL_FACE);
gl::CullFace(gl::BACK);
// Render to g-buffer
impl().g_buffer_pass_program.bind();
impl().g_buffer_pass_program["u_camera_transform"] = camera_transform;
impl().g_buffer_pass_program["u_max_intensity"] = opts.max_intensity;
for (auto const & p : objects_by_mask)
{
if (p.second.empty()) continue;
std::uint32_t mask = p.first;
impl().g_buffer_pass_program["u_flag_mask"] = mask;
for (std::size_t i : p.second)
{
auto const & o = objects[i];
if (mask & O_UNIFORM_COLOR)
impl().g_buffer_pass_program["u_color"] = *o.mat.color;
if (mask & O_TEXTURE_COLOR)
{
gl::ActiveTexture(gl::TEXTURE0);
o.mat.texture->bind();
}
if (mask & O_PRE_TRANSFORM)
impl().g_buffer_pass_program["u_pre_transform"] = *o.pre_transform;
if (mask & O_POST_TRANSFORM)
impl().g_buffer_pass_program["u_post_transform"] = *o.post_transform;
impl().g_buffer_pass_program["u_material"] = geom::vector<float, 3>{o.mat.diffuse, o.mat.specular.intensity, o.mat.specular.shininess};
o.mesh->draw();
}
}
// Setup destination framebuffer
target.bind();
gl::Disable(gl::DEPTH_TEST);
gl::Disable(gl::BLEND);
gl::ActiveTexture(gl::TEXTURE0);
impl().g_buffer_texture[0].bind();
gl::ActiveTexture(gl::TEXTURE1);
impl().g_buffer_texture[1].bind();
gl::ActiveTexture(gl::TEXTURE2);
impl().g_buffer_texture[2].bind();
gl::ActiveTexture(gl::TEXTURE3);
impl().g_buffer_texture[3].bind();
impl().screen_mesh.bind();
// Draw unlit & ambient layers
impl().ambient_pass_program.bind();
impl().ambient_pass_program["u_ambient"] = opts.ambient;
impl().ambient_pass_program["u_max_intensity"] = opts.max_intensity;
gl::DrawArrays(gl::TRIANGLES, 0, 6);
// Directional lights
geom::point<float, 2> bbox_hull_screen[8];
auto lit_bbox_hull_size = bbox_to_screen_fan(camera_transform, lit_bbox, bbox_hull_screen);
impl().screen_mesh.load(bbox_hull_screen, lit_bbox_hull_size, gl::TRIANGLE_FAN);
gl::ActiveTexture(gl::TEXTURE4);
impl().directional_shadow_texture.bind();
for (auto const & l : opts.directional_lights)
{
geom::matrix<float, 4, 4> light_transform;
if (l.shadowed)
{
geom::vector<float, 3> light_axes[3];
light_axes[2] = -geom::normalized(l.direction);
light_axes[1] = {0.f, 0.f, 1.f};
if (light_axes[2][2] > 0.5f)
light_axes[1] = {0.f, 1.f, 0.f};
geom::gram_schmidt(light_axes[2], light_axes[1]);
light_axes[0] = geom::cross(light_axes[2], light_axes[1]);
geom::box<float, 3> light_bbox;
geom::point<float, 3> origin = geom::point<float, 3>::zero();
for (auto const & v : geom::vertices(casts_shadow_bbox))
{
for (std::size_t i = 0; i < 3; ++i)
light_bbox[i] |= geom::dot(light_axes[i], v - origin);
}
light_transform = geom::orthographic_camera{light_bbox}.projection() * geom::homogeneous(geom::by_rows(light_axes[0], light_axes[1], light_axes[2]));
impl().directional_shadow_framebuffer.bind();
gl::DrawBuffer(gl::NONE);
gl::Viewport(0, 0, impl().directional_shadow_texture.width(), impl().directional_shadow_texture.height());
gl::Clear(gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Enable(gl::CULL_FACE);
gl::CullFace(gl::FRONT);
gl::Disable(gl::BLEND);
impl().shadow_builder_program.bind();
impl().shadow_builder_program["u_light_transform"] = light_transform;
for (auto const & p : objects_by_mask)
{
if (p.second.empty()) continue;
std::uint32_t mask = p.first;
if (!(mask & O_CASTS_SHADOW)) continue;
impl().shadow_builder_program["u_flag_mask"] = mask;
for (std::size_t i : p.second)
{
auto const & o = objects[i];
if (mask & O_PRE_TRANSFORM)
impl().shadow_builder_program["u_pre_transform"] = *o.pre_transform;
if (mask & O_POST_TRANSFORM)
impl().shadow_builder_program["u_post_transform"] = *o.post_transform;
o.mesh->draw();
}
}
}
target.bind();
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::ONE, gl::ONE);
gl::Disable(gl::DEPTH_TEST);
gl::CullFace(gl::BACK);
impl().directional_light_pass_program.bind();
impl().directional_light_pass_program["u_camera_position"] = camera_position;
impl().directional_light_pass_program["u_max_intensity"] = opts.max_intensity;
impl().directional_light_pass_program["u_light_direction"] = geom::normalized(l.direction);
impl().directional_light_pass_program["u_light_color"] = l.color;
impl().directional_light_pass_program["u_shadowed"] = l.shadowed;
if (l.shadowed) impl().directional_light_pass_program["u_light_transform"] = light_transform;
impl().screen_mesh.draw();
}
// Point lights
gl::Enable(gl::TEXTURE_CUBE_MAP_SEAMLESS);
gl::ActiveTexture(gl::TEXTURE4);
impl().point_shadow_texture.bind();
float min_intensity = opts.min_intensity.value_or(opts.max_intensity / 256.f);
for (auto const & l : opts.point_lights)
{
float I = std::max({l.color[0], l.color[1], l.color[2]});
auto r = geom::solve_quadratic(l.attenuation.c2, l.attenuation.c1, l.attenuation.c0 - I / min_intensity);
float near = l.min_shadow_distance;
geom::vector<float, 3> far_positive;
geom::vector<float, 3> far_negative;
for (std::size_t i = 0; i < 3; ++i)
{
far_positive[i] = casts_shadow_bbox[i].max - l.position[i];
far_negative[i] = l.position[i] - casts_shadow_bbox[i].min;
if (r)
{
far_positive[i] = std::min(far_positive[i], r->second);
far_negative[i] = std::min(far_negative[i], r->second);
}
far_positive[i] = std::max(far_positive[i], near);
far_negative[i] = std::max(far_negative[i], near);
}
if (l.shadowed)
{
geom::matrix<float, 4, 4> transform[6];
// +X
{
float far = far_positive[0];
transform[0] = geom::matrix<float, 4, 4>::zero();
transform[0][0][2] = -1.f;
transform[0][1][1] = -1.f;
transform[0][2][0] = (far + near) / (far - near);
transform[0][2][3] = - 2.f * far * near / (far - near);
transform[0][3][0] = 1.f;
}
// -X
{
float far = far_negative[0];
transform[1] = geom::matrix<float, 4, 4>::zero();
transform[1][0][2] = 1.f;
transform[1][1][1] = -1.f;
transform[1][2][0] = - (far + near) / (far - near);
transform[1][2][3] = - 2.f * far * near / (far - near);
transform[1][3][0] = - 1.f;
}
// +Y
{
float far = far_positive[1];
transform[2] = geom::matrix<float, 4, 4>::zero();
transform[2][0][0] = 1.f;
transform[2][1][2] = 1.f;
transform[2][2][1] = (far + near) / (far - near);
transform[2][2][3] = - 2.f * far * near / (far - near);
transform[2][3][1] = 1.f;
}
// -Y
{
float far = far_negative[1];
transform[3] = geom::matrix<float, 4, 4>::zero();
transform[3][0][0] = 1.f;
transform[3][1][2] = -1.f;
transform[3][2][1] = - (far + near) / (far - near);
transform[3][2][3] = - 2.f * far * near / (far - near);
transform[3][3][1] = - 1.f;
}
// +Z
{
float far = far_positive[2];
transform[4] = geom::matrix<float, 4, 4>::zero();
transform[4][0][0] = 1.f;
transform[4][1][1] = -1.f;
transform[4][2][2] = (far + near) / (far - near);
transform[4][2][3] = - 2.f * far * near / (far - near);
transform[4][3][2] = 1.f;
}
// -Z
{
float far = far_negative[2];
transform[5] = geom::matrix<float, 4, 4>::zero();
transform[5][0][0] = -1.f;
transform[5][1][1] = -1.f;
transform[5][2][2] = - (far + near) / (far - near);
transform[5][2][3] = - 2.f * far * near / (far - near);
transform[5][3][2] = - 1.f;
}
geom::matrix<float, 4, 4> const translate_by_light = geom::translation<float, 3>(geom::point<float, 3>::zero() - l.position).homogeneous_matrix();
impl().point_shadow_framebuffer.bind();
gl::DrawBuffer(gl::NONE);
gl::Viewport(0, 0, impl().point_shadow_texture.width(), impl().point_shadow_texture.height());
gl::Clear(gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Enable(gl::CULL_FACE);
gl::CullFace(gl::FRONT);
gl::Disable(gl::BLEND);
impl().cubemap_shadow_builder_program.bind();
impl().cubemap_shadow_builder_program["u_light_transform"] = geom::matrix<float, 4, 4>::identity();
// impl().cubemap_shadow_builder_program["u_layer_transform[0]"] = geom::matrix<float, 4, 4>::identity();
// impl().cubemap_shadow_builder_program["u_layer_transform[1]"] = geom::matrix<float, 4, 4>::identity();
// impl().cubemap_shadow_builder_program["u_layer_transform[2]"] = geom::matrix<float, 4, 4>::identity();
// impl().cubemap_shadow_builder_program["u_layer_transform[3]"] = geom::matrix<float, 4, 4>::identity();
// impl().cubemap_shadow_builder_program["u_layer_transform[4]"] = geom::matrix<float, 4, 4>::identity();
// impl().cubemap_shadow_builder_program["u_layer_transform[5]"] = geom::matrix<float, 4, 4>::identity();
impl().cubemap_shadow_builder_program["u_layer_transform[0]"] = transform[0] * translate_by_light;
impl().cubemap_shadow_builder_program["u_layer_transform[1]"] = transform[1] * translate_by_light;
impl().cubemap_shadow_builder_program["u_layer_transform[2]"] = transform[2] * translate_by_light;
impl().cubemap_shadow_builder_program["u_layer_transform[3]"] = transform[3] * translate_by_light;
impl().cubemap_shadow_builder_program["u_layer_transform[4]"] = transform[4] * translate_by_light;
impl().cubemap_shadow_builder_program["u_layer_transform[5]"] = transform[5] * translate_by_light;
for (auto const & p : objects_by_mask)
{
if (p.second.empty()) continue;
std::uint32_t mask = p.first;
if (!(mask & O_CASTS_SHADOW)) continue;
impl().cubemap_shadow_builder_program["u_flag_mask"] = mask;
for (std::size_t i : p.second)
{
auto const & o = objects[i];
if (mask & O_PRE_TRANSFORM)
impl().cubemap_shadow_builder_program["u_pre_transform"] = *o.pre_transform;
if (mask & O_POST_TRANSFORM)
impl().cubemap_shadow_builder_program["u_post_transform"] = *o.post_transform;
o.mesh->draw();
}
}
/*
gfx::pixmap_float pixmap({impl().point_shadow_texture.width(), impl().point_shadow_texture.height()});
impl().point_shadow_texture.pixels(1, gl::DEPTH_COMPONENT, gl::FLOAT, pixmap.data());
auto pixels = util::map([](float x){ return std::uint8_t(x * 255); }, pixmap);
std::ofstream out{"/home/lisyarus/depth.pgm"};
gfx::write_pgm(pixels, out);
//*/
}
target.bind();
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::ONE, gl::ONE);
gl::Disable(gl::DEPTH_TEST);
gl::CullFace(gl::BACK);
impl().point_light_pass_program.bind();
impl().point_light_pass_program["u_camera_position"] = camera_position;
impl().point_light_pass_program["u_max_intensity"] = opts.max_intensity;
impl().point_light_pass_program["u_shadowed"] = l.shadowed;
if (l.shadowed)
{
impl().point_light_pass_program["u_shadow_near"] = near;
impl().point_light_pass_program["u_shadow_far_positive"] = far_positive;
impl().point_light_pass_program["u_shadow_far_negative"] = far_negative;
}
geom::box<float, 3> light_bbox;
if (r)
{
float light_influence_radius = r->second;
for (std::size_t i = 0; i < 3; ++i)
light_bbox[i] = {l.position[i] - light_influence_radius, l.position[i] + light_influence_radius};
light_bbox &= lit_bbox;
}
else
{
light_bbox = lit_bbox;
}
auto light_bbox_hull_size = bbox_to_screen_fan(camera_transform, light_bbox, bbox_hull_screen);
impl().screen_mesh.load(bbox_hull_screen, light_bbox_hull_size, gl::TRIANGLE_FAN);
impl().point_light_pass_program["u_light_position"] = l.position;
impl().point_light_pass_program["u_light_color"] = l.color;
impl().point_light_pass_program["u_light_attenuation"] = geom::vector{l.attenuation.c0, l.attenuation.c1, l.attenuation.c2};
impl().screen_mesh.draw();
}
check_error();
}
}