17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 13:29:07 +01:00

feat(terrain): holes cut in the heightmap terrain and shader grass painted over it

This commit is contained in:
2026-10-20 01:32:01 +02:00
parent d7ba6926fc
commit f3bdadad66
32 changed files with 2020 additions and 30 deletions

41
shaders/grass.frag Normal file
View File

@@ -0,0 +1,41 @@
// grass of the heightmap terrain (opengl46_grass, grass.vert): the blades lit like the rest of the scene
in vec3 f_normal;
in vec4 f_pos;
in vec3 f_albedo;
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
#include <common>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
#include <light_common.glsl>
#include <apply_fog.glsl>
#include <tonemapping.glsl>
void main()
{
vec3 fragnormal = normalize(f_normal);
glossiness = 6.0;
metalic = 0.0;
vec3 fragcolor = apply_lights(ambient, fragnormal, f_albedo, 0.0, 0.2, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), 1.0);
#if POSTFX_ENABLED
out_color = color;
#else
out_color = tonemap(color);
#endif
#if MOTIONBLUR_ENABLED
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
#endif
}

206
shaders/grass.vert Normal file
View File

@@ -0,0 +1,206 @@
// grass of the heightmap terrain (opengl46_grass): a blade made of the numbers of its vertex and its instance.
//
// the instances of a draw stand in a tile of a chunk. a blade takes its place in the tile from a sequence which spreads
// any number of its first members evenly (R2, shifted for each tile), so drawing fewer of them thins the grass out
// evenly; a blade picks its kind by the densities painted there, and gets its height, its turn and its lean from its own
// numbers, so it's the same in every frame. blades of no kind, in the holes, or past the share drawn at their distance are
// collapsed into a point outside the view. the vertices go up the blade in pairs, the last one is its tip.
// the chunk is drawn relative to its corner: x, z in metres from the corner, y the height as it is
#include <common>
// x, z: corner of the tile in the chunk, metres; y: side of the tile; w: blades of the tile where its densest kind is painted fully
uniform vec4 grass_tile;
// x: the share of the densest kind at the densest place of the tile (the blades of the tile are counted for it); y: segments
// of a blade; z, w: number of the tile in the world
uniform vec4 grass_part;
// x: side of the chunk, metres; y: cells of its grid along a side; z: grass samples along a side less one
uniform vec4 grass_chunk;
// the camera relative to the corner of the chunk (y as it is)
uniform vec4 grass_camera;
// x: distance the grass starts to thin out at; y: reach of the grass
uniform vec4 grass_lod;
// x, y: direction of the wind (x, z of the world); z: its strength, 0..1; w: time, seconds
uniform vec4 grass_wind;
// the kinds of the chunk: colour at the root and at the tip (linear rgb), and height, its variation, width, lean
uniform vec4 grass_base[4];
uniform vec4 grass_tip[4];
uniform vec4 grass_shape[4];
// share of the blades of each kind against the densest one of the chunk
uniform vec4 grass_share;
layout(binding = 0) uniform sampler2D grass_heights; // heights of the grid points, R32F
layout(binding = 1) uniform sampler2D grass_density; // densities of the kinds, RGBA8
layout(binding = 2) uniform sampler2D grass_holes; // holes cut in the ground, R8
out vec3 f_normal;
out vec4 f_pos;
out vec3 f_albedo;
out vec4 f_clip_pos;
out vec4 f_clip_future_pos;
out vec4 UserData;
uint hash(uint x)
{
x ^= x >> 16;
x *= 0x7feb352dU;
x ^= x >> 15;
x *= 0x846ca68bU;
x ^= x >> 16;
return x;
}
float random(inout uint state)
{
state = hash(state);
return float(state >> 8) * (1.0 / 16777216.0);
}
// the share of the blades drawn at specified distance; the same as lod_share() of opengl46grass.cpp
float lod_share(float distance)
{
return distance <= grass_lod.x ? 1.0 : pow(grass_lod.x / distance, 1.5);
}
// height of the ground at a point of the chunk and its normal, the way the full grid is triangulated
float ground(vec2 point, out vec3 normal)
{
float cells = grass_chunk.y;
float spacing = grass_chunk.x / cells;
vec2 place = clamp(point / spacing, vec2(0.0), vec2(cells));
ivec2 cell = min(ivec2(place), ivec2(int(cells) - 1));
vec2 t = place - vec2(cell);
float h00 = texelFetch(grass_heights, cell, 0).r;
float h10 = texelFetch(grass_heights, cell + ivec2(1, 0), 0).r;
float h01 = texelFetch(grass_heights, cell + ivec2(0, 1), 0).r;
float h11 = texelFetch(grass_heights, cell + ivec2(1, 1), 0).r;
// the quads are cut along the diagonal from (x0, z1) to (x1, z0), as the mesh is made
if (t.x + t.y <= 1.0)
{
normal = normalize(vec3(-(h10 - h00) / spacing, 1.0, -(h01 - h00) / spacing));
return h00 + t.x * (h10 - h00) + t.y * (h01 - h00);
}
normal = normalize(vec3(-(h11 - h01) / spacing, 1.0, -(h11 - h10) / spacing));
return h11 + (1.0 - t.x) * (h01 - h11) + (1.0 - t.y) * (h10 - h11);
}
void collapse()
{
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
f_normal = vec3(0.0, 1.0, 0.0);
f_pos = vec4(0.0);
f_albedo = vec3(0.0);
f_clip_pos = gl_Position;
f_clip_future_pos = gl_Position;
UserData = vec4(0.0);
}
void main()
{
int blade = gl_InstanceID;
// the numbers of the blade, drawn in the same order whatever becomes of them
uint state = hash(uint(blade) * 0x9E3779B9U ^ hash(uint(int(grass_part.z)) * 0x85EBCA6BU ^ hash(uint(int(grass_part.w)) + 0x27D4EB2FU)));
uint tilestate = hash(uint(int(grass_part.z)) * 0x85EBCA6BU ^ hash(uint(int(grass_part.w)) + 0x165667B1U));
vec2 shift = vec2(random(tilestate), random(tilestate));
vec2 jitter = vec2(random(state), random(state)) - 0.5;
float pick = random(state);
float heightrandom = random(state) * 2.0 - 1.0;
float widthrandom = random(state);
float turnrandom = random(state) * 6.2831853;
float leanrandom = random(state) * 6.2831853;
float leanamount = random(state);
float shade = random(state);
float phase = random(state);
// place: R2 spreads the first blades evenly at any count, a little jitter breaks its lattice
vec2 spot = fract(shift + float(blade) * vec2(0.7548776662466927, 0.5698402909980532));
vec2 point = grass_tile.xy + fract(spot + jitter * (0.7 / sqrt(grass_tile.w))) * grass_tile.z;
if (any(lessThan(point, vec2(0.0))) || any(greaterThanEqual(point, vec2(grass_chunk.x))))
{
collapse();
return;
}
vec2 local = point / grass_chunk.x;
float samples = grass_chunk.z;
vec4 density = texture(grass_density, (local * samples + 0.5) / (samples + 1.0));
float holeside = float(textureSize(grass_holes, 0).x);
float hole = texture(grass_holes, (local * (holeside - 1.0) + 0.5) / holeside).r;
// the kind: the blades of the tile are shared among the kinds by their densities here
vec4 cover = density * grass_share / max(grass_part.x, 1e-3);
float covered = cover.x + cover.y + cover.z + cover.w;
if (covered > 1.0)
cover /= covered;
int kind = -1;
float sum = 0.0;
for (int i = 0; i < 4; ++i)
{
sum += cover[i];
if (pick < sum)
{
kind = i;
break;
}
}
vec3 groundnormal;
vec3 root = vec3(point.x, ground(point, groundnormal), point.y);
float distance = length(root - grass_camera.xyz);
float share = lod_share(distance);
float rank = (float(blade) + 0.5) / grass_tile.w;
if (kind < 0 || hole > 0.5 || rank >= share || distance > grass_lod.y)
{
collapse();
return;
}
vec4 shape = grass_shape[kind];
float height = shape.x * (1.0 + shape.y * heightrandom);
// the last of the blades drawn at the distance grow in, and the grass fades out at the end of its reach
height *= 1.0 - smoothstep(share * 0.8, share, rank);
height *= 1.0 - smoothstep(grass_lod.y * 0.75, grass_lod.y, distance);
// where the paint thins out the blades get shorter
height *= mix(0.5, 1.0, clamp(density[kind] * 1.5, 0.0, 1.0));
// the blades drawn further away stand for more of them, so they're wider
float width = shape.z * (0.8 + 0.4 * widthrandom) / sqrt(share);
// the face of the blade turns towards the camera further away, so the blades don't vanish edge on
vec2 facing = vec2(cos(turnrandom), sin(turnrandom));
vec2 toview = grass_camera.xz - root.xz;
toview = toview / max(length(toview), 1e-3);
facing = mix(facing, toview, smoothstep(grass_lod.x * 0.5, grass_lod.x * 2.0, distance));
facing = facing / max(length(facing), 1e-3);
vec2 across = vec2(-facing.y, facing.x);
// the lean of the blade and the wind: a steady push along it, with gusts running across the field, and a flutter
vec2 lean = vec2(cos(leanrandom), sin(leanrandom)) * (shape.w * (0.4 + 0.6 * leanamount));
float gust = 0.5 + 0.5 * sin(grass_wind.w * 0.9 - dot(root.xz, grass_wind.xy) * 0.12);
float flutter = sin(grass_wind.w * (2.0 + phase) + phase * 6.2831853);
vec2 sway = grass_wind.xy * grass_wind.z * (0.15 + 0.45 * gust + 0.08 * flutter);
vec2 bend = lean + sway;
// a long lean takes some of the height
float bent = min(length(bend), 0.9);
int segments = int(grass_part.y);
int level = min(gl_VertexID / 2, segments);
float side = (gl_VertexID >= segments * 2) ? 0.0 : ((gl_VertexID & 1) == 0 ? -0.5 : 0.5);
float t = float(level) / float(segments);
float halfwidth = width * (1.0 - pow(t, 1.5));
vec2 xz = root.xz + across * (side * halfwidth) + bend * (height * t * t);
float y = root.y - 0.02 + height * t * (1.0 - 0.35 * bent * t);
vec3 position = vec3(xz.x, y, xz.y);
// lit mostly as the ground under it, which keeps a field of blades from shimmering
vec3 normal = normalize(groundnormal * 0.8 + vec3(facing.x, 0.0, facing.y) * (side == 0.0 ? 0.0 : 0.25) + vec3(0.0, 0.3, 0.0));
vec3 albedo = mix(grass_base[kind].rgb, grass_tip[kind].rgb, t) * (0.85 + 0.3 * shade);
// the lower part of the blades is shaded by the others
albedo *= mix(0.7, 1.0, t);
f_pos = modelview * vec4(position, 1.0);
f_normal = normalize(modelviewnormal * normal);
f_albedo = albedo;
f_clip_pos = projection * f_pos;
f_clip_future_pos = (projection * future) * f_pos;
gl_Position = f_clip_pos;
UserData = vec4(0.0);
}

