509 lines
14 KiB
C++
509 lines
14 KiB
C++
#include <psemek/app/application_base.hpp>
|
|
#include <psemek/app/default_application_factory.hpp>
|
|
|
|
#include <psemek/gfx/renderer/deferred.hpp>
|
|
#include <psemek/gfx/effect/gamma.hpp>
|
|
#include <psemek/gfx/effect/fxaa.hpp>
|
|
#include <psemek/gfx/painter.hpp>
|
|
|
|
#include <psemek/math/mesh.hpp>
|
|
#include <psemek/math/camera.hpp>
|
|
#include <psemek/math/math.hpp>
|
|
#include <psemek/math/rotation.hpp>
|
|
#include <psemek/math/translation.hpp>
|
|
#include <psemek/math/scale.hpp>
|
|
|
|
#include <psemek/util/clock.hpp>
|
|
#include <psemek/util/moving_average.hpp>
|
|
#include <psemek/util/to_string.hpp>
|
|
|
|
#include <fstream>
|
|
|
|
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<std::chrono::duration<float>> clock;
|
|
util::clock<std::chrono::duration<float>> frame_clock;
|
|
util::moving_average<float> 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<float, 3> position;
|
|
gfx::color_rgba color;
|
|
math::vector<float, 2> texcoord;
|
|
math::vector<float, 3> normal;
|
|
|
|
static auto attribs()
|
|
{
|
|
return gfx::make_attribs_description<decltype(position), gfx::normalized<decltype(color)>, decltype(texcoord), decltype(normal)>();
|
|
}
|
|
};
|
|
|
|
struct instance
|
|
{
|
|
math::matrix<float, 3, 4> transform;
|
|
|
|
static auto attribs()
|
|
{
|
|
return gfx::make_attribs_description<gfx::instanced<decltype(transform)>>();
|
|
}
|
|
};
|
|
|
|
{
|
|
gfx::color_rgba const color = gfx::white;
|
|
|
|
std::vector<vertex> 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<math::triangle<std::uint32_t>> 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<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
|
|
|
|
auto triangles = math::deindex(math::vertices(box), math::faces(box));
|
|
|
|
std::vector<math::triangle<vertex>> 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<vertex> vertices;
|
|
|
|
math::point<float, 3> 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<math::triangle<std::uint32_t>> 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<vertex> vertices;
|
|
|
|
math::point<float, 3> 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<math::triangle<std::uint32_t>> 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<float>(event.size[0]) / event.size[1]);
|
|
|
|
pre_gamma_texture.load<math::vector<std::uint16_t, 3>>(math::cast<std::size_t>(event.size));
|
|
pre_gamma_framebuffer.color(pre_gamma_texture);
|
|
pre_gamma_framebuffer.assert_complete();
|
|
|
|
pre_fxaa_texture.load<gfx::color_rgb>(math::cast<std::size_t>(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<gfx::deferred_renderer::object> 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<float, 3>(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<float, 3>{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<float, 3>{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<float, 3>{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<gfx::deferred_renderer::material> 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<float, 3>{l.position - math::point<float, 3>::zero()}.transform() * math::scale<float, 3>(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<application::factory> make_application_factory()
|
|
{
|
|
return default_application_factory<deferred_app>({.name = "Deferred shading example", .multisampling = 0});
|
|
}
|
|
|
|
}
|