Add basic deferred renderer & usage example
This commit is contained in:
parent
13595864fd
commit
9f12d27502
3 changed files with 1060 additions and 0 deletions
429
examples/deferred.cpp
Normal file
429
examples/deferred.cpp
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
|
||||
#include <psemek/gfx/renderer/deferred.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
|
||||
#include <psemek/geom/mesh.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/geom/translation.hpp>
|
||||
#include <psemek/geom/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::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<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()
|
||||
: 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<float, 3> position;
|
||||
gfx::color_rgba color;
|
||||
geom::vector<float, 2> texcoord;
|
||||
geom::vector<float, 3> normal;
|
||||
|
||||
static auto attribs()
|
||||
{
|
||||
return gfx::make_attribs_description<decltype(position), gfx::normalized<decltype(color)>, decltype(texcoord), decltype(normal)>();
|
||||
}
|
||||
};
|
||||
|
||||
struct instance
|
||||
{
|
||||
geom::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<geom::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 = geom::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
|
||||
|
||||
auto triangles = geom::deindex(geom::vertices(box), geom::faces(box));
|
||||
|
||||
std::vector<geom::triangle<vertex>> 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<vertex> vertices;
|
||||
|
||||
geom::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 = (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<geom::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;
|
||||
|
||||
geom::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 * 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<geom::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_resize(int width, int height)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
camera.set_fov(camera.fov_y, static_cast<float>(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<gfx::deferred_renderer::object> objects;
|
||||
|
||||
{
|
||||
gfx::deferred_renderer::object obj;
|
||||
obj.mesh = &plane;
|
||||
obj.pre_transform = geom::scale<float, 3>(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<float, 3>{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<float, 3>{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<float, 3>{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<float, 3>{l.position - geom::point<float, 3>::zero()}.transform() * geom::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}}};
|
||||
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<deferred_app>();
|
||||
}
|
||||
100
libs/gfx/include/psemek/gfx/renderer/deferred.hpp
Normal file
100
libs/gfx/include/psemek/gfx/renderer/deferred.hpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/gfx/color.hpp>
|
||||
#include <psemek/gfx/mesh.hpp>
|
||||
#include <psemek/gfx/texture.hpp>
|
||||
#include <psemek/gfx/framebuffer.hpp>
|
||||
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/box.hpp>
|
||||
|
||||
#include <psemek/util/pimpl.hpp>
|
||||
|
||||
namespace psemek::gfx
|
||||
{
|
||||
|
||||
struct deferred_renderer
|
||||
{
|
||||
deferred_renderer();
|
||||
~deferred_renderer();
|
||||
|
||||
struct material
|
||||
{
|
||||
std::optional<color_4f> 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<float, 3> bbox;
|
||||
std::optional<geom::matrix<float, 3, 4>> pre_transform;
|
||||
std::optional<geom::matrix<float, 3, 4>> post_transform;
|
||||
};
|
||||
|
||||
struct directional_light
|
||||
{
|
||||
color_3f color;
|
||||
geom::vector<float, 3> 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<float, 3> position;
|
||||
bool shadowed = true;
|
||||
};
|
||||
|
||||
struct options
|
||||
{
|
||||
gfx::framebuffer const * framebuffer;
|
||||
GLenum draw_buffer;
|
||||
geom::box<int, 2> viewport;
|
||||
geom::camera const * camera;
|
||||
|
||||
std::optional<color_4f> clear_color;
|
||||
color_3f ambient;
|
||||
std::vector<directional_light> directional_lights;
|
||||
std::vector<point_light> point_lights;
|
||||
|
||||
// Used for HDR tone-mapping
|
||||
float max_intensity;
|
||||
// Equals max_intensity / 256 by default
|
||||
std::optional<float> min_intensity;
|
||||
};
|
||||
|
||||
void render(std::vector<object> const & objects, options const & opts);
|
||||
|
||||
private:
|
||||
psemek_declare_pimpl
|
||||
};
|
||||
|
||||
}
|
||||
531
libs/gfx/source/renderer/deferred.cpp
Normal file
531
libs/gfx/source/renderer/deferred.cpp
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
#include <psemek/gfx/renderer/deferred.hpp>
|
||||
|
||||
#include <psemek/gfx/program.hpp>
|
||||
#include <psemek/gfx/framebuffer.hpp>
|
||||
#include <psemek/gfx/texture.hpp>
|
||||
#include <psemek/gfx/error.hpp>
|
||||
|
||||
#include <psemek/util/unused.hpp>
|
||||
|
||||
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<geom::vector<std::size_t, 2>> g_buffer_size;
|
||||
|
||||
gfx::array fullscreen_array;
|
||||
};
|
||||
|
||||
deferred_renderer::deferred_renderer()
|
||||
: pimpl_{std::make_unique<struct impl>()}
|
||||
{
|
||||
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<object> 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<std::uint32_t, std::vector<std::size_t>> 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<std::size_t>(opts.viewport.dimensions());
|
||||
if (!impl().g_buffer_size || *impl().g_buffer_size != buffer_size)
|
||||
{
|
||||
// TODO: compact normals storage
|
||||
|
||||
impl().g_buffer_texture[0].load<geom::vector<gfx::float16, 3>>(buffer_size);
|
||||
impl().g_buffer_texture[1].load<geom::vector<std::uint16_t, 4>>(buffer_size);
|
||||
impl().g_buffer_texture[2].load<geom::vector<gfx::float16, 3>>(buffer_size);
|
||||
impl().g_buffer_texture[3].load<geom::vector<gfx::float16, 3>>(buffer_size);
|
||||
impl().g_buffer_depth.load<gfx::depth24_pixel>(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<float, 3>{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();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue