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:
41
shaders/grass.frag
Normal file
41
shaders/grass.frag
Normal 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
206
shaders/grass.vert
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
9
shaders/mat_terrain_holes.frag
Normal file
9
shaders/mat_terrain_holes.frag
Normal 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>
|
||||
Reference in New Issue
Block a user