Implement SSAO in deferred renderer

This commit is contained in:
Nikita Lisitsa 2020-12-10 10:41:40 +03:00
parent 077a95e78b
commit c8c5e874da
3 changed files with 354 additions and 134 deletions

View file

@ -6,7 +6,7 @@ file(GLOB_RECURSE PSEMEK_GFX_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "sou
add_library(psemek-gfx ${PSEMEK_GFX_HEADERS} ${PSEMEK_GFX_SOURCES})
target_include_directories(psemek-gfx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-gfx PUBLIC psemek-util psemek-geom psemek-cg OpenGL::GL)
target_link_libraries(psemek-gfx PUBLIC psemek-util psemek-geom psemek-cg psemek-random OpenGL::GL)
psemek_add_resources(psemek-gfx
resources/font_9x12.pbm psemek/gfx/resource/font_9x12

View file

@ -108,6 +108,14 @@ namespace psemek::gfx
};
std::optional<bloom_data> bloom;
struct ssao_data
{
float radius;
std::size_t downsample = 2;
};
std::optional<ssao_data> ssao;
};
void render(std::vector<object> const & objects, render_target const & target, options const & opts);

View file

@ -8,6 +8,10 @@
#include <psemek/gfx/effect/blur.hpp>
#include <psemek/gfx/effect/overlay.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform_hemiball.hpp>
#include <psemek/random/uniform_sphere.hpp>
#include <psemek/geom/homogeneous.hpp>
#include <psemek/geom/gram_schmidt.hpp>
#include <psemek/geom/translation.hpp>
@ -15,6 +19,8 @@
#include <psemek/cg/convex_hull_2d/graham.hpp>
#include <psemek/util/to_string.hpp>
#include <map>
namespace psemek::gfx
@ -337,6 +343,8 @@ vec3 unpack_normal(uint v)
R"(
uniform vec3 u_ambient;
uniform sampler2D u_ssao_texture;
uniform int u_use_ssao;
uniform float u_max_intensity;
@ -348,6 +356,8 @@ void main()
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;
@ -582,6 +592,58 @@ void main()
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<float, 4, 4> const & camera_transform, geom::box<float, 3> const & b, geom::point<float, 2> * result)
@ -645,6 +707,7 @@ void main()
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)
@ -680,6 +743,20 @@ void main()
gfx::framebuffer bloom_framebuffer[3];
gfx::texture_2d bloom_texture[3];
gfx::texture_2d ssao_rotation_texture;
std::optional<geom::vector<std::size_t, 2>> ssao_size;
std::optional<std::size_t> 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;
};
@ -701,6 +778,7 @@ void main()
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;
@ -735,12 +813,57 @@ void main()
impl().point_shadow_framebuffer.depth(impl().point_shadow_texture);
impl().point_shadow_framebuffer.assert_complete();
impl().bloom_texture[0].linear_filter();
impl().bloom_texture[1].linear_filter();
impl().bloom_texture[2].linear_filter();
impl().bloom_texture[0].clamp();
impl().bloom_texture[1].clamp();
impl().bloom_texture[2].clamp();
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<float, 3> d{geom::vector{0.f, 0.f, 1.f}};
std::vector<geom::vector<float, 3>> 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<float, 2> d;
basic_pixmap<geom::vector<float, 2>> 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<geom::point<float, 2>>();
}
@ -873,6 +996,20 @@ void main()
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<std::uint8_t>(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<std::pair<std::size_t, float>> hblur_params;
std::optional<std::pair<std::size_t, float>> vblur_params;
@ -1014,6 +1151,8 @@ void main()
// Render bloom
if (opts.bloom)
{
impl().bloom_framebuffer[0].bind();
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
@ -1063,9 +1202,12 @@ void main()
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;
@ -1109,9 +1251,11 @@ void main()
gl::DepthMask(gl::TRUE);
gl::Disable(gl::BLEND);
}
// Apply horizontal blur to bloom
if (opts.bloom)
{
render_target target;
target.framebuffer = &impl().bloom_framebuffer[1];
@ -1127,6 +1271,7 @@ void main()
// Apply vertical blur to bloom
if (opts.bloom)
{
render_target target;
target.framebuffer = &impl().bloom_framebuffer[2];
@ -1140,12 +1285,7 @@ void main()
impl().vblur_container.at(*vblur_params).invoke(impl().bloom_texture[1], target);
}
// Setup destination framebuffer
target.bind();
gl::Disable(gl::DEPTH_TEST);
gl::Disable(gl::BLEND);
// Bind g-buffer textures
gl::ActiveTexture(gl::TEXTURE0);
impl().g_buffer_texture[0].bind();
@ -1156,6 +1296,68 @@ void main()
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<float>(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
@ -1163,6 +1365,16 @@ void main()
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