From 9f12d27502d56e15f0659a6e96b43c4b9856fb90 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sat, 24 Oct 2020 14:37:26 +0300 Subject: [PATCH] Add basic deferred renderer & usage example --- examples/deferred.cpp | 429 ++++++++++++++ .../include/psemek/gfx/renderer/deferred.hpp | 100 ++++ libs/gfx/source/renderer/deferred.cpp | 531 ++++++++++++++++++ 3 files changed, 1060 insertions(+) create mode 100644 examples/deferred.cpp create mode 100644 libs/gfx/include/psemek/gfx/renderer/deferred.hpp create mode 100644 libs/gfx/source/renderer/deferred.cpp diff --git a/examples/deferred.cpp b/examples/deferred.cpp new file mode 100644 index 00000000..bc934f23 --- /dev/null +++ b/examples/deferred.cpp @@ -0,0 +1,429 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace psemek; + +struct deferred_app + : app::app +{ + deferred_app(); + + void on_resize(int width, int height) override; + + void on_mouse_move(int x, int y, int dx, int dy) override; + void on_mouse_wheel(int delta) override; + + void update() override; + void render() override; + + gfx::deferred_renderer renderer; + + geom::spherical_camera camera; + + gfx::mesh plane; + gfx::mesh cube; + gfx::mesh sphere; + gfx::mesh torus; + + gfx::texture_2d test_texture; + + util::clock> clock; + util::clock> frame_clock; + util::moving_average frame_time{32}; + + gfx::painter painter; +}; + +deferred_app::deferred_app() + : app("Deferred shading example", 0) +{ + vsync(false); + + camera.fov_y = geom::rad(45.f); + camera.near_clip = 0.1f; + camera.far_clip = 1000.f; + camera.target = {0.f, 0.f, 0.f}; + camera.distance = 20.f; + camera.azimuthal_angle = 0.f; + camera.elevation_angle = geom::rad(30.f); + + // 0 - vec3 position + // 1 - vec4 color (used if color & texture are not set) + // 2 - vec2 texcoord (used if texture is set) + // 3 - vec3 normal (used if lit = true) + + // For instanced mesh: + // 4 - mat3x4 per-instance transform (used in conjunction with transform) + + struct vertex + { + geom::point position; + gfx::color_rgba color; + geom::vector texcoord; + geom::vector normal; + + static auto attribs() + { + return gfx::make_attribs_description, decltype(texcoord), decltype(normal)>(); + } + }; + + struct instance + { + geom::matrix transform; + + static auto attribs() + { + return gfx::make_attribs_description>(); + } + }; + + { + gfx::color_rgba const color = gfx::white; + + std::vector vertices; + vertices.push_back({{-1.f, -1.f, 0.f}, color, {0.f, 0.f}, {0.f, 0.f, 1.f}}); + vertices.push_back({{ 1.f, -1.f, 0.f}, color, {0.f, 0.f}, {0.f, 0.f, 1.f}}); + vertices.push_back({{-1.f, 1.f, 0.f}, color, {0.f, 0.f}, {0.f, 0.f, 1.f}}); + vertices.push_back({{ 1.f, 1.f, 0.f}, color, {0.f, 0.f}, {0.f, 0.f, 1.f}}); + + std::vector> indices; + indices.push_back({0, 1, 2}); + indices.push_back({2, 1, 3}); + + plane.setup(vertex::attribs()); + plane.load(vertices, indices); + } + + { + auto box = geom::box{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}}; + + auto triangles = geom::deindex(geom::vertices(box), geom::faces(box)); + + std::vector> vertices; + + for (auto const & t : triangles) + { + auto & r = vertices.emplace_back(); + + auto n = geom::normal(t[0], t[1], t[2]); + + std::size_t tcm = 0; + if (std::abs(n[1]) > std::abs(n[tcm])) tcm = 1; + if (std::abs(n[2]) > std::abs(n[tcm])) tcm = 2; + + std::size_t tc0 = (tcm + 1) % 3; + std::size_t tc1 = (tcm + 2) % 3; + + auto c = gfx::color_rgba{255, 255, 255, 255}; + + for (std::size_t i = 0; i < 3; ++i) + { + r[i].position = t[i]; + r[i].color = c; + r[i].normal = n; + r[i].texcoord[0] = t[i][tc0] * 0.5f + 0.5f; + r[i].texcoord[1] = t[i][tc1] * 0.5f + 0.5f; + } + } + + cube.setup(vertex::attribs()); + cube.load(vertices, gl::STATIC_DRAW); + } + + { + std::vector vertices; + + geom::point const origin {0.f, 0.f, 0.f}; + + float const radius = 1.f; + + int const N = 24; + + auto c = gfx::color_rgba{255, 255, 255, 255}; + + for (int j = - N + 1; j < N; ++j) + { + for (int i = 0; i < 4 * N; ++i) + { + float a = (geom::pi * i) / (2 * N); + float b = (geom::pi * j) / (2 * N); + + geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)}; + + vertices.push_back({origin + radius * n, c, {0.f, 0.f}, n}); + } + } + + vertices.push_back({origin + geom::vector{0.f, 0.f, -radius}, c, {0.f, 0.f}, {0.f, 0.f, -1.f}}); + vertices.push_back({origin + geom::vector{0.f, 0.f, radius}, c, {0.f, 0.f}, {0.f, 0.f, 1.f}}); + + std::vector> indices; + + auto idx = [](int i, int j) -> std::uint32_t { return (i % (4 * N)) + 4 * N * (j + N - 1); }; + + for (int j = - N + 1; j + 1 < N; ++j) + { + for (int i = 0; i < 4 * N; ++i) + { + indices.push_back({idx(i, j), idx(i + 1, j), idx(i, j + 1)}); + indices.push_back({idx(i, j + 1), idx(i + 1, j), idx(i + 1, j + 1)}); + } + } + + for (int i = 0; i < 4 * N; ++i) + { + indices.push_back({idx(i, 1 - N), (2 * N - 1) * (4 * N), idx(i + 1, 1 - N)}); + } + + for (int i = 0; i < 4 * N; ++i) + { + indices.push_back({idx(i, N - 1), idx(i + 1, N - 1), (2 * N - 1) * (4 * N) + 1}); + } + + sphere.setup(vertex::attribs()); + sphere.load(vertices, indices, gl::STATIC_DRAW); + } + + { + std::vector vertices; + + geom::point const position = {0.f, 0.f, 0.f}; + + float const radius1 = 0.8f; + float const radius2 = 0.2f; + + int const N = 72; + int const M = 24; + + gfx::color_rgba color { 255, 255, 255, 255 }; + + for (int j = 0; j < M; ++j) + { + for (int i = 0; i < N; ++i) + { + float a = (2.f * geom::pi * i) / N; + float b = (2.f * geom::pi * j) / M; + + geom::vector r{std::cos(a), std::sin(a), 0.f}; + + geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)}; + + vertices.push_back({position + radius1 * r + radius2 * n, color, {0.f, 0.f}, n}); + } + } + + std::vector> indices; + + auto idx = [](int i, int j) -> std::uint32_t { return (i % N) + N * (j % M); }; + + for (int j = 0; j < M; ++j) + { + for (int i = 0; i < N; ++i) + { + indices.push_back({idx(i, j), idx(i + 1, j), idx(i, j + 1)}); + indices.push_back({idx(i, j + 1), idx(i + 1, j), idx(i + 1, j + 1)}); + } + } + + torus.setup(vertex::attribs()); + torus.load(vertices, indices, gl::STATIC_DRAW); + } + + { + gfx::pixmap_rgb pixmap({256, 256}, gfx::gray); + + int n = 8; + int const s = pixmap.width() / n; + int r = 10; + + for (auto p : pixmap.indices()) + { + int cx = p[0] % s; + int cy = p[1] % s; + + float tx = (cx - s / 2.f) / r; + float ty = (cy - s / 2.f) / r; + + float z = 0.f; + float d = tx * tx + ty * ty; + if (d <= 1.f) + { + z = std::sqrt(1.f - d); + } + (void)z; + + pixmap(p) = gfx::lerp(gfx::gray, gfx::red, z).as_color_rgb(); + } + + test_texture.load(pixmap); + test_texture.linear_filter(); + test_texture.generate_mipmap(); + } +} + +void deferred_app::on_resize(int width, int height) +{ + app::on_resize(width, height); + camera.set_fov(camera.fov_y, static_cast(width) / height); +} + +void deferred_app::on_mouse_move(int x, int y, int dx, int dy) +{ + app::on_mouse_move(x, y, dx, dy); + + if (is_middle_button_down()) + { + camera.azimuthal_angle -= dx * 0.01f; + camera.elevation_angle += dy * 0.01f; + } +} + +void deferred_app::on_mouse_wheel(int delta) +{ + camera.distance *= std::pow(0.8f, delta); +} + +void deferred_app::update() +{} + +void deferred_app::render() +{ + float const time = clock.count(); + + frame_time.push(frame_clock.restart().count()); + + std::vector objects; + + { + gfx::deferred_renderer::object obj; + obj.mesh = &plane; + obj.pre_transform = geom::scale(10.f).affine_matrix(); + obj.bbox = {{{-10.f, 10.f}, {-10.f, 10.f}, {0.f, 0.f}}}; + objects.push_back(obj); + } + + for (float x : {-3.f, 3.f}) + { + for (float y : {-3.f, 3.f}) + { + gfx::deferred_renderer::object obj; + obj.mesh = &cube; + obj.pre_transform = geom::translation{geom::vector{x, y, 3.f}}.affine_matrix(); + obj.bbox = {{{x - 1.f, x + 1.f}, {y - 1.f, y + 1.f}, {2.f, 4.f}}}; + objects.push_back(obj); + } + } + + for (float z : {2.f, 6.f}) + { + gfx::deferred_renderer::object obj; + obj.mesh = &sphere; + obj.pre_transform = geom::translation{geom::vector{0.f, 0.f, z}}.affine_matrix(); + obj.bbox = {{{-1.f, 1.f}, {-1.f, 1.f}, {z - 1.f, z + 1.f}}}; + obj.mat.specular.intensity = 4.f; + obj.mat.specular.shininess = 50.f; + objects.push_back(obj); + } + + { + gfx::deferred_renderer::object obj; + obj.mesh = &torus; + obj.pre_transform = geom::translation{geom::vector{0.f, 0.f, 4.f}}.affine_matrix(); + obj.bbox = {{{-1.f, 1.f}, {-1.f, 1.f}, {4 - 0.2f, 4 + 0.2f}}}; + obj.mat.specular.intensity = 4.f; + obj.mat.specular.shininess = 50.f; + objects.push_back(obj); + } + + gfx::deferred_renderer::options options; + options.framebuffer = &gfx::framebuffer::null(); + options.draw_buffer = gl::BACK; + options.viewport = {{{0, width()}, {0, height()}}}; + options.camera = &camera; + + options.clear_color = gfx::light(gfx::blue, 0.8f).as_color_4f(); + options.ambient = {1.f, 1.f, 1.f}; + + options.directional_lights.emplace_back(); + options.directional_lights[0].color = {5.f, 5.f, 5.f}; + options.directional_lights[0].direction = {1.f, 2.f, 3.f}; + + options.point_lights.emplace_back(); + options.point_lights[0].color = {15.f, 10.f, 10.f}; + options.point_lights[0].position = {2.f * std::cos(time / 2.f), 2.f * std::sin(time / 2.f), 2.f}; + options.point_lights[0].attenuation = {1.f, 0.1f, 0.05f}; + + options.point_lights.emplace_back(); + options.point_lights[1].color = {10.f, 10.f, 15.f}; + options.point_lights[1].position = {-6.f * std::cos(time / 2.f), -6.f * std::sin(time / 2.f), 2.f}; + options.point_lights[1].attenuation = {1.f, 0.1f, 0.05f}; + + options.point_lights.emplace_back(); + options.point_lights[2].color = {10.f, 15.f, 10.f}; + options.point_lights[2].position = {0.f, 0.f, 4.f}; + options.point_lights[2].attenuation = {1.f, 0.1f, 0.05f}; + + for (int i = 0; i < 24; ++i) + { + float a = (i * geom::pi) / 12.f; + + auto & l = options.point_lights.emplace_back(); + l.color = {15.f, 15.f, 15.f}; + l.position = {std::cos(a) * 10.f, std::sin(a) * 10.f, 0.5f}; + l.attenuation = {1.f, 0.5f, 1.f}; + } + + for (auto const & l : options.point_lights) + { + float const s = 0.1f; + gfx::deferred_renderer::object obj; + obj.mesh = &sphere; + obj.pre_transform = (geom::translation{l.position - geom::point::zero()}.transform() * geom::scale(s).transform()).affine_matrix(); + obj.bbox = {{{l.position[0] - s, l.position[0] + s}, {l.position[1] - s, l.position[1] + s}, {l.position[2] - s, l.position[2] + s}}}; + obj.mat.color = geom::vector{l.color[0], l.color[1], l.color[2], 1.f}; + obj.mat.lit = false; + obj.casts_shadow = false; + objects.push_back(obj); + } + + options.max_intensity = 15.f; + + renderer.render(objects, options); + + { + gfx::painter::text_options opts; + opts.scale = 2.f; + opts.f = gfx::painter::font::font_9x12; + opts.x = gfx::painter::x_align::left; + opts.y = gfx::painter::y_align::top; + opts.c = gfx::yellow.as_color_rgba(); + + painter.text({10.f, 10.f}, util::to_string("FPS: ", 1.f / frame_time.average()), opts); + } + + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); + painter.render(geom::window_camera{width(), height()}.transform()); +} + +int main() +{ + return app::main(); +} diff --git a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp new file mode 100644 index 00000000..e6f0892f --- /dev/null +++ b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include + +namespace psemek::gfx +{ + + struct deferred_renderer + { + deferred_renderer(); + ~deferred_renderer(); + + struct material + { + std::optional color; + texture_2d const * texture = nullptr; + bool transparent = false; + bool lit = true; + + float diffuse = 1.f; + struct + { + // specular highlight is calculated as + // intensity * dot(reflected, view)^shininess + float intensity = 0.f; + float shininess = 1.f; + } specular; + }; + + struct object + { + // Attribute specification: + // 0 - vec3 position + // 1 - vec4 color (used if color & texture are not set) + // 2 - vec2 texcoord (used if texture is set) + // 3 - vec3 normal (used if lit = true) + + // For instanced mesh: + // 4 - mat3x4 per-instance transform (used in conjunction with transform) + + gfx::mesh const * mesh = nullptr; + material mat; + bool casts_shadow = true; + geom::box bbox; + std::optional> pre_transform; + std::optional> post_transform; + }; + + struct directional_light + { + color_3f color; + geom::vector direction; + bool shadowed = true; + }; + + struct point_light + { + color_3f color; + // Intensity at distance d is computed as + // 1.0 / (c0 + d * c1 + d^2 * c2) + struct { + float c0, c1, c2; + } attenuation; + geom::point position; + bool shadowed = true; + }; + + struct options + { + gfx::framebuffer const * framebuffer; + GLenum draw_buffer; + geom::box viewport; + geom::camera const * camera; + + std::optional clear_color; + color_3f ambient; + std::vector directional_lights; + std::vector point_lights; + + // Used for HDR tone-mapping + float max_intensity; + // Equals max_intensity / 256 by default + std::optional min_intensity; + }; + + void render(std::vector const & objects, options const & opts); + + private: + psemek_declare_pimpl + }; + +} diff --git a/libs/gfx/source/renderer/deferred.cpp b/libs/gfx/source/renderer/deferred.cpp new file mode 100644 index 00000000..0b1d40b5 --- /dev/null +++ b/libs/gfx/source/renderer/deferred.cpp @@ -0,0 +1,531 @@ +#include + +#include +#include +#include +#include + +#include + +namespace psemek::gfx +{ + + 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; +)"; + + 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; +} +)"; + + 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 vec3 out2; +layout (location = 3) out vec3 out3; + +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 = normalize(normal) * 0.5 + vec3(0.5); + out3 = u_material; +} +)"; + + 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); +} +)"; + + char const ambient_pass_fs[] = +R"(#version 330 + +uniform sampler2D u_g0; +uniform sampler2D u_g1; +uniform sampler2D u_g2; +uniform sampler2D u_g3; + +uniform vec3 u_ambient; + +uniform float u_max_intensity; + +in vec2 texcoord; + +out vec4 out_color; + +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); +} +)"; + + char const directional_light_pass_fs[] = +R"(#version 330 + +uniform sampler2D u_g0; +uniform sampler2D u_g1; +uniform sampler2D u_g2; +uniform sampler2D u_g3; + +uniform vec3 u_light_direction; +uniform vec3 u_light_color; + +uniform vec3 u_camera_position; + +uniform float u_max_intensity; + +in vec2 texcoord; + +out vec4 out_color; + +void main() +{ + vec3 position = texture(u_g0, texcoord).xyz; + vec4 albedo = texture(u_g1, texcoord); + vec3 normal = texture(u_g2, texcoord).xyz * 2.0 - vec3(1.0); + 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 * albedo.a; + + out_color = vec4(color, 1.0); +} +)"; + + char const point_light_pass_fs[] = +R"(#version 330 + +uniform sampler2D u_g0; +uniform sampler2D u_g1; +uniform sampler2D u_g2; +uniform sampler2D u_g3; + +uniform vec3 u_light_position; +uniform vec3 u_light_color; +uniform vec3 u_light_attenuation; + +uniform vec3 u_camera_position; + +uniform float u_max_intensity; + +in vec2 texcoord; + +out vec4 out_color; + +void main() +{ + vec3 position = texture(u_g0, texcoord).xyz; + vec4 albedo = texture(u_g1, texcoord); + vec3 normal = texture(u_g2, texcoord).xyz * 2.0 - vec3(1.0); + vec3 material = texture(u_g3, texcoord).xyz; + + vec3 view = normalize(u_camera_position - position); + + vec3 light = u_light_position - position; + + float r = length(light); + light /= r; + + float d = dot(light, normal); + + vec3 refl = 2.0 * normal * d - light; + + 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 * albedo.a / (u_light_attenuation.x + r * (u_light_attenuation.y + r * u_light_attenuation.z)); + + out_color = vec4(color, 1.0); +} +)"; + + 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, ambient_pass_fs}; + gfx::program directional_light_pass_program{fullscreen_vs, directional_light_pass_fs}; + gfx::program point_light_pass_program{fullscreen_vs, point_light_pass_fs}; + + // G-buffer attachments: + // 0 - position (rbg) + // 1 - albedo (rgb), lit (a) + // 2 - normal (rgb) + // 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> g_buffer_size; + + gfx::array fullscreen_array; + }; + + 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().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().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; + } + + 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; + } + + void deferred_renderer::render(std::vector const & objects, 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 + + std::unordered_map> objects_by_mask; + + 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); + } + + // TODO: frustum culling + + // Resize g-buffer if needed + + auto buffer_size = geom::cast(opts.viewport.dimensions()); + if (!impl().g_buffer_size || *impl().g_buffer_size != buffer_size) + { + // TODO: compact normals storage + + impl().g_buffer_texture[0].load>(buffer_size); + 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().g_buffer_size = buffer_size; + } + + // Setup g-buffer + + impl().g_framebuffer.bind(); + + gl::Viewport(0, 0, opts.viewport[0].length(), opts.viewport[1].length()); + + GLenum draw_buffers[4] { gl::COLOR_ATTACHMENT0, gl::COLOR_ATTACHMENT1, gl::COLOR_ATTACHMENT2, gl::COLOR_ATTACHMENT3 }; + gl::DrawBuffers(4, draw_buffers); + check_error(); + 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{o.mat.diffuse, o.mat.specular.intensity, o.mat.specular.shininess}; + + o.mesh->draw(); + } + } + + // Setup destination framebuffer + + assert(opts.framebuffer); + opts.framebuffer->bind(); + + gl::DrawBuffer(opts.draw_buffer); + + gl::Viewport(opts.viewport[0].min, opts.viewport[1].min, opts.viewport[0].length(), opts.viewport[1].length()); + + 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().fullscreen_array.bind(); + + // TODO: directional light shadows + + // TODO: point light shadows + + // TODO: fill only affected areas for lights + + // 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); + (void)camera_position; + + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::ONE, gl::ONE); + + // Directional lights + + 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; + + for (auto const & l : opts.directional_lights) + { + impl().directional_light_pass_program["u_light_direction"] = geom::normalized(l.direction); + impl().directional_light_pass_program["u_light_color"] = l.color; + gl::DrawArrays(gl::TRIANGLES, 0, 6); + } + + // Point lights + + 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; + + for (auto const & l : opts.point_lights) + { + 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}; + gl::DrawArrays(gl::TRIANGLES, 0, 6); + } + + check_error(); + } + +}