#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(); }