#include #ifndef PSEMEK_GLES #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; const uint O_BLOOMING = 1u << 8; const uint O_BUMP_MAP = 1u << 9; 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 sampler2D u_bump_texture; uniform vec2 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 vec2 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; } vec3 n = normal; if ((u_flag_mask & O_BUMP_MAP) != 0u) { mat2 dtdf; dtdf[0] = dFdx(texcoord); dtdf[1] = dFdy(texcoord); mat2x3 dpdf; dpdf[0] = dFdx(position); dpdf[1] = dFdy(position); mat2x3 dpdt = dpdf * inverse(dtdf); vec3 z = n; vec3 x = normalize(dpdt[0]); vec3 y = normalize(dpdt[1]); vec3 v = texture(u_bump_texture, texcoord).xyz * 2.0 - vec3(1.0); n = normalize(v.x * x + v.y * y + v.z * z); } out0 = position; out1 = vec4(albedo.rgb / u_max_intensity, (u_flag_mask & O_LIT) != 0u ? 1.f : 0.5f); out2 = pack_normal(normalize(n)); 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 sampler2D u_ssao_texture; uniform int u_use_ssao; uniform float u_max_intensity; void main() { vec4 albedo = texture(u_g1, texcoord); vec3 color; if (albedo.a < 0.25) discard; else if (albedo.a < 0.75) color = albedo.rgb; else if (u_use_ssao == 1) color = albedo.rgb * u_ambient * texture(u_ssao_texture, texcoord).r; 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; #define MAX_CASCADES 8 uniform mat4 u_light_transform[MAX_CASCADES]; uniform sampler2DArrayShadow u_shadow; uniform int u_shadowed; uniform int u_cascades; 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); vec2 material = texture(u_g3, texcoord).xy; 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) + pow(max(0.0, dot(view, refl)), material.y) * material.x; vec3 color = l * albedo.rgb * u_light_color; if (u_shadowed != 0 && d > 0.0) { for (int cascade = 0; cascade < MAX_CASCADES; ++cascade) { if (cascade >= u_cascades) break; vec4 shadow_space = u_light_transform[cascade] * vec4(position, 1.0); vec3 tc = shadow_space.xyz / shadow_space.w; tc = tc * 0.5 + vec3(0.5); if (tc.x >= 0.0 && tc.x <= 1.0 && tc.y >= 0.0 && tc.y <= 1.0 && tc.z >= 0.0) { // int l = cascade + 1; // color = vec3(float(l & 1), float(l & 2) / 2.0, float(l & 4) / 4.0); // break; float s = 0.0; for (int tx = -2; tx <= 2; ++tx) { for (int ty = -2; ty <= 2; ++ty) { float d = 1.0 / 1024.0 / pow(2.0, float(cascade)); s += texture(u_shadow, vec4(tc.x + float(tx) * d, tc.y + float(ty) * d, float(cascade), tc.z)) / 25.0; } } color *= s; break; } } } 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); vec2 material = texture(u_g3, texcoord).xy; 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) + pow(max(0.0, dot(view, refl)), material.y) * material.x; 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 char const transparent_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 vec4 color; out vec2 texcoord; 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); } gl_Position = u_camera_transform * pos; color = in_color; texcoord = in_texcoord; } )"; static char const transparent_pass_fs[] = R"( uniform vec4 u_color; uniform sampler2D u_texture; uniform float u_max_intensity; in vec4 color; in vec2 texcoord; layout (location = 0) out vec4 out_color; 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; } out_color = vec4(albedo.rgb / u_max_intensity, albedo.a); } )"; static char const ssao_pass_fs[] = R"( uniform sampler2D u_ssao_rotation; uniform vec2 u_ssao_rotation_step; uniform float u_ssao_radius; uniform vec3 u_ssao_kernel[32]; uniform mat4 u_view; uniform mat4 u_transform; void main() { vec4 albedo = texture(u_g1, texcoord); if (albedo.a < 0.5) { out_color = vec4(1.0); return; } vec3 position = texture(u_g0, texcoord).xyz; vec3 normal = unpack_normal(texture(u_g2, texcoord).r); vec3 tangent = texture(u_ssao_rotation, texcoord * u_ssao_rotation_step).xyz; tangent = normalize(tangent - normal * dot(normal, tangent)); vec3 bitangent = cross(normal, tangent); mat3 tbn = mat3(tangent, bitangent, normal); float occlusion = 0.0; for (int i = 0; i < 32; ++i) { vec3 sample = position + (tbn * u_ssao_kernel[i]) * u_ssao_radius; vec4 clip = u_transform * vec4(sample, 1.0); clip.xy /= clip.w; clip.xy = clip.xy * 0.5 + vec2(0.5); vec3 pos = texture(u_g0, clip.xy).xyz; float w = length(pos - position) / u_ssao_radius; if (w <= 1.0 && dot(pos - position, normal) >= 0.f && (u_view * vec4(pos, 1.0)).z > (u_view * vec4(sample, 1.0)).z) occlusion += 1.0 / 32.0; } out_color = vec4(1.0 - occlusion); } )"; char const occlusion_pass_vs[] = R"(#version 330 uniform mat4 u_camera_transform; uniform vec3 u_box_min; uniform vec3 u_box_max; vec3 vertices[8] = vec3[8]( vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), vec3(1.0, 1.0, 1.0) ); ivec3 faces[12] = ivec3[12]( // -Z ivec3(0, 2, 1), ivec3(1, 2, 3), // +Z ivec3(4, 5, 6), ivec3(6, 5, 7), // -Y ivec3(0, 1, 4), ivec3(4, 1, 5), // +Y ivec3(2, 6, 3), ivec3(6, 7, 3), // -X ivec3(0, 4, 2), ivec3(2, 4, 6), // +X ivec3(1, 3, 5), ivec3(3, 7, 5) ); void main() { ivec3 face = faces[gl_VertexID / 3]; int i = gl_VertexID % 3; vec3 pos = vertices[(i == 0) ? face.x : (i == 1) ? face.y : face.z]; gl_Position = u_camera_transform * vec4(pos * (u_box_max - u_box_min) + u_box_min, 1.0); } )"; char const occlusion_pass_fs[] = R"(#version 330 void main(){} )"; static std::size_t bbox_to_screen_fan(math::matrix const & camera_transform, math::box const & b, math::point * result) { math::point 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) { math::point p; p[0] = math::lerp(b[0], x); p[1] = math::lerp(b[1], y); p[2] = math::lerp(b[2], z); auto q = camera_transform * math::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; } math::point * 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 { deferred_renderer::position_mode position_mode = deferred_renderer::position_mode::float32; bool position_mode_changed = true; gfx::program occlusion_pass_program{occlusion_pass_vs, occlusion_pass_fs}; 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}; gfx::program transparent_pass_program{std::string(g_buffer_pass_common) + transparent_pass_vs, std::string(g_buffer_pass_common) + transparent_pass_fs}; gfx::program bloom_pass_program{std::string(g_buffer_pass_common) + transparent_pass_vs, std::string(g_buffer_pass_common) + transparent_pass_fs}; gfx::program ssao_pass_program{fullscreen_vs, std::string(light_common) + ssao_pass_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::framebuffer occlusion_framebuffer; gfx::framebuffer bg_framebuffer; gfx::texture_2d g_buffer_texture[4]; gfx::texture_2d g_buffer_depth; // Only albedo & depth attached gfx::framebuffer transparent_framebuffer; std::optional> g_buffer_size; gfx::framebuffer directional_shadow_framebuffer; gfx::texture_2d_array directional_shadow_texture; gfx::framebuffer point_shadow_framebuffer; gfx::texture_cubemap point_shadow_texture; std::map, hblur> hblur_container; std::map, vblur> vblur_container; overlay bloom_overlay; std::optional> bloom_size; std::optional bloom_downsample; // 0: original, unblurred bloom // 1: horizontally blurred bloom // 2: fully blurred bloom gfx::framebuffer bloom_framebuffer[3]; gfx::texture_2d bloom_texture[3]; gfx::texture_2d ssao_rotation_texture; std::optional> ssao_size; std::optional ssao_downsample; // 0: original, unblurred ssao // 1: horizontally blurred ssao // 2: fully blurred ssao gfx::framebuffer ssao_framebuffer[3]; gfx::texture_2d ssao_texture[3]; hblur ssao_hblur{5, 2.f}; vblur ssao_vblur{5, 2.f}; gfx::mesh screen_mesh; std::vector queries; gfx::array box_array; std::optional> prev_transform; }; deferred_renderer::deferred_renderer() : pimpl_{make_impl()} { impl().g_buffer_pass_program.bind(); impl().g_buffer_pass_program["u_texture"] = 0; impl().g_buffer_pass_program["u_bump_texture"] = 1; for (std::size_t i = 0; i < 4; ++i) { impl().g_buffer_texture[i].nearest_filter(); } impl().g_buffer_depth.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().ambient_pass_program["u_ssao_texture"] = 4; 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.linear_filter(); impl().directional_shadow_texture.clamp(); gl::TexParameteri(impl().directional_shadow_texture.target, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE); gl::TexParameteri(impl().directional_shadow_texture.target, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL); impl().point_shadow_texture.linear_filter(); impl().point_shadow_texture.clamp(); gl::TexParameteri(impl().point_shadow_texture.target, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE); gl::TexParameteri(impl().point_shadow_texture.target, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL); for (std::size_t i = 0; i < 3; ++i) { impl().bloom_texture[i].linear_filter(); impl().bloom_texture[i].clamp(); } { // params are random random::generator rng{0xaa8e8eabull, 0x44a700e7ull}; { random::uniform_hemiball_vector_distribution d{math::vector{0.f, 0.f, 1.f}}; std::vector> ssao_kernel(32); for (auto & v : ssao_kernel) v = d(rng); impl().ssao_pass_program.bind(); for (std::size_t i = 0; i < ssao_kernel.size(); ++i) { impl().ssao_pass_program[util::to_string("u_ssao_kernel[", i, "]").data()] = ssao_kernel[i]; } } { random::uniform_sphere_vector_distribution d; basic_pixmap> pm({8, 8}); for (auto & v : pm) v = d(rng); impl().ssao_rotation_texture.load(pm); impl().ssao_rotation_texture.nearest_filter(); impl().ssao_rotation_texture.repeat(); } } for (std::size_t i = 0; i < 3; ++i) { impl().ssao_texture[i].linear_filter(); impl().ssao_texture[i].clamp(); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_G, gl::RED); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_B, gl::RED); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_A, gl::RED); } impl().ssao_pass_program.bind(); impl().ssao_pass_program["u_g0"] = 0; impl().ssao_pass_program["u_g1"] = 1; impl().ssao_pass_program["u_g2"] = 2; impl().ssao_pass_program["u_g3"] = 3; impl().ssao_pass_program["u_ssao_rotation"] = 4; impl().screen_mesh.setup>(); } deferred_renderer::~deferred_renderer() = default; void deferred_renderer::set_position_mode(position_mode mode) { impl().position_mode = mode; impl().position_mode_changed = true; } 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; static std::uint32_t const O_BLOOMING = 1 << 8; static std::uint32_t const O_BUMP_MAP = 1 << 9; 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.mat->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; if (o.mat->blooming) m |= O_BLOOMING; if (o.mat->bump_map) m |= O_BUMP_MAP; return m; } void deferred_renderer::render(std::vector const & objects, render_target const & target, options const & opts) { // Allocate query objects std::size_t const bins_total = opts.grid_size[0] * opts.grid_size[1] * opts.grid_size[2]; if (impl().queries.size() < bins_total) { auto old_size = impl().queries.size(); impl().queries.resize(bins_total); gl::GenQueries(bins_total - old_size, impl().queries.data() + old_size); } // Get camera info assert(opts.camera); auto const camera_transform = opts.camera->transform(); auto const camera_position = opts.camera->position(); auto const camera_direction = opts.camera->direction(); auto const camera_clip_planes = opts.camera->clip_planes(); cg::frustum camera_frustum(camera_transform); // Sort objects by mask & compute bbox struct objects_bucket { std::vector objects; std::size_t first_visible; }; struct bin { util::hash_map, objects_bucket> buckets; math::box bbox; bool contains_near_clip = false; float camera_separation; }; math::box all_bbox; std::vector camera_distance(objects.size()); for (std::size_t i = 0; i < objects.size(); ++i) { auto const & o = objects[i]; all_bbox |= o.bbox; float dist = -std::numeric_limits::infinity(); for (int c = 0; c < 8; ++c) { dist = std::max(dist, dot(o.bbox.corner(c & 1, (c & 2) >> 1, (c & 4) >> 1) - camera_position, camera_direction)); } camera_distance[i] = dist; } util::array bins({opts.grid_size[0], opts.grid_size[1], opts.grid_size[2]}); math::box lit_bbox; math::box casts_shadow_bbox; for (std::size_t i = 0; i < objects.size(); ++i) { auto const & o = objects[i]; assert(o.mesh); if (o.mat->lit && o.mat->transparent) throw util::exception("Materials that are both lit & transparent are not supported"); if (o.mat->lit && o.mat->blooming) throw util::exception("Materials that are both lit & blooming are not supported"); if (o.mat->casts_shadow && o.mat->transparent) throw util::exception("Transparent objects cannot cast shadow"); auto c = o.bbox.center() - all_bbox.corner(0, 0, 0); int bx = math::clamp(std::floor(c[0] / all_bbox[0].length() * opts.grid_size[0]), {0, opts.grid_size[0] - 1}); int by = math::clamp(std::floor(c[1] / all_bbox[1].length() * opts.grid_size[1]), {0, opts.grid_size[1] - 1}); int bz = math::clamp(std::floor(c[2] / all_bbox[2].length() * opts.grid_size[2]), {0, opts.grid_size[2] - 1}); bin & b = bins(bx, by, bz); b.bbox |= o.bbox; b.buckets[std::tuple{mask(o), o.mat}].objects.push_back(i); if (o.mat->lit) lit_bbox |= o.bbox; if (o.mat->casts_shadow) casts_shadow_bbox |= o.bbox; } for (auto & b : bins) { b.bbox = math::expand(b.bbox, b.bbox.dimensions() / 64.f); b.camera_separation = cg::separation(camera_frustum, cg::box{b.bbox}).distance; b.contains_near_clip = true; for (int i = 0; i < 4; ++i) b.contains_near_clip &= math::contains(b.bbox, camera_frustum.vertices[i]); for (auto & p : b.buckets) { std::sort(p.second.objects.begin(), p.second.objects.end(), [&](auto i, auto j){ return camera_distance[i] < camera_distance[j]; }); p.second.first_visible = std::partition_point(p.second.objects.begin(), p.second.objects.end(), [&](auto i){ return camera_distance[i] < 0.f; }) - p.second.objects.begin(); } } bool use_occlusion = false; auto render_all = [&](auto & program, auto && predicate, bool clip_by_camera = false) { for (int bi = 0; bi < bins_total; ++bi) { auto const & b = bins.data()[bi]; if (clip_by_camera && b.camera_separation > 0.f) continue; bool const bin_use_occlusion = clip_by_camera && !b.contains_near_clip && !b.buckets.empty() && use_occlusion; if (bin_use_occlusion) gl::BeginConditionalRender(impl().queries[bi], gl::QUERY_NO_WAIT); for (auto const & p : b.buckets) { if (p.second.objects.empty()) continue; std::uint32_t mask = std::get<0>(p.first); if (!predicate(mask)) continue; material const * mat = std::get<1>(p.first); program["u_flag_mask"] = mask; if (mask & O_UNIFORM_COLOR) program["u_color"] = *(mat->color); if (mask & O_TEXTURE_COLOR) { gl::ActiveTexture(gl::TEXTURE0); mat->texture->bind(); } if (mask & O_BUMP_MAP) { gl::ActiveTexture(gl::TEXTURE1); mat->bump_map->bind(); } program["u_material"] = math::vector{mat->specular.intensity, mat->specular.shininess}; for (std::size_t i = clip_by_camera ? p.second.first_visible : 0; i < p.second.objects.size(); ++i) { std::size_t id = p.second.objects[i]; auto const & o = objects[id]; if (mask & O_PRE_TRANSFORM) program["u_pre_transform"] = *o.pre_transform; if (mask & O_POST_TRANSFORM) program["u_post_transform"] = *o.post_transform; o.mesh->draw(); } } if (bin_use_occlusion) gl::EndConditionalRender(); } }; // Resize g-buffer if needed auto const buffer_size = math::cast(target.viewport.dimensions()); bool const buffer_size_changed = !impl().g_buffer_size || *impl().g_buffer_size != buffer_size; if (buffer_size_changed || impl().position_mode_changed) { if (impl().position_mode == position_mode::float16) impl().g_buffer_texture[0].load>(buffer_size); else if (impl().position_mode == position_mode::float32) impl().g_buffer_texture[0].load>(buffer_size); if (buffer_size_changed) { impl().g_buffer_texture[1].load>(buffer_size); impl().g_buffer_texture[2].load>(buffer_size); impl().g_buffer_texture[3].load>(buffer_size); impl().g_buffer_depth.load(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().occlusion_framebuffer.depth(impl().g_buffer_depth); impl().bg_framebuffer.color(impl().g_buffer_texture[1]); impl().g_framebuffer.assert_complete(); impl().occlusion_framebuffer.assert_complete(); impl().bg_framebuffer.assert_complete(); impl().transparent_framebuffer.color(impl().g_buffer_texture[1]); impl().transparent_framebuffer.depth(impl().g_buffer_depth); impl().transparent_framebuffer.assert_complete(); impl().g_buffer_size = buffer_size; impl().position_mode_changed = false; } bool const bloom_size_changed = opts.bloom && (!impl().bloom_size || *impl().bloom_size != buffer_size || !impl().bloom_downsample || *impl().bloom_downsample != opts.bloom->downsample); if (bloom_size_changed) { impl().bloom_texture[0].load(buffer_size); impl().bloom_framebuffer[0].color(impl().bloom_texture[0]); impl().bloom_framebuffer[0].depth(impl().g_buffer_depth); impl().bloom_framebuffer[0].assert_complete(); impl().bloom_texture[1].load({buffer_size[0] / opts.bloom->downsample, buffer_size[1]}); impl().bloom_framebuffer[1].color(impl().bloom_texture[1]); impl().bloom_framebuffer[1].assert_complete(); impl().bloom_texture[2].load(buffer_size / opts.bloom->downsample); impl().bloom_framebuffer[2].color(impl().bloom_texture[2]); impl().bloom_framebuffer[2].assert_complete(); impl().bloom_size = buffer_size; impl().bloom_downsample = opts.bloom->downsample; } bool const ssao_size_changed = opts.ssao && (!impl().ssao_size || *impl().ssao_size != buffer_size || !impl().ssao_downsample || *impl().ssao_downsample != opts.ssao->downsample); if (ssao_size_changed) { for (std::size_t i = 0; i < 3; ++i) { impl().ssao_texture[i].load(buffer_size / opts.ssao->downsample); impl().ssao_framebuffer[i].color(impl().ssao_texture[i]); impl().ssao_framebuffer[i].assert_complete(); } impl().ssao_size = buffer_size; impl().ssao_downsample = opts.ssao->downsample; } std::optional> hblur_params; std::optional> vblur_params; if (opts.bloom) { hblur_params = std::pair{opts.bloom->size, opts.bloom->sigma}; if (impl().hblur_container.count(*hblur_params) == 0) { auto params = std::tuple{hblur_params->first, hblur_params->second}; impl().hblur_container.emplace(std::piecewise_construct, params, params); } vblur_params = std::pair{opts.bloom->size, opts.bloom->sigma}; if (impl().vblur_container.count(*vblur_params) == 0) { auto params = std::tuple{vblur_params->first, vblur_params->second}; impl().vblur_container.emplace(std::piecewise_construct, params, params); } } // Occlusion pre-pass if (!buffer_size_changed && impl().prev_transform) { use_occlusion = true; impl().occlusion_framebuffer.bind(); gl::Viewport(0, 0, target.viewport[0].length(), target.viewport[1].length()); gl::DepthMask(gl::FALSE); gl::Enable(gl::DEPTH_TEST); gl::DepthFunc(gl::LEQUAL); gl::Enable(gl::DEPTH_CLAMP); gl::Disable(gl::CULL_FACE); impl().occlusion_pass_program.bind(); impl().occlusion_pass_program["u_camera_transform"] = *impl().prev_transform; impl().box_array.bind(); for (int bi = 0; bi < bins_total; ++bi) { auto & b = bins.data()[bi]; if (b.buckets.empty()) continue; if (b.contains_near_clip) continue; if (b.camera_separation > 0.f) continue; impl().occlusion_pass_program["u_box_min"] = math::vector{b.bbox[0].min, b.bbox[1].min, b.bbox[2].min}; impl().occlusion_pass_program["u_box_max"] = math::vector{b.bbox[0].max, b.bbox[1].max, b.bbox[2].max}; gl::BeginQuery(gl::ANY_SAMPLES_PASSED, impl().queries[bi]); gl::DrawArrays(gl::TRIANGLES, 0, 36); gl::EndQuery(gl::ANY_SAMPLES_PASSED); } gl::DepthMask(gl::TRUE); gl::Disable(gl::DEPTH_CLAMP); } // 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; buffer_1_clear[3] = 0.f; } gl::ClearBufferfv(gl::COLOR, 1, buffer_1_clear); if (opts.background_generator) { impl().bg_framebuffer.bind(); gl::Enable(gl::DEPTH_TEST); gl::DepthFunc(gl::LEQUAL); gl::Disable(gl::BLEND); opts.background_generator(); impl().g_framebuffer.bind(); } 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; render_all(impl().g_buffer_pass_program, [](auto mask){ return !(mask & O_TRANSPARENT); }, true); // Render unlit transparent objects gl::DrawBuffer(gl::COLOR_ATTACHMENT0); impl().transparent_framebuffer.bind(); impl().transparent_pass_program.bind(); impl().transparent_pass_program["u_camera_transform"] = camera_transform; impl().transparent_pass_program["u_max_intensity"] = 1.f; gl::Enable(gl::BLEND); gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::ZERO, gl::ONE); gl::DepthMask(gl::FALSE); render_all(impl().transparent_pass_program, [](auto mask){ return (mask & O_TRANSPARENT); }, true); // Render bloom if (opts.bloom) { impl().bloom_framebuffer[0].bind(); gl::ClearColor(0.f, 0.f, 0.f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT); gl::Enable(gl::DEPTH_TEST); gl::DepthFunc(gl::LEQUAL); gl::Disable(gl::BLEND); impl().bloom_pass_program.bind(); impl().bloom_pass_program["u_camera_transform"] = camera_transform; impl().bloom_pass_program["u_max_intensity"] = opts.max_intensity; render_all(impl().bloom_pass_program, [](auto mask){ return (mask & O_BLOOMING); }, true); } // Render unlit transparent objects to bloom if (opts.bloom) { impl().transparent_pass_program.bind(); impl().transparent_pass_program["u_camera_transform"] = camera_transform; impl().transparent_pass_program["u_max_intensity"] = 1.f; gl::Enable(gl::BLEND); gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::ZERO, gl::ONE); render_all(impl().transparent_pass_program, [](auto mask){ return (mask & O_TRANSPARENT); }, true); } gl::Disable(gl::BLEND); gl::DepthMask(gl::TRUE); // Apply horizontal blur to bloom if (opts.bloom) { render_target target; target.framebuffer = &impl().bloom_framebuffer[1]; target.draw_buffer = gl::COLOR_ATTACHMENT0; target.viewport = {{{0, impl().bloom_texture[1].width()}, {0, impl().bloom_texture[1].height()}}}; target.bind(); gl::ClearColor(0.f, 0.f, 0.f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT); impl().hblur_container.at(*hblur_params).invoke(impl().bloom_texture[0], target); } // Apply vertical blur to bloom if (opts.bloom) { render_target target; target.framebuffer = &impl().bloom_framebuffer[2]; target.draw_buffer = gl::COLOR_ATTACHMENT0; target.viewport = {{{0, impl().bloom_texture[2].width()}, {0, impl().bloom_texture[2].height()}}}; target.bind(); gl::ClearColor(0.f, 0.f, 0.f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT); impl().vblur_container.at(*vblur_params).invoke(impl().bloom_texture[1], target); } // Bind g-buffer textures 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(); gl::Disable(gl::DEPTH_TEST); gl::Disable(gl::BLEND); // Generate SSAO if (opts.ssao) { impl().ssao_framebuffer[0].bind(); gl::Viewport(0, 0, impl().ssao_texture[0].width(), impl().ssao_texture[0].height()); gl::ClearColor(0.f, 0.f, 0.f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT); impl().ssao_pass_program.bind(); gl::ActiveTexture(gl::TEXTURE4); impl().ssao_rotation_texture.bind(); impl().ssao_pass_program["u_ssao_rotation_step"] = math::cast(impl().ssao_texture[0].size()) / (1.f * impl().ssao_rotation_texture.width()); impl().ssao_pass_program["u_ssao_radius"] = opts.ssao->radius; impl().ssao_pass_program["u_transform"] = camera_transform; impl().ssao_pass_program["u_view"] = opts.camera->view(); gl::DrawArrays(gl::TRIANGLES, 0, 6); } // Blur ssao horizontally if (opts.ssao) { render_target target; target.framebuffer = &impl().ssao_framebuffer[1]; target.draw_buffer = gl::COLOR_ATTACHMENT0; target.viewport = {{{0, impl().ssao_texture[1].width()}, {0, impl().ssao_texture[1].height()}}}; target.bind(); gl::ClearColor(0.f, 0.f, 0.f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT); impl().ssao_hblur.invoke(impl().ssao_texture[0], target); } // Blur ssao vertically if (opts.ssao) { render_target target; target.framebuffer = &impl().ssao_framebuffer[2]; target.draw_buffer = gl::COLOR_ATTACHMENT0; target.viewport = {{{0, impl().ssao_texture[2].width()}, {0, impl().ssao_texture[2].height()}}}; target.bind(); gl::ClearColor(0.f, 0.f, 0.f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT); impl().ssao_vblur.invoke(impl().ssao_texture[1], target); } // Setup destination framebuffer gl::ActiveTexture(gl::TEXTURE0); impl().g_buffer_texture[0].bind(); target.bind(); gl::Clear(gl::COLOR_BUFFER_BIT); 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; if (opts.ssao) { gl::ActiveTexture(gl::TEXTURE4); impl().ssao_texture[2].bind(); impl().ambient_pass_program["u_use_ssao"] = 1; } else { impl().ambient_pass_program["u_use_ssao"] = 0; } gl::DrawArrays(gl::TRIANGLES, 0, 6); // Directional lights math::point 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) { std::vector> light_transform; if (l.shadowed) { if (l.cascades == 0) throw util::exception("The number of shadow map cascades should be positive"); if (l.cascades > 8) throw util::exception("More than 8 shadow map cascades are not supported"); light_transform.resize(l.cascades); // Compute cascade split points auto dir = math::as_vector(camera_clip_planes[4]); std::vector> cascade_splits(l.cascades); { float offset; auto dir_length = math::length(dir); float near = camera_clip_planes[4][3] / dir_length; dir /= dir_length; offset = math::dot(camera_position - math::point::zero(), dir); near += offset; float far = -camera_clip_planes[5][3] / math::length(math::as_vector(camera_clip_planes[5])) - offset; if (l.cascade_ranges.empty()) { float lambda = 1.f / 24.f; for (int i = 0; i < l.cascades; ++i) { cascade_splits[i].max = (1.f - lambda) * near * std::pow(far / near, i * 1.f / l.cascades) + lambda * (near + (far - near) * (i * 1.f / l.cascades)); cascade_splits[i].min = (1.f - lambda) * near * std::pow(far / near, (i + 1) * 1.f / l.cascades) + lambda * (near + (far - near) * ((i + 1) * 1.f / l.cascades)); cascade_splits[i] -= offset; } } else { for (int i = 0; i < l.cascades; ++i) { cascade_splits[i] = - l.cascade_ranges[i] - offset; } } } std::size_t dir_max_index = 0; if (std::abs(dir[1]) > std::abs(dir[dir_max_index])) dir_max_index = 1; if (std::abs(dir[2]) > std::abs(dir[dir_max_index])) dir_max_index = 2; if (impl().directional_shadow_texture.width() != l.shadow_map_size || impl().directional_shadow_texture.depth() != l.cascades) { impl().directional_shadow_texture.load({l.shadow_map_size, l.shadow_map_size, l.cascades}); } math::vector light_axes[3]; light_axes[2] = -math::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}; math::gram_schmidt(light_axes[2], light_axes[1]); light_axes[0] = math::cross(light_axes[2], light_axes[1]); for (int cascade = 0; cascade < l.cascades; ++cascade) { math::box shadowed_bbox; math::matrix cascade_transform = camera_transform; { float near = cascade_splits[cascade].max; float far = cascade_splits[cascade].min; math::vector b; b[0] = -2.f * cascade_transform[3][dir_max_index]; b[1] = -2.f * cascade_transform[3][3]; math::matrix m; m[0][0] = - dir[dir_max_index]; m[0][1] = dir[dir_max_index]; m[1][0] = -near; m[1][1] = far; math::gauss(m, b); for (std::size_t i = 0; i < 3; ++i) cascade_transform[2][i] = dir[i] * (b[0] + b[1]) / 2.f; cascade_transform[2][3] = (b[0] * near + b[1] * far) / 2.f; } cg::frustum cascade_frustum{cascade_transform}; for (auto v : cg::vertices(cascade_frustum)) shadowed_bbox |= v; shadowed_bbox &= casts_shadow_bbox; static auto const origin = math::point::zero(); math::box light_bbox; for (auto const & v : math::vertices(shadowed_bbox)) { for (std::size_t i = 0; i < 3; ++i) light_bbox[i] |= math::dot(light_axes[i], v - origin); } for (auto const & v : math::vertices(casts_shadow_bbox)) light_bbox[2] |= math::dot(light_axes[2], v - origin); light_transform[cascade] = math::orthographic_camera{light_bbox}.projection() * math::homogeneous(math::by_rows(light_axes[0], light_axes[1], light_axes[2])); cg::frustum light_frustum{light_transform[cascade]}; impl().directional_shadow_framebuffer.bind(); gl::DrawBuffer(gl::NONE); impl().directional_shadow_framebuffer.depth(impl().directional_shadow_texture, cascade); impl().directional_shadow_framebuffer.assert_complete(); 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[cascade]; auto & program = impl().shadow_builder_program; for (int bi = 0; bi < bins_total; ++bi) { auto const & b = bins.data()[bi]; if (cg::separation(light_frustum, cg::box{b.bbox}).distance > 0.f) continue; for (auto const & p : b.buckets) { if (p.second.objects.empty()) continue; std::uint32_t mask = std::get<0>(p.first); if (!(mask & O_CASTS_SHADOW)) continue; program["u_flag_mask"] = mask; for (std::size_t i = 0; i < p.second.objects.size(); ++i) { std::size_t id = p.second.objects[i]; auto const & o = objects[id]; if (mask & O_PRE_TRANSFORM) program["u_pre_transform"] = *o.pre_transform; if (mask & O_POST_TRANSFORM) 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"] = math::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_cascades"] = static_cast(l.cascades); for (int i = 0; i < l.cascades; ++i) { impl().directional_light_pass_program[util::to_string("u_light_transform[", i, "]").data()] = light_transform[i]; } } 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 = math::solve_quadratic(l.attenuation.c2, l.attenuation.c1, l.attenuation.c0 - I / min_intensity); float near = l.min_shadow_distance; math::vector far_positive; math::vector 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) { math::matrix transform[6]; // +X { float far = far_positive[0]; transform[0] = math::matrix::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] = math::matrix::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] = math::matrix::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] = math::matrix::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] = math::matrix::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] = math::matrix::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; } math::matrix const translate_by_light = math::translation(math::point::zero() - l.position).homogeneous_matrix(); impl().point_shadow_framebuffer.bind(); if (impl().point_shadow_texture.width() != l.shadow_map_size) { for (int f = 0; f < 6; ++f) impl().point_shadow_texture.load(f, {l.shadow_map_size, l.shadow_map_size}); impl().point_shadow_framebuffer.depth(impl().point_shadow_texture); impl().point_shadow_framebuffer.assert_complete(); } 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"] = math::matrix::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; render_all(impl().cubemap_shadow_builder_program, [](auto mask){ return (mask & O_CASTS_SHADOW); }); } 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; } math::box 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"] = math::vector{l.attenuation.c0, l.attenuation.c1, l.attenuation.c2}; impl().screen_mesh.draw(); } // Overlay bloom if (opts.bloom) { gl::Enable(gl::BLEND); gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); impl().bloom_overlay.invoke(impl().bloom_texture[2], target); } impl().prev_transform = camera_transform; check_error(); } texture_2d const & deferred_renderer::depth() const { return impl().g_buffer_depth; } } #endif