#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; 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 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.5) 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; 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 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[16]; 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 < 16; ++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 / 16.0; } out_color = vec4(1.0 - occlusion); } )"; static std::size_t bbox_to_screen_fan(geom::matrix const & camera_transform, geom::box const & b, geom::point * result) { geom::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) { geom::point p; p[0] = geom::lerp(b[0], x); p[1] = geom::lerp(b[1], y); p[2] = geom::lerp(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 * 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 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::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 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{2, 2.f}; vblur ssao_vblur{2, 2.f}; gfx::mesh screen_mesh; }; static int const shadow_map_size = 1024; deferred_renderer::deferred_renderer() : pimpl_{std::make_unique()} { 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().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.load({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(f, {shadow_map_size, shadow_map_size}); } impl().point_shadow_framebuffer.depth(impl().point_shadow_texture); impl().point_shadow_framebuffer.assert_complete(); 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{geom::vector{0.f, 0.f, 1.f}}; std::vector> ssao_kernel(16); 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; 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; return m; } static std::optional test_mesh; void deferred_renderer::render(std::vector 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> objects_by_mask; geom::box lit_bbox; geom::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 std::runtime_error("Materials that are both tit & transparent are not supported"); if (o.mat->lit && o.mat->blooming) throw std::runtime_error("Materials that are both tit & blooming are not supported"); if (o.mat->casts_shadow && o.mat->transparent) throw std::runtime_error("Transparent objects cannot cast shadow"); objects_by_mask[mask(objects[i])].push_back(i); if (o.mat->lit) lit_bbox |= o.bbox; if (o.mat->casts_shadow) casts_shadow_bbox |= o.bbox; } // Resize g-buffer if needed auto const buffer_size = geom::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); } 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().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); } } // 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) { std::uint32_t mask = p.first; if (mask & O_TRANSPARENT) continue; if (p.second.empty()) continue; 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{o.mat->diffuse, o.mat->specular.intensity, o.mat->specular.shininess}; o.mesh->draw(); } } // 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); for (auto const & p : objects_by_mask) { std::uint32_t mask = p.first; if (!(mask & O_TRANSPARENT)) continue; if (p.second.empty()) continue; impl().transparent_pass_program["u_flag_mask"] = mask; for (std::size_t i : p.second) { auto const & o = objects[i]; if (mask & O_UNIFORM_COLOR) impl().transparent_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().transparent_pass_program["u_pre_transform"] = *o.pre_transform; if (mask & O_POST_TRANSFORM) impl().transparent_pass_program["u_post_transform"] = *o.post_transform; o.mesh->draw(); } } // 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; for (auto const & p : objects_by_mask) { std::uint32_t mask = p.first; if (!(mask & O_BLOOMING)) continue; if (p.second.empty()) continue; impl().bloom_pass_program["u_flag_mask"] = mask; for (std::size_t i : p.second) { auto const & o = objects[i]; if (mask & O_UNIFORM_COLOR) impl().bloom_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().bloom_pass_program["u_pre_transform"] = *o.pre_transform; if (mask & O_POST_TRANSFORM) impl().bloom_pass_program["u_post_transform"] = *o.post_transform; impl().bloom_pass_program["u_material"] = geom::vector{o.mat->diffuse, o.mat->specular.intensity, o.mat->specular.shininess}; o.mesh->draw(); } } } // 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); for (auto const & p : objects_by_mask) { std::uint32_t mask = p.first; if (!(mask & O_TRANSPARENT)) continue; if (p.second.empty()) continue; impl().transparent_pass_program["u_flag_mask"] = mask; for (std::size_t i : p.second) { auto const & o = objects[i]; if (mask & O_UNIFORM_COLOR) impl().transparent_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().transparent_pass_program["u_pre_transform"] = *o.pre_transform; if (mask & O_POST_TRANSFORM) impl().transparent_pass_program["u_post_transform"] = *o.post_transform; o.mesh->draw(); } } } 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"] = geom::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(); 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 geom::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) { geom::matrix light_transform; if (l.shadowed) { geom::vector 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 light_bbox; geom::point origin = geom::point::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 far_positive; geom::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) { geom::matrix transform[6]; // +X { float far = far_positive[0]; transform[0] = geom::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] = geom::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] = geom::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] = geom::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] = geom::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] = geom::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; } geom::matrix const translate_by_light = geom::translation(geom::point::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::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(); } } } 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 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(); } // 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); } check_error(); } }