Add shadow mapping example

This commit is contained in:
Nikita Lisitsa 2020-10-16 07:46:54 +03:00
parent 77125540e6
commit 10213bb3d9

777
examples/shadow.cpp Normal file
View file

@ -0,0 +1,777 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/color.hpp>
#include <psemek/gfx/texture.hpp>
#include <psemek/gfx/renderbuffer.hpp>
#include <psemek/gfx/framebuffer.hpp>
#include <psemek/gfx/error.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/mesh.hpp>
#include <psemek/geom/homogeneous.hpp>
#include <psemek/geom/gram_schmidt.hpp>
#include <psemek/util/clock.hpp>
#include <fstream>
using namespace psemek;
static char const phong_vertex_source[] =
R"(#version 330
uniform mat4 u_transform;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec3 in_normal;
layout (location = 2) in vec4 in_color;
out vec3 position;
out vec4 color;
out vec3 normal;
void main()
{
position = in_position.xyz;
gl_Position = u_transform * in_position;
color = in_color;
normal = in_normal;
}
)";
static char const phong_fragment_source[] =
R"(#version 330
uniform vec4 u_light_position;
uniform vec4 u_camera_position;
uniform vec4 u_material;
in vec3 position;
in vec4 color;
in vec3 normal;
out vec4 out_color;
void main()
{
vec3 n = normalize(normal);
vec3 r = u_light_position.xyz - u_light_position.w * position;
r = normalize(r);
vec3 v = u_camera_position.xyz - u_camera_position.w * position;
v = normalize(v);
vec3 d = 2.0 * dot(r, n) * n - r;
vec3 light_color = vec3(1.0, 1.0, 1.0);
vec3 specular_color = vec3(1.0, 1.0, 1.0);
vec3 col = u_material.x * color.rgb + u_material.y * max(0.0, dot(n, r)) * color.rgb * light_color + u_material.z * (pow(max(0.0, dot(d, v)), u_material.w)) * specular_color;
out_color = vec4(col, color.a);
}
)";
struct phong_renderer
{
struct material
{
float ambient;
float diffuse;
float specular;
float shininess;
};
struct render_state
{
struct material material;
gfx::mesh const * mesh;
};
struct light
{
geom::vector<float, 4> position;
};
struct render_options
{
gfx::framebuffer const * framebuffer;
GLenum draw_buffer;
geom::box<int, 2> viewport;
geom::matrix<float, 4, 4> transform;
geom::point<float, 3> camera_position;
struct light light;
};
void push(render_state const & state);
void render(render_options const & options);
private:
std::vector<render_state> render_states_;
gfx::program program_{phong_vertex_source, phong_fragment_source};
};
void phong_renderer::push(render_state const & state)
{
render_states_.push_back(state);
}
void phong_renderer::render(render_options const & options)
{
if (!options.framebuffer) return;
options.framebuffer->bind();
gl::DrawBuffer(options.draw_buffer);
gl::Viewport(options.viewport[0].min, options.viewport[1].min, options.viewport[0].length(), options.viewport[1].length());
program_.bind();
program_["u_transform"] = options.transform;
program_["u_light_position"] = options.light.position;
program_["u_camera_position"] = geom::homogeneous(options.camera_position);
for (auto const & state : render_states_)
{
if (!state.mesh) continue;
program_["u_material"] = geom::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
state.mesh->draw();
}
render_states_.clear();
}
static char const shadow_builder_vertex_source[] =
R"(#version 330
uniform mat4 u_transform;
layout (location = 0) in vec4 in_position;
void main()
{
gl_Position = u_transform * in_position;
}
)";
static char const shadow_builder_fragment_source[] =
R"(#version 330
layout (location = 0) out float depth;
void main()
{
depth = gl_FragCoord.z;
}
)";
struct shadow_map_builder
{
struct render_state
{
gfx::mesh const * mesh;
geom::box<float, 3> bbox;
};
struct render_options
{
geom::vector<float, 3> light;
};
shadow_map_builder(std::size_t width, std::size_t height);
void push(render_state const & state);
void build(render_options const & options);
geom::matrix<float, 4, 4> const & transform() const;
gfx::texture_2d const & texture() const;
private:
std::vector<render_state> render_states_;
gfx::program program_{shadow_builder_vertex_source, shadow_builder_fragment_source};
gfx::framebuffer framebuffer_;
gfx::texture_2d depth_texture_;
geom::matrix<float, 4, 4> transform_;
};
shadow_map_builder::shadow_map_builder(std::size_t width, std::size_t height)
{
depth_texture_.load<gfx::depth24_pixel>({width, height});
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL);
framebuffer_.depth(depth_texture_);
framebuffer_.assert_complete();
}
void shadow_map_builder::push(render_state const & state)
{
render_states_.push_back(state);
}
void shadow_map_builder::build(render_options const & options)
{
geom::vector<float, 3> light_axes[3];
light_axes[2] = -options.light;
light_axes[1] = {0.f, 0.f, 1.f};
if (light_axes[2][2] > 0.5f)
light_axes[1] = {0.f, 1.f, 0.f};
geom::gram_schmidt(light_axes[2], light_axes[1]);
light_axes[0] = geom::cross(light_axes[2], light_axes[1]);
geom::box<float, 3> light_bbox;
geom::point<float, 3> origin = geom::point<float, 3>::zero();
for (auto const & state : render_states_)
{
for (auto const & v : geom::vertices(state.bbox))
{
for (std::size_t i = 0; i < 3; ++i)
light_bbox[i] |= geom::dot(light_axes[i], v - origin);
}
}
transform_ = geom::orthographic_camera{light_bbox}.projection() * geom::homogeneous(geom::by_rows(light_axes[0], light_axes[1], light_axes[2]));
framebuffer_.bind();
gl::DrawBuffer(gl::NONE);
gl::Viewport(0, 0, depth_texture_.width(), depth_texture_.height());
gl::Clear(gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Enable(gl::CULL_FACE);
gl::CullFace(gl::FRONT);
program_.bind();
program_["u_transform"] = transform_;
gfx::check_error();
for (auto const & state: render_states_)
{
state.mesh->draw();
gfx::check_error();
}
render_states_.clear();
/*
framebuffer_.null().bind();
gfx::pixmap_float pixmap({depth_texture_.width(), depth_texture_.height()});
depth_texture_.pixels(gl::DEPTH_COMPONENT, gl::FLOAT, pixmap.data());
auto pixels = util::map([](float x){ return std::uint8_t(x * 255); }, pixmap);
std::ofstream out{"depth.pgm"};
gfx::write_pgm(pixels, out);
*/
}
geom::matrix<float, 4, 4> const & shadow_map_builder::transform() const
{
return transform_;
}
gfx::texture_2d const & shadow_map_builder::texture() const
{
return depth_texture_;
}
static char const shadow_vertex_source[] =
R"(#version 330
uniform mat4 u_transform;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec3 in_normal;
layout (location = 2) in vec4 in_color;
out vec3 position;
out vec4 color;
out vec3 normal;
void main()
{
position = in_position.xyz;
gl_Position = u_transform * in_position;
color = in_color;
normal = in_normal;
}
)";
static char const shadow_fragment_source[] =
R"(#version 330
uniform vec4 u_light_position;
uniform vec4 u_camera_position;
uniform mat4 u_shadow_transform;
uniform sampler2DShadow u_shadow_map;
uniform vec4 u_material;
in vec3 position;
in vec4 color;
in vec3 normal;
out vec4 out_color;
void main()
{
vec3 n = normalize(normal);
vec3 r = u_light_position.xyz - u_light_position.w * position;
r = normalize(r);
vec3 v = u_camera_position.xyz - u_camera_position.w * position;
v = normalize(v);
vec3 d = 2.0 * dot(r, n) * n - r;
vec3 light_color = vec3(1.0, 1.0, 1.0);
vec3 specular_color = vec3(1.0, 1.0, 1.0);
float light_dot = dot(n, r);
vec3 ambient = u_material.x * color.rgb;
vec3 diffuse = u_material.y * max(0.0, light_dot) * color.rgb * light_color;
vec3 specular = u_material.z * (pow(max(0.0, dot(d, v)), u_material.w)) * specular_color;
vec3 result = ambient;
vec4 shadow_space = u_shadow_transform * vec4(position, 1.0);
vec3 tc = shadow_space.xyz / shadow_space.w;
tc = tc * 0.5 + vec3(0.5);
float fragment_depth = tc.z;
float shadow_value = 1.0;
if (tc.x >= 0.0 && tc.x <= 1.0 && tc.y >= 0.0 && tc.y <= 1.0 && tc.z >= 0.0)
{
if (light_dot > 0.0)
{
shadow_value = texture(u_shadow_map, tc);
}
}
result += shadow_value * (diffuse + specular);
out_color = vec4(result, color.a);
}
)";
struct shadow_renderer
{
struct material
{
float ambient;
float diffuse;
float specular;
float shininess;
};
struct render_state
{
struct material material;
gfx::mesh const * mesh;
bool casts_shadow;
std::optional<geom::box<float, 3>> bbox;
};
struct light
{
geom::vector<float, 4> position;
};
struct render_options
{
gfx::framebuffer const * framebuffer;
GLenum draw_buffer;
geom::box<int, 2> viewport;
geom::matrix<float, 4, 4> transform;
geom::point<float, 3> camera_position;
struct light light;
};
void push(render_state const & state);
void render(render_options const & options);
gfx::texture_2d const & depth_texture() const { return builder_.texture(); }
private:
std::vector<render_state> render_states_;
gfx::program program_{shadow_vertex_source, shadow_fragment_source};
shadow_map_builder builder_{1024, 1024};
};
void shadow_renderer::push(render_state const & state)
{
render_states_.push_back(state);
}
void shadow_renderer::render(render_options const & options)
{
for (auto const & state : render_states_)
{
if (state.casts_shadow && state.bbox)
builder_.push({state.mesh, *state.bbox});
}
shadow_map_builder::render_options build_options;
build_options.light = {options.light.position[0], options.light.position[1], options.light.position[2]};
builder_.build(build_options);
options.framebuffer->bind();
gl::DrawBuffer(options.draw_buffer);
gl::Viewport(options.viewport[0].min, options.viewport[1].min, options.viewport[0].length(), options.viewport[1].length());
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Enable(gl::CULL_FACE);
gl::CullFace(gl::BACK);
program_.bind();
program_["u_transform"] = options.transform;
program_["u_light_position"] = options.light.position;
program_["u_camera_position"] = geom::homogeneous(options.camera_position);
program_["u_shadow_transform"] = builder_.transform();
program_["u_shadow_map"] = 0;
gl::ActiveTexture(gl::TEXTURE0);
builder_.texture().bind();
for (auto const & state : render_states_)
{
program_["u_material"] = geom::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
state.mesh->draw();
}
render_states_.clear();
}
struct vertex
{
geom::point<float, 3> position;
geom::vector<float, 3> normal;
gfx::color_rgba color;
};
struct shadow_app
: app::app
{
geom::spherical_camera camera;
shadow_renderer renderer;
gfx::mesh plane_mesh;
shadow_renderer::material plane_material;
gfx::mesh cube_mesh;
shadow_renderer::material cube_material;
geom::box<float, 3> cube_bbox;
gfx::mesh sphere_mesh;
shadow_renderer::material sphere_material;
geom::box<float, 3> sphere_bbox;
gfx::mesh torus_mesh;
shadow_renderer::material torus_material;
geom::box<float, 3> torus_bbox;
util::clock<std::chrono::duration<float>> clock;
float time = 0.f;
bool paused = false;
shadow_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 on_key_down(SDL_Keycode key) override;
void render() override;
};
shadow_app::shadow_app()
: app("Shadow")
{
vsync(true);
camera.near_clip = 0.1f;
camera.far_clip = 1000.f;
camera.fov_y = geom::rad(45.f);
camera.azimuthal_angle = 0.f;
camera.elevation_angle = geom::rad(30.f);
camera.target = {0.f, 0.f, 0.f};
camera.distance = 10.f;
{
std::vector<vertex> vertices;
vertices.push_back({{-10.f, -10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
vertices.push_back({{ 10.f, -10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
vertices.push_back({{-10.f, 10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
vertices.push_back({{-10.f, 10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
vertices.push_back({{ 10.f, -10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
vertices.push_back({{ 10.f, 10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
plane_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
plane_mesh.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW);
plane_material.ambient = 0.2f;
plane_material.diffuse = 1.f;
plane_material.specular = 0.f;
plane_material.shininess = 1.f;
}
{
auto cube = geom::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {0.f, 5.f}}};
auto vertices = geom::vertices(cube);
auto faces = geom::faces(cube);
auto normals = geom::flat_normals(vertices, faces);
auto flat_vertices = geom::deindex(vertices, faces);
std::vector<vertex> mesh_vertices;
for (std::size_t i = 0; i < flat_vertices.size(); ++i)
{
mesh_vertices.push_back({flat_vertices[i][0], normals[i], {255, 127, 127, 255}});
mesh_vertices.push_back({flat_vertices[i][1], normals[i], {255, 127, 127, 255}});
mesh_vertices.push_back({flat_vertices[i][2], normals[i], {255, 127, 127, 255}});
}
cube_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
cube_mesh.load(mesh_vertices, gl::TRIANGLES, gl::STATIC_DRAW);
cube_material.ambient = 0.2f;
cube_material.diffuse = 1.f;
cube_material.specular = 0.6f;
cube_material.shininess = 10000.f;
cube_bbox = cube;
}
{
std::vector<vertex> vertices;
geom::point<float, 3> const position = {3.f, 2.f, 3.f};
float const radius = 1.f;
int const N = 24;
gfx::color_rgba color { 63, 63, 191, 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({position + radius * n, n, color});
}
}
vertices.push_back({position + geom::vector{0.f, 0.f, -radius}, {0.f, 0.f, -1.f}, color});
vertices.push_back({position + geom::vector{0.f, 0.f, radius}, {0.f, 0.f, 1.f}, color});
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_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
sphere_mesh.load(vertices, indices, gl::STATIC_DRAW);
sphere_material.ambient = 0.2f;
sphere_material.diffuse = 1.f;
sphere_material.specular = 1.f;
sphere_material.shininess = 100.f;
sphere_bbox[0] = {position[0] - radius, position[0] + radius};
sphere_bbox[1] = {position[1] - radius, position[1] + radius};
sphere_bbox[2] = {position[2] - radius, position[2] + radius};
}
{
std::vector<vertex> vertices;
geom::point<float, 3> const position = {-3.f, 2.f, 3.f};
float const radius1 = 1.f;
float const radius2 = 0.2f;
int const N = 72;
int const M = 24;
gfx::color_rgba color { 63, 63, 191, 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, n, color});
}
}
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_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
torus_mesh.load(vertices, indices, gl::STATIC_DRAW);
torus_material.ambient = 0.2f;
torus_material.diffuse = 1.f;
torus_material.specular = 1.f;
torus_material.shininess = 100.f;
torus_bbox[0] = {position[0] - radius1 - radius2, position[0] + radius1 + radius2};
torus_bbox[1] = {position[1] - radius1 - radius2, position[1] + radius1 + radius2};
torus_bbox[2] = {position[2] - radius2, position[2] + radius2};
}
}
void shadow_app::on_resize(int width, int height)
{
app::on_resize(width, height);
camera.set_fov(camera.fov_y, (1.f * width) / height);
}
void shadow_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 shadow_app::on_mouse_wheel(int delta)
{
app::on_mouse_wheel(delta);
camera.distance *= std::pow(0.8f, delta);
}
void shadow_app::on_key_down(SDL_Keycode key)
{
if (key == SDLK_SPACE)
paused = !paused;
}
void shadow_app::render()
{
if (!paused)
time += clock.restart().count() / 4.f;
else
clock.restart();
geom::vector<float, 3> light_dir = {std::cos(time), std::sin(time), 0.5f};
gfx::framebuffer::null().bind();
gl::DrawBuffer(gl::BACK);
gl::Viewport(0, 0, width(), height());
gl::ClearColor(0.7f, 0.7f, 1.f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
renderer.push({plane_material, &plane_mesh, false, std::nullopt});
renderer.push({sphere_material, &sphere_mesh, true, sphere_bbox});
renderer.push({cube_material, &cube_mesh, true, cube_bbox});
renderer.push({torus_material, &torus_mesh, true, torus_bbox});
shadow_renderer::render_options options;
options.framebuffer = &gfx::framebuffer::null();
options.viewport = {{{0, width()}, {0, height()}}};
options.draw_buffer = gl::BACK;
options.transform = camera.transform();
options.light.position = geom::homogeneous(light_dir);
options.camera_position = camera.position();
renderer.render(options);
gfx::check_error();
}
int main()
{
return app::main<shadow_app>();
}