View File

@@ -51,6 +51,12 @@ void main()
{
// place within the chunk, 0..1 along x and z, and the paint samples around it
vec2 local = clamp(vec2(f_coord.x - param[2].x, -f_coord.y - param[2].y) * param[2].z, 0.0, 1.0);
#if TERRAIN_HOLES
// the openings cut in the ground (mat_terrain_holes.frag): the mask led between its samples, cut over the half.
// discarded at the end, the neighbours of the fragment still need it for their derivatives
float holeside = float(textureSize(holes, 0).x);
bool cut = (texture(holes, (local * (holeside - 1.0) + 0.5) / holeside).r > 0.5);
#endif
float samples = param[2].w;
vec2 paintcoord = (local * samples + 0.5) / (samples + 1.0);
vec4 w0 = texture(weights0, paintcoord);
@@ -122,6 +128,10 @@ void main()
vec3 fragcolor = ambient;
fragcolor = apply_lights(fragcolor, fragnormal, albedo, reflectivity, specularity, shadow_tone);
#if TERRAIN_HOLES
if (cut)
discard;
#endif
vec4 color = vec4(apply_fog(fragcolor), alpha_mult);
#if POSTFX_ENABLED
out_color = color;

View File

@@ -0,0 +1,9 @@
// heightmap terrain with holes cut in it, for the portals of tunnels and the like (opengl46_terrain_materials): the cells cut
// out entirely are left out of the mesh, the rest of the openings is cut here. the mask of the holes covers the chunk, 255
// where the ground is cut; the fragments where the value led between its samples is over the half are discarded
#define TERRAIN_HOLES 1
#texture (holes, 7, R)
uniform sampler2D holes;
#include <mat_terrain.frag>