mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-18 19:39:20 +02:00
The tiles moved out of the scenery's pak into an archive of their own: a sorted index of fixed-size entries found by where a tile sits in the quadtree rather than by a name, each entry compressed on its own so a tile can be read without touching the rest. A name apiece would have been seventy megabytes of index to read before the first tile could be drawn. An entry carries what its tile's ground asks for - how far it departs from the finest mesh, and how long its triangles are - because which level to draw has to be settled for tiles that are not on the card yet, and the index is the only thing about them that is read up front. Until now the figure came from the tile's level, which is the worst tile in it, so one steep corner spoke for every tile that shared its level. The mesh carries a normal per vertex, octahedron-encoded into two bytes, in place of the face normal the fragment stage worked out from how the view position changed across a triangle. A face normal makes the ground read as facets, and a coarser level's facets face differently from the ones they stand for, so the light over a hillside changed as the level did. A geometry stage hands the fragment stage the three materials of the triangle it is in and the barycentric weights, so ground changes over a triangle rather than along its edge; 3.3 has no gl_BaryCoordEXT. And the level a tile is drawn at now counts the camera's height. The distance was measured on the ground plane alone, which puts the tile underfoot nought metres away from a camera a kilometre above it - and nought metres asks for the finest level there is, so a view from the air wanted the whole ground at full detail, more tiles than the buffers hold, and what would not fit came out as holes. Archives written under the old rules are refused at the door and baked again.
105 lines
4.1 KiB
GLSL
105 lines
4.1 KiB
GLSL
// Terrain mesh, fragment stage.
|
|
//
|
|
// The texture coordinate is the world position over the material's tiling, so the ground keeps the
|
|
// scale it was authored at whatever level the tile is drawn at. Where a triangle has one material at
|
|
// every corner - which is nearly all of them - that is one texture sample; where it crosses a boundary
|
|
// the three are blended by how near the fragment is to each corner, so the ground changes over a
|
|
// triangle rather than along its edge.
|
|
//
|
|
// The normal comes from the vertex, not from how the view position changes across the triangle. A
|
|
// normal worked out here would be the triangle's own, so the ground would read as facets, and a coarser
|
|
// level's facets face differently from the ones they stand for - which shows as the light over a
|
|
// hillside changing when the level does.
|
|
|
|
in vec4 f_pos;
|
|
in vec3 f_normal;
|
|
in vec2 f_ground;
|
|
in vec3 f_weights;
|
|
flat in uvec3 f_materials;
|
|
|
|
#include <common>
|
|
#include <apply_fog.glsl>
|
|
#include <tonemapping.glsl>
|
|
|
|
uniform sampler2DArray ground;
|
|
// per material: rgb the colour to show while it has no texture, a the metres one texture repeat
|
|
// covers - negative while there is no texture layer for it yet
|
|
uniform sampler2D materialtable;
|
|
|
|
layout(location = 0) out vec4 out_color;
|
|
#if MOTIONBLUR_ENABLED
|
|
layout(location = 1) out vec4 out_motion;
|
|
#endif
|
|
|
|
// Value noise from the world position: the same everywhere the ground is, at any level, and costing
|
|
// nothing to store. Two octaves are enough for what it is for.
|
|
float ground_hash( vec2 Cell )
|
|
{
|
|
return fract( sin( dot( Cell, vec2( 127.1, 311.7 ) ) ) * 43758.5453 );
|
|
}
|
|
|
|
float ground_noise( vec2 Where )
|
|
{
|
|
vec2 cell = floor( Where );
|
|
vec2 part = fract( Where );
|
|
part = part * part * ( 3.0 - 2.0 * part );
|
|
return mix(
|
|
mix( ground_hash( cell ), ground_hash( cell + vec2( 1.0, 0.0 ) ), part.x ),
|
|
mix( ground_hash( cell + vec2( 0.0, 1.0 ) ), ground_hash( cell + vec2( 1.0, 1.0 ) ), part.x ),
|
|
part.y );
|
|
}
|
|
|
|
vec3 material_colour( uint Material )
|
|
{
|
|
vec4 entry = texelFetch( materialtable, ivec2( int( Material ), 0 ), 0 );
|
|
if( entry.a < 0.0 ) { return entry.rgb; }
|
|
return texture( ground, vec3( f_ground / max( entry.a, 0.01 ), float( Material ) ) ).rgb;
|
|
}
|
|
|
|
void main()
|
|
{
|
|
vec3 albedo;
|
|
if( ( f_materials.x == f_materials.y ) && ( f_materials.x == f_materials.z ) ) {
|
|
// the common case: one kind of ground across the whole triangle, one texture sample
|
|
albedo = material_colour( f_materials.x );
|
|
}
|
|
else {
|
|
// The weights say how near the fragment is to each corner, so left alone the ground would fade
|
|
// along a straight line across the triangle and the mesh would be visible in the texturing.
|
|
// Disturbing them with noise taken from the world position makes the two grounds interlock
|
|
// instead, which is what a boundary between them looks like - and since the noise is a function
|
|
// of where the fragment is, it does not change with the level the tile is drawn at.
|
|
float grain = ground_noise( f_ground * 0.6 ) * 0.62
|
|
+ ground_noise( f_ground * 2.9 ) * 0.28
|
|
+ ground_noise( f_ground * 11.0 ) * 0.10;
|
|
vec3 weights = max( f_weights + ( grain - 0.5 ) * 0.55, vec3( 0.0 ) );
|
|
// and sharpened a little, so the interlocking reads as one ground over the other rather than as
|
|
// a wash of both
|
|
weights = weights * weights;
|
|
weights /= max( weights.x + weights.y + weights.z, 1e-4 );
|
|
albedo = weights.x * material_colour( f_materials.x )
|
|
+ weights.y * material_colour( f_materials.y )
|
|
+ weights.z * material_colour( f_materials.z );
|
|
}
|
|
|
|
vec3 normal = normalize( f_normal );
|
|
// a wall is drawn from either side, so the normal follows whichever way it is seen from
|
|
if( dot( normal, -f_pos.xyz ) < 0.0 ) { normal = -normal; }
|
|
|
|
// a plain lambert term against the first directional light, so that relief reads
|
|
float lambert = 0.35;
|
|
if( lights_count >= 1U && lights[ 0 ].type == LIGHT_DIR ) {
|
|
lambert = 0.35 + 0.65 * max( dot( normal, normalize( -lights[ 0 ].dir ) ), 0.0 );
|
|
}
|
|
vec3 col = albedo * lambert;
|
|
|
|
#if POSTFX_ENABLED
|
|
out_color = vec4( apply_fog( col ), 1.0 );
|
|
#else
|
|
out_color = tonemap( vec4( apply_fog( col ), 1.0 ) );
|
|
#endif
|
|
#if MOTIONBLUR_ENABLED
|
|
out_motion = vec4( 0.0 );
|
|
#endif
|
|
}
|