#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 const & pos, geom::vector const & dir, float size); void render(geom::camera const & camera, float time); private: struct candle { geom::point pos; geom::vector dir; float size; geom::vector noise_offset; }; std::vector candles_; bool instances_need_update_ = false; gfx::program program_{vertex_source, fragment_source}; gfx::mesh mesh_; gfx::texture_2d texture_; gfx::texture_2d noise_texture_; void update_instances(); }; candle_renderer::candle_renderer() { std::vector> 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, gfx::instanced>, gfx::instanced>, gfx::instanced, gfx::instanced>>(); mesh_.load(vertices, gl::TRIANGLES); geom::vector c0 {1.f, 0.99f, 0.98f, 1.f}; geom::vector c1 {1.f, 0.4f, 0.f, 0.75f}; geom::vector c2 {c1[0], c1[1], c1[2], 0.f}; geom::gradient gy{ std::make_pair(0.f, 0.f), geom::easing_type::quadratic_in, std::make_pair(1.f, 2.f), }; geom::gradient> 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(); } { random::generator rng; random::uniform_sphere_vector_distribution d; util::array, 2> grad({16, 16}); for (auto & v : grad) v = d(rng); pcg::perlin perlinx(grad, pcg::seamless); for (auto & v : grad) v = d(rng); pcg::perlin perliny(grad, pcg::seamless); gfx::basic_pixmap> 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 const & pos, geom::vector const & dir, float size) { random::generator rng{random::device{}}; random::uniform_sphere_vector_distribution d; geom::vector 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(); mesh_.draw(); } void candle_renderer::update_instances() { struct instance { geom::point pos; geom::vector dir; float size; geom::vector noise_offset; }; std::vector 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> 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 present() 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(); }