diff --git a/examples/grass.cpp b/examples/grass.cpp new file mode 100644 index 00000000..6e1596c4 --- /dev/null +++ b/examples/grass.cpp @@ -0,0 +1,479 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace psemek; + +static char const ground_vs[] = +R"(#version 330 + +uniform mat4 u_transform; + +layout (location = 0) in vec4 in_position; +layout (location = 1) in vec4 in_color; + +out vec4 color; + +void main() +{ + gl_Position = u_transform * in_position; + color = in_color; +} +)"; + +static char const ground_fs[] = +R"(#version 330 + +in vec4 color; + +out vec4 out_color; + +void main() +{ + out_color = color; +} +)"; + +static char const grass_vs[] = +R"(#version 330 + +uniform mat4 u_transform; +uniform mat4 u_tile_transform; +uniform float u_height00; +uniform float u_height01; +uniform float u_height10; +uniform float u_height11; +uniform sampler1D u_texture; + +layout (location = 0) in vec4 in_position; +layout (location = 1) in float in_t; + +out vec4 color; + +void main() +{ + vec2 p = (u_tile_transform * vec4(in_position.xy * 2.0 - vec2(0.5), 0.0, 1.0)).xy; + + vec2 o = (u_tile_transform * vec4(0.5, 0.5, 0.0, 1.0)).xy; + + o = vec2(floor(o.x), floor(o.y)); + + vec2 d = p - o; + + float h = mix(mix(u_height00, u_height01, d.x), mix(u_height10, u_height11, d.x), d.y); + + gl_Position = u_transform * vec4(p, in_position.z * h, 1.0); + color = mix(vec4(0.0, 0.0, 0.0, 1.0), texture(u_texture, in_t), in_position.z); +} +)"; + +static char const grass_fs[] = +R"(#version 330 + +in vec4 color; + +out vec4 out_color; + +void main() +{ + out_color = color; +} +)"; + +struct grass_app + : app::app +{ + geom::free_camera camera; + + int size = 64; + + pcg::perlin density; + + gfx::program ground_program{ground_vs, ground_fs}; + gfx::indexed_mesh ground_mesh; + + gfx::program grass_program{grass_vs, grass_fs}; + gfx::indexed_mesh grass_mesh; + gfx::texture_1d grass_texture; + + geom::matrix random_transform[8]; + util::array random_transform_index; + + util::clock<> frame_clock; + util::moving_average frame_time{64}; + + util::clock> update_clock; + + gfx::painter painter; + + grass_app() + : app("Grass", 4) + { + vsync(false); + + camera.fov_y = geom::rad(45.f); + camera.near_clip = 0.01f; + camera.far_clip = 1000.f; + camera.pos = {0.5f, 0.5f, 1.5f}; + camera.elevation_angle = geom::rad(15.f); + camera.azimuthal_angle = geom::rad(-90.f); + + init_ground(); + init_grass(); + + pcg::generator rng; + pcg::uniform_sphere_vector_distribution d; + + util::array, 2> grad({size / 8 + 1, size / 8 + 1}); + for (auto & v : grad) v = d(rng); + density = pcg::perlin(std::move(grad)); + + for (int i = 0; i < 8; ++i) + { + if (i < 4) + random_transform[i] = geom::matrix::identity(); + else + random_transform[i] = geom::swap(0, 1).homogeneous_matrix(); + + random_transform[i] = random_transform[i] + * geom::translation(geom::vector{0.5f, 0.5f, 0.f}).homogeneous_matrix() + * geom::plane_rotation(0, 1, i * geom::pi / 2.f).homogeneous_matrix() + * geom::translation(geom::vector{-0.5f, -0.5f, 0.f}).homogeneous_matrix(); + } + + random_transform_index.resize({size, size}); + for (auto & i : random_transform_index) + i = pcg::uniform_int_distribution{0, 7}(rng); + } + + void init_ground() + { + ground_mesh.setup, gfx::normalized>(); + + struct vertex + { + geom::point pos; + gfx::color_rgba color; + }; + + std::vector vertices; + std::vector> triangles; + + gfx::color_rgba ground_color{47, 23, 11, 255}; + + for (int x = 0; x < size; ++x) + { + for (int y = 0; y < size; ++y) + { + std::uint32_t base = vertices.size(); + + float d = 0.0f; + + vertices.push_back({{x + d, y + d, 0.f}, ground_color}); + vertices.push_back({{x + 1 - d, y + d, 0.f}, ground_color}); + vertices.push_back({{x + d, y + 1 - d, 0.f}, ground_color}); + vertices.push_back({{x + 1 - d, y + 1 - d, 0.f}, ground_color}); + + triangles.push_back({base + 0, base + 1, base + 2}); + triangles.push_back({base + 2, base + 1, base + 3}); + } + } + + ground_mesh.load(vertices, triangles, gl::STATIC_DRAW); + } + + void init_grass() + { + frame_clock.restart(); + struct vertex + { + geom::point pos; + std::uint8_t t; + std::uint8_t density; + + vertex(geom::point const & p, float t, float d) + { + for (std::size_t i = 0; i < 2; ++i) + pos[i] = geom::clamp(std::round((p[i] + 0.5f) / 2.f * 65535.f), {0.f, 65535.f}); + + pos[2] = geom::clamp(std::round(p[2] * 65535.f), {0.f, 65535.f}); + + this->t = geom::clamp(std::round(t * 255.f), {0.f, 255.f}); + this->density = geom::clamp(std::round(d * 255.f), {0.f, 255.f}); + } + }; + + { + geom::gradient g + { + std::make_pair(-1.f, gfx::color_4f{0.f, 0.2f, 0.f, 1.f}), + geom::easing_type::linear, + std::pair{0.f, gfx::color_4f{0.4f, 0.65f, 0.35f, 1.f}}, + geom::easing_type::linear, + std::pair{1.f, gfx::color_4f{0.7f, 0.75f, 0.2f, 1.f}} + }; + + util::array pm({256}); + for (std::size_t i = 0; i < pm.width(); ++i) + { + pm(i) = gfx::to_coloru8(g((i + 0.5f) / pm.width())); +// pm(i) = gfx::to_coloru8(gfx::color_4f{(i + 0.5f) / pm.width(), 0.f, 0.f, 1.f}); + } + + grass_texture.load(pm); + grass_texture.linear_filter(); + grass_texture.generate_mipmap(); + } + + pcg::generator rng; + + std::size_t memory = 0; + + grass_mesh.setup>, gfx::normalized, gfx::normalized>(); + + std::vector vertices; + std::vector> triangles; + pcg::uniform_ball_vector_distribution d_offset; + pcg::uniform_box_point_distribution d_origin({{{0.f, 1.f}, {0.f, 1.f}}}); + pcg::uniform_sphere_vector_distribution d_orientation; + pcg::uniform_real_distribution d_width{1.f / 64.f, 1.f / 128.f}; + pcg::uniform_real_distribution d_height{0.25f, 1.f}; + pcg::uniform_real_distribution d_density{0.f, 1.f}; + + for (int blade = 0; blade < 2048; ++blade) + { + int const segments = 8; + float const max_lean = 0.2f; + + int tx = (blade % 64) % 8; + int ty = (blade % 64) / 8; + + auto o = d_origin(rng); + o[0] = o[0] / 8.f + tx / 8.f; + o[1] = o[1] / 8.f + ty / 8.f; + +// geom::point o{(blade % 256) / 256.f, std::fmod(blade * phi, 1.f)}; +// o += d_offset(rng) * 0.f; + auto r = d_orientation(rng); + auto s = d_width(rng) * 0.5f; + auto h = d_height(rng); + + auto n = geom::ort(r); + + auto color = pcg::uniform_real_distribution{0.f, 1.f}(rng); + + float den = d_density(rng); + + auto get = [&](float x, float z) -> vertex + { + assert(z >= 0.f); + assert(z <= 1.f); + float y = (1.f - std::sqrt(1.f - z * z)) * max_lean; + + float w = 1.f - z / h; + + return {geom::point{o[0] + r[0] * s * x * w + n[0] * y, o[1] + r[1] * s * x * w + n[1] * y, z}, color, den}; + }; + + + for (int s = 0; s <= segments; ++s) + { + float t = (s * 1.f) / segments; + t = geom::easing(geom::easing_type::quadratic_out, t); + float z = h * t; + std::uint32_t base = vertices.size(); + + if (s < segments) + { + vertices.push_back(get(-1.f, z)); + vertices.push_back(get( 1.f, z)); + + if (s > 0) + { + triangles.push_back({base - 2, base - 1, base}); + triangles.push_back({base, base - 1, base + 1}); + } + } + else + { + vertices.push_back(get(0.f, z)); + triangles.push_back({base - 2, base - 1, base}); + } + } + } + + grass_mesh.load(vertices, triangles, gl::STATIC_DRAW); + + memory += (vertices.size() * sizeof(vertices[0]) + triangles.size() * sizeof(triangles[0])); + + log::info() << memory << " bytes"; + log::info() << vertices.size() << " vertices"; + log::info() << frame_clock.count(); + } + + 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); + } + + void update() override + { + float dt = update_clock.restart().count(); + + auto d = camera.direction(); + float s = 20.f; + + if (is_key_down(SDLK_LSHIFT)) + s = 2.5f; + + if (is_key_down(SDLK_SPACE)) + { + d[2] = 0.f; + d = geom::normalized(d); + } + + auto n = geom::normalized(geom::cross(d, geom::vector{0.f, 0.f, 1.f})); + + if (is_key_down(SDLK_w)) + { + camera.pos += d * dt * s; + } + + if (is_key_down(SDLK_s)) + { + camera.pos -= d * dt * s; + } + + if (is_key_down(SDLK_a)) + { + camera.pos -= n * dt * s; + } + + if (is_key_down(SDLK_d)) + { + camera.pos += n * dt * s; + } + } + + void render() override + { + gl::ClearColor(0.8f, 0.8f, 1.f, 1.f); + gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); + + gl::Enable(gl::DEPTH_TEST); + gl::DepthFunc(gl::LEQUAL); + gl::Disable(gl::BLEND); + + ground_program.bind(); + ground_program["u_transform"] = camera.transform(); + ground_mesh.draw(); + + grass_program.bind(); + grass_program["u_texture"] = 0; + grass_texture.bind(); + grass_program["u_transform"] = camera.transform(); + + std::size_t triangles = 0; + + for (int x = 0; x < size; ++x) + { + for (int y = 0; y < size; ++y) + { + grass_program["u_tile_transform"] = geom::translation(geom::vector{x, y, 0.f}).homogeneous_matrix() + * random_transform[random_transform_index(x, y)]; + + float d = density({(x + 0.5f) / size, (y + 0.5f) / size}); + + int const N = 8; + + int i = std::floor(d * N); + if (i > N - 1) i = N - 1; + + { + float h00 = density({(x + 0.f) / size, (y + 0.f) / size}); + float h01 = density({(x + 1.f) / size, (y + 0.f) / size}); + float h10 = density({(x + 0.f) / size, (y + 1.f) / size}); + float h11 = density({(x + 1.f) / size, (y + 1.f) / size}); + + grass_program["u_height00"] = h00; + grass_program["u_height01"] = h01; + grass_program["u_height10"] = h10; + grass_program["u_height11"] = h11; + grass_mesh.draw(0, ((i + 1) * grass_mesh.index_count()) / N); +// grass_mesh.draw(); + + triangles += grass_mesh.index_count() / 3; + } + } + } + + frame_time.push(frame_clock.restart().count()); + + { + gfx::painter::text_options opts; + opts.x = gfx::painter::x_align::left; + opts.y = gfx::painter::y_align::top; + opts.f = gfx::painter::font::font_9x12; + opts.scale = 2.f; + opts.c = {0, 0, 0, 255}; + + painter.text({0.f, 0.f}, util::to_string("FPS: ", std::round(1.0 / frame_time.average())), opts); + painter.text({0.f, 24.f}, util::to_string("Camera: ", camera.position()), opts); + painter.text({0.f, 48.f}, util::to_string("Triangles: ", triangles), opts); + } + + gl::Disable(gl::DEPTH_TEST); + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); + geom::window_camera camera; + camera.width = width(); + camera.height = height(); + painter.render(camera.transform()); + } + +}; + +int main() +{ + return app::main(); +}