Add a procedural fire example

This commit is contained in:
Nikita Lisitsa 2020-10-01 23:10:13 +03:00
parent f5e9154492
commit af5bf48533

326
examples/fire.cpp Normal file
View file

@ -0,0 +1,326 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/texture.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/geom/easing.hpp>
#include <psemek/geom/gradient.hpp>
#include <psemek/pcg/random/device.hpp>
#include <psemek/pcg/random/generator.hpp>
#include <psemek/pcg/random/uniform_sphere.hpp>
#include <psemek/pcg/perlin.hpp>
#include <psemek/util/clock.hpp>
using namespace psemek;
static char const vertex_source[] =
R"(#version 330
uniform mat4 u_transform;
uniform vec3 u_camera_pos;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec3 in_instance_pos;
layout (location = 2) in vec3 in_instance_z;
layout (location = 3) in float in_instance_size;
layout (location = 4) in vec2 in_instance_noise_offset;
out vec2 texcoord;
out vec2 noise_offset;
out float z;
void main()
{
vec3 instance_y = u_camera_pos - in_instance_pos;
instance_y -= dot(in_instance_z, instance_y) * in_instance_z;
instance_y = -normalize(instance_y);
vec3 instance_x = cross(instance_y, in_instance_z);
mat4 transform;
transform[0] = vec4(instance_x, 0.0);
transform[1] = vec4(instance_y, 0.0);
transform[2] = vec4(in_instance_z, 0.0);
transform[3] = vec4(in_instance_pos, 1.0);
gl_Position = u_transform * (transform * in_position);
texcoord = in_position.xz * 0.5 + vec2(0.5, 0.5);
noise_offset = in_instance_noise_offset;
z = in_position.z;
}
)";
static char const fragment_source[] =
R"(#version 330
uniform sampler2D u_texture;
uniform sampler2D u_noise_texture;
uniform float u_time;
in vec2 texcoord;
in vec2 noise_offset;
in float z;
out vec4 out_color;
void main()
{
vec2 tc = texcoord + (2.0 * texture(u_noise_texture, texcoord + noise_offset * u_time).xy - vec2(1.0, 1.0)) * 0.1 * z;
out_color = texture(u_texture, tc);
}
)";
struct candle_renderer
{
candle_renderer();
void add(geom::point<float, 3> const & pos, geom::vector<float, 3> const & dir, float size);
void render(geom::camera const & camera, float time);
private:
struct candle
{
geom::point<float, 3> pos;
geom::vector<float, 3> dir;
float size;
geom::vector<float, 2> noise_offset;
};
std::vector<candle> candles_;
bool instances_need_update_ = false;
gfx::program program_{vertex_source, fragment_source};
gfx::instanced_mesh mesh_;
gfx::texture_2d texture_;
gfx::texture_2d noise_texture_;
void update_instances();
};
candle_renderer::candle_renderer()
{
std::vector<geom::point<float, 3>> vertices;
vertices.push_back({-1.f, 0.f, -1.f});
vertices.push_back({ 1.f, 0.f, -1.f});
vertices.push_back({ 1.f, 0.f, 1.f});
vertices.push_back({-1.f, 0.f, -1.f});
vertices.push_back({ 1.f, 0.f, 1.f});
vertices.push_back({-1.f, 0.f, 1.f});
mesh_.setup<geom::point<float, 3>, gfx::instanced<geom::point<float, 3>>, gfx::instanced<geom::vector<float, 3>>, gfx::instanced<float>, gfx::instanced<geom::vector<float, 2>>>();
mesh_.load(vertices, gl::TRIANGLES);
geom::vector<float, 4> c0 {1.f, 0.99f, 0.98f, 1.f};
geom::vector<float, 4> c1 {1.f, 0.4f, 0.f, 0.75f};
geom::vector<float, 4> c2 {c1[0], c1[1], c1[2], 0.f};
geom::gradient<float> gy{
std::make_pair(0.f, 0.f),
geom::easing_type::quadratic_in,
std::make_pair(1.f, 2.f),
};
geom::gradient<float, geom::vector<float, 4>> gc
{
std::make_pair(0.1f, c0),
geom::easing_type::linear,
std::pair{0.8f, c1},
geom::easing_type::cubic,
std::pair{1.2f, c2}
};
{
gfx::pixmap_rgba pm({512, 512}, {0, 0, 0, 0});
for (auto idx : pm.indices())
{
float x = 2.f * (idx[0] + 0.5f) / pm.width() - 1.f;
float y = 2.f * (idx[1] + 0.5f) / pm.height() - 1.f;
float d = 10.f;
float size = 0.1f;
if (y >= 0)
{
d = std::abs(x) / size + gy(y);
}
else
{
d = std::sqrt(x * x + y * y) / size;
}
pm(idx) = gfx::to_coloru8(gc(d));
}
texture_.load(pm);
texture_.linear_filter();
texture_.anisotropy();
texture_.generate_mipmap();
texture_.clamp();
}
{
pcg::generator rng;
pcg::uniform_sphere_vector_distribution<float, 2> d;
util::array<geom::vector<float, 2>, 2> grad({16, 16});
for (auto & v : grad) v = d(rng);
pcg::perlin<float, 2> perlinx(grad, pcg::seamless);
for (auto & v : grad) v = d(rng);
pcg::perlin<float, 2> perliny(grad, pcg::seamless);
gfx::basic_pixmap<geom::vector<std::uint8_t, 2>> pm({512, 512});
for (auto idx : pm.indices())
{
float x = (0.5f + idx[0]) / pm.width();
float y = (0.5f + idx[1]) / pm.height();
pm(idx) = gfx::to_coloru8(geom::vector{perlinx({x, y}), perliny({x, y})});
}
noise_texture_.load(pm);
noise_texture_.linear_filter();
noise_texture_.anisotropy();
noise_texture_.generate_mipmap();
noise_texture_.repeat();
}
}
void candle_renderer::add(geom::point<float, 3> const & pos, geom::vector<float, 3> const & dir, float size)
{
pcg::generator rng{pcg::random_device{}};
pcg::uniform_sphere_vector_distribution<float, 2> d;
geom::vector<float, 2> noise_offset;
while (true)
{
noise_offset = d(rng);
if (std::abs(noise_offset[0]) < 0.5f)
break;
}
if (noise_offset[1] > 0.f)
noise_offset[1] *= -1.f;
candles_.push_back({pos, dir, size, noise_offset * 0.5f});
instances_need_update_ = true;
}
void candle_renderer::render(geom::camera const & camera, float time)
{
if (instances_need_update_) update_instances();
program_.bind();
program_["u_transform"] = camera.transform();
program_["u_camera_pos"] = camera.position();
program_["u_texture"] = 0;
program_["u_noise_texture"] = 1;
program_["u_time"] = time;
gl::ActiveTexture(gl::TEXTURE0);
texture_.bind();
gl::ActiveTexture(gl::TEXTURE1);
noise_texture_.bind();
(void)time;
mesh_.draw();
}
void candle_renderer::update_instances()
{
struct instance
{
geom::point<float, 3> pos;
geom::vector<float, 3> dir;
float size;
geom::vector<float, 2> noise_offset;
};
std::vector<instance> instances;
instances.reserve(candles_.size());
for (candle const & c: candles_)
{
instances.push_back({c.pos, c.dir, c.size, c.noise_offset});
}
mesh_.load_instance(instances);
}
struct fire_app
: app::app
{
geom::spherical_camera camera;
candle_renderer candles;
util::clock<std::chrono::duration<float>> clock;
float time = 0.f;
fire_app()
: app("Fire")
{
camera.fov_y = geom::rad(45.f);
camera.near_clip = 0.01f;
camera.far_clip = 1000.f;
camera.target = {0.f, 0.f, 0.f};
camera.elevation_angle = 0.f;
camera.azimuthal_angle = 0.f;
camera.distance = 4.f;
candles.add({0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, 1.f);
}
void on_resize(int width, int height) override
{
app::on_resize(width, height);
camera.set_fov(camera.fov_y, (1.f * width) / height);
}
void on_mouse_move(int x, int y, int dx, int dy) override
{
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 on_mouse_wheel(int delta) override
{
app::on_mouse_wheel(delta);
camera.distance *= std::pow(0.8f, delta);
}
void update() override
{
if (!is_key_down(SDLK_SPACE))
time += clock.restart().count();
}
void render() override
{
gl::ClearColor(0.f, 0.f, 0.f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
candles.render(camera, time);
}
};
int main()
{
return app::main<fire_app>();
}