#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 #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); color = texture(u_texture, in_t); } )"; static char const grass_fs[] = R"(#version 330 in vec4 color; out vec4 out_color; void main() { out_color = color; } )"; static char const grass_slice_vs[] = R"(#version 330 uniform mat4 u_transform; uniform mat4 u_tile_transform; uniform float u_density00; uniform float u_density01; uniform float u_density10; uniform float u_density11; layout (location = 0) in vec4 in_position; layout (location = 1) in vec2 in_texcoord; out vec3 texcoord; void main() { vec2 p = (u_tile_transform * vec4(in_position.xy, 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 level = mix(mix(u_density00, u_density01, d.x), mix(u_density10, u_density11, d.x), d.y); gl_Position = u_transform * vec4(p, in_position.z, 1.0); texcoord = vec3(in_texcoord, level); } )"; static char const grass_slice_fs[] = R"(#version 330 uniform sampler2DArray u_texture; in vec3 texcoord; out vec4 out_color; void main() { float l0 = floor(texcoord.z); float l1 = l0 + 1; float t = texcoord.z - l0; vec4 c0 = texture(u_texture, vec3(texcoord.xy, l0)); vec4 c1 = texture(u_texture, vec3(texcoord.xy, l1)); vec4 c = mix(c0, c1, t); // vec4 c = c0; out_color = vec4(c.rgb / c.a, c.a); } )"; struct grass_app : app::app { geom::free_camera camera; int size = 64; int const density_level_count = 8; pcg::perlin density; gfx::program ground_program{ground_vs, ground_fs}; gfx::mesh ground_mesh; gfx::program grass_program{grass_vs, grass_fs}; gfx::mesh grass_mesh; gfx::texture_1d grass_texture; gfx::program grass_slice_program{grass_slice_vs, grass_slice_fs}; gfx::texture_2d_array grass_slice_z_texture; gfx::mesh grass_slice_z_mesh; gfx::framebuffer grass_slice_framebuffer; gfx::renderbuffer grass_slice_renderbuffer; 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.1f; camera.far_clip = 1000.f; camera.pos = {0.5f, 0.5f, 1.5f}; camera.rotateYZ(geom::rad(-90.f)); init_ground(); init_grass(); init_grass_slices(); random::generator rng; random::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 = random::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())); } grass_texture.load(pm); grass_texture.linear_filter(); grass_texture.generate_mipmap(); } random::generator rng; std::size_t memory = 0; grass_mesh.setup>, gfx::normalized, gfx::normalized>(); std::vector vertices; std::vector> triangles; random::uniform_box_point_distribution d_origin({{{0.f, 1.f}, {0.f, 1.f}}}); random::uniform_sphere_vector_distribution d_orientation; random::uniform_real_distribution d_width{1.f / 64.f, 1.f / 128.f}; random::uniform_real_distribution d_height{0.25f, 1.f}; random::uniform_real_distribution d_density{0.f, 1.f}; for (int blade = 0; blade < 4096; ++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; auto r = d_orientation(rng); auto s = d_width(rng) * 0.5f; auto h = d_height(rng); auto n = geom::ort(r); auto color = random::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 init_grass_slices() { struct vertex { geom::point position; geom::vector texcoord; }; int const slice_count = 4; float slice_width = 1.f / slice_count; std::vector vertices; vertices.push_back({{0.f, 0.f, slice_width}, {0.f, 0.f}}); vertices.push_back({{1.f, 0.f, slice_width}, {1.f, 0.f}}); vertices.push_back({{0.f, 1.f, slice_width}, {0.f, 1.f}}); vertices.push_back({{0.f, 1.f, slice_width}, {0.f, 1.f}}); vertices.push_back({{1.f, 0.f, slice_width}, {1.f, 0.f}}); vertices.push_back({{1.f, 1.f, slice_width}, {1.f, 1.f}}); grass_slice_z_mesh.setup, geom::vector>(); grass_slice_z_mesh.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW); std::size_t slice_resolution = 256; grass_slice_z_texture.load({slice_resolution, slice_resolution, density_level_count}); grass_slice_renderbuffer.storage(gl::DEPTH24_STENCIL8, {slice_resolution, slice_resolution}); for (int d = 0; d < density_level_count; ++d) { grass_slice_framebuffer.color(grass_slice_z_texture, d); grass_slice_framebuffer.depth(grass_slice_renderbuffer); grass_slice_framebuffer.assert_complete(); gl::Viewport(0, 0, slice_resolution, slice_resolution); gl::ClearColor(0.f, 0.f, 0.f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); gl::Enable(gl::DEPTH_TEST); gl::DepthFunc(gl::LEQUAL); grass_program.bind(); grass_program["u_tile_transform"] = geom::matrix::identity(); grass_program["u_height00"] = 1.f; grass_program["u_height01"] = 1.f; grass_program["u_height10"] = 1.f; grass_program["u_height11"] = 1.f; grass_program["u_texture"] = 0; grass_texture.bind(); for (int x = -1; x <= 1; ++x) { for (int y = -1; y <= 1; ++y) { grass_program["u_transform"] = geom::translation({-1.f + 2.f * x, -1.f + 2.f * y, 0.f}).homogeneous_matrix() * geom::scale({2.f, 2.f, 1.f}).homogeneous_matrix(); grass_mesh.draw(0, (grass_mesh.index_count() * (d + 1)) / density_level_count); } } } gfx::framebuffer::null().bind(); grass_slice_z_texture.linear_filter(); grass_slice_z_texture.anisotropy(); grass_slice_z_texture.generate_mipmap(); grass_slice_z_texture.clamp(); // grass_slice_z_texture.bind(); // gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST); // gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR); } 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.rotateZX(0.01f * dx); camera.rotateYZ(0.01f * dy); } } 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 present() 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::Enable(gl::BLEND); gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); auto camera_transform = camera.transform(); 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) { if (x < size / 2) continue; 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 i = std::floor(d * density_level_count); if (i > density_level_count - 1) i = density_level_count - 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()) / density_level_count); triangles += (((i + 1) * grass_mesh.index_count()) / density_level_count) / 3; } } } grass_slice_program.bind();; grass_slice_program["u_transform"] = camera_transform; grass_slice_program["u_texture"] = 0; grass_slice_z_texture.bind(); for (int x = 0; x < size; ++x) { for (int y = 0; y < size; ++y) { if (x >= size / 2) continue; grass_slice_program["u_tile_transform"] = geom::translation(geom::vector{x, y, 0.f}).homogeneous_matrix() * random_transform[random_transform_index(x, y)]; float d00 = density({(x + 0.f) / size, (y + 0.f) / size}) * density_level_count; float d01 = density({(x + 1.f) / size, (y + 0.f) / size}) * density_level_count; float d10 = density({(x + 0.f) / size, (y + 1.f) / size}) * density_level_count; float d11 = density({(x + 1.f) / size, (y + 1.f) / size}) * density_level_count; grass_slice_program["u_density00"] = d00; grass_slice_program["u_density01"] = d01; grass_slice_program["u_density10"] = d10; grass_slice_program["u_density11"] = d11; grass_slice_z_mesh.draw(); } } 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(); }