#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace psemek; struct deferred_app : app::application_base { deferred_app(options const & options, context const & context); void on_event(app::resize_event const & event) override; void on_event(app::mouse_move_event const & event) override; void on_event(app::mouse_wheel_event const & event) override; void update() override; void present() override; gfx::deferred_renderer renderer; gfx::framebuffer pre_gamma_framebuffer; gfx::texture_2d pre_gamma_texture; gfx::gamma_correction gamma_correction; gfx::framebuffer pre_fxaa_framebuffer; gfx::texture_2d pre_fxaa_texture; gfx::fxaa fxaa; math::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(options const &, context const & context) { context.vsync(false); camera.fov_y = math::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 = math::rad(30.f); pre_gamma_texture.linear_filter(); pre_fxaa_texture.linear_filter(); // 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 { math::point position; gfx::color_rgba color; math::vector texcoord; math::vector normal; static auto attribs() { return gfx::make_attribs_description, decltype(texcoord), decltype(normal)>(); } }; struct instance { math::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 = math::box{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}}; auto triangles = math::deindex(math::vertices(box), math::faces(box)); std::vector> vertices; for (auto const & t : triangles) { auto & r = vertices.emplace_back(); auto n = math::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; math::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 = (math::pi * i) / (2 * N); float b = (math::pi * j) / (2 * N); math::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 + math::vector{0.f, 0.f, -radius}, c, {0.f, 0.f}, {0.f, 0.f, -1.f}}); vertices.push_back({origin + math::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; math::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 * math::pi * i) / N; float b = (2.f * math::pi * j) / M; math::vector r{std::cos(a), std::sin(a), 0.f}; math::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_event(app::resize_event const & event) { app::application_base::on_event(event); camera.set_fov(camera.fov_y, static_cast(event.size[0]) / event.size[1]); pre_gamma_texture.load>(math::cast(event.size)); pre_gamma_framebuffer.color(pre_gamma_texture); pre_gamma_framebuffer.assert_complete(); pre_fxaa_texture.load(math::cast(event.size)); pre_fxaa_framebuffer.color(pre_fxaa_texture); pre_fxaa_framebuffer.assert_complete(); } void deferred_app::on_event(app::mouse_move_event const & event) { auto const old_mouse = state().mouse; app::application_base::on_event(event); if (state().mouse_button_down.contains(app::mouse_button::middle)) { auto delta = event.position - old_mouse; camera.azimuthal_angle -= delta[0] * 0.01f; camera.elevation_angle += delta[1] * 0.01f; } } void deferred_app::on_event(app::mouse_wheel_event const & event) { app::application_base::on_event(event); camera.distance *= std::pow(0.8f, event.delta); } void deferred_app::update() {} void deferred_app::present() { float const time = clock.count(); frame_time.push(frame_clock.restart().count()); std::vector objects; gfx::deferred_renderer::material plane_material; plane_material.color = gfx::color_4f{1.f, 1.f, 1.f, 1.f}; plane_material.casts_shadow = false; { gfx::deferred_renderer::object obj; obj.mesh = &plane; obj.pre_transform = math::scale(10.f).affine_matrix(); obj.bbox = {{{-10.f, 10.f}, {-10.f, 10.f}, {0.f, 0.f}}}; obj.mat = &plane_material; objects.push_back(obj); } gfx::deferred_renderer::material cube_material; cube_material.color = gfx::color_4f{1.f, 1.f, 1.f, 1.f}; 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 = math::translation{math::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}}}; obj.mat = &cube_material; objects.push_back(obj); } } gfx::deferred_renderer::material sphere_material; sphere_material.color = gfx::color_4f{1.f, 1.f, 1.f, 1.f}; sphere_material.specular.intensity = 4.f; sphere_material.specular.shininess = 50.f; for (float z : {2.f, 6.f}) { gfx::deferred_renderer::object obj; obj.mesh = &sphere; obj.pre_transform = math::translation{math::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 = &sphere_material; objects.push_back(obj); } { gfx::deferred_renderer::object obj; obj.mesh = &torus; obj.pre_transform = math::translation{math::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 = &sphere_material; objects.push_back(obj); } gfx::deferred_renderer::options options; options.camera = &camera; options.clear_color = math::vector{0.f, 0.f, 0.1f}; options.ambient = {1.f, 1.f, 1.f}; options.directional_lights.emplace_back(); options.directional_lights[0].color = {1.f, 1.f, 1.f}; options.directional_lights[0].direction = {1.f, 2.f, 3.f}; options.point_lights.emplace_back(); options.point_lights[0].color = {20.f, 0.f, 0.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[0].min_shadow_distance = 0.1f; options.point_lights.emplace_back(); options.point_lights[1].color = {0.f, 0.f, 20.f}; options.point_lights[1].position = {-6.f * std::cos(time / 1.f), -6.f * std::sin(time / 1.f), 2.f}; options.point_lights[1].attenuation = {1.f, 0.1f, 0.05f}; options.point_lights[1].min_shadow_distance = 0.1f; options.point_lights.emplace_back(); options.point_lights[2].color = {0.f, 20.f, 0.f}; options.point_lights[2].position = {0.f, 0.f, 4.f}; options.point_lights[2].attenuation = {1.f, 0.1f, 0.05f}; options.point_lights[2].min_shadow_distance = 0.1f; for (int i = 0; i < 24; ++i) { float a = (i * math::pi) / 12.f; auto & l = options.point_lights.emplace_back(); l.color = {15.f, 15.f, 15.f}; l.position = {std::cos(a) * 9.f, std::sin(a) * 9.f, 0.5f}; l.attenuation = {0.f, 0.f, 10.f}; l.shadowed = false; l.min_shadow_distance = 0.1f; } std::vector light_materials(options.point_lights.size()); for (std::size_t i = 0; i < options.point_lights.size(); ++i) { auto const & l = options.point_lights[i]; float const s = 0.1f; gfx::deferred_renderer::object obj; obj.mesh = &sphere; obj.pre_transform = (math::translation{l.position - math::point::zero()}.transform() * math::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}}}; light_materials[i].color = math::vector{l.color[0], l.color[1], l.color[2], 1.f}; light_materials[i].lit = false; light_materials[i].casts_shadow = false; obj.mat = &light_materials[i]; objects.push_back(obj); } float const gamma = 2.2f; options.max_intensity = 20.f; options.min_intensity = options.max_intensity / std::pow(8.f, gamma); gfx::framebuffer::null().bind(); gl::DrawBuffer(gl::BACK); gl::ClearColor(0.f, 0.f, 0.0f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT); { gfx::render_target target; target.framebuffer = &pre_gamma_framebuffer; target.draw_buffer = gl::COLOR_ATTACHMENT0; target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}}; renderer.render(objects, target, options); } { gfx::render_target target; target.framebuffer = &pre_fxaa_framebuffer; target.draw_buffer = gl::COLOR_ATTACHMENT0; target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}}; gamma_correction.invoke(pre_gamma_texture, target, {1.f / gamma}); } { gfx::render_target target; target.framebuffer = &gfx::framebuffer::null(); target.draw_buffer = gl::BACK_LEFT; target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}}; fxaa.invoke(pre_fxaa_texture, target); } { 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: ", std::round(1.f / frame_time.average())), opts); } gl::Enable(gl::BLEND); gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); painter.render(math::window_camera{state().size[0], state().size[1]}.transform()); } namespace psemek::app { std::unique_ptr make_application_factory() { return default_application_factory({.name = "Deferred shading example", .multisampling = 0}); } }