mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-31 09:59:06 +01:00
Give the terrain its own archive, its normals, and a level per tile
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.
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
// Terrain mesh, fragment stage.
|
||||
//
|
||||
// One triangle carries one material: the cook only welds vertices that share it, so nothing has to
|
||||
// be blended here and a material boundary follows the edges the scenery drew it with. 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.
|
||||
// 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 mesh carries no normals - that would be four more bytes on every vertex - so the surface
|
||||
// normal comes from how the view position changes across the triangle, which is its face normal.
|
||||
// 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;
|
||||
flat in uint f_material;
|
||||
in vec3 f_weights;
|
||||
flat in uvec3 f_materials;
|
||||
|
||||
#include <common>
|
||||
#include <apply_fog.glsl>
|
||||
@@ -26,16 +31,59 @@ layout(location = 0) out vec4 out_color;
|
||||
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()
|
||||
{
|
||||
vec4 entry = texelFetch( materialtable, ivec2( int( f_material ), 0 ), 0 );
|
||||
vec3 albedo =
|
||||
( entry.a < 0.0 )
|
||||
? entry.rgb
|
||||
: texture( ground, vec3( f_ground / max( entry.a, 0.01 ), float( f_material ) ) ).rgb;
|
||||
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( cross( dFdx( f_pos.xyz ), dFdy( f_pos.xyz ) ) );
|
||||
// towards the camera, whichever way the derivatives came out
|
||||
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
|
||||
|
||||
42
shaders/terrainmesh.geom
Normal file
42
shaders/terrainmesh.geom
Normal file
@@ -0,0 +1,42 @@
|
||||
// Terrain mesh, geometry stage.
|
||||
//
|
||||
// It is here only to tell the fragment stage which three materials the triangle it is in has at its
|
||||
// corners, and how near that fragment is to each of them. A vertex carries one material; a triangle
|
||||
// crossing a boundary between two kinds of ground has different ones at its corners, and what makes
|
||||
// that read as a transition rather than as a line along the triangle's edge is blending the three by
|
||||
// their barycentric weight.
|
||||
//
|
||||
// Nothing else needs a whole stage: the weights cannot be worked out in the fragment stage on its own,
|
||||
// since an indexed mesh shares a vertex between triangles and a vertex cannot say which corner of which
|
||||
// triangle it is. Desktop GL 4.6 has gl_BaryCoordEXT for exactly this; 3.3 does not.
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(triangles) in;
|
||||
layout(triangle_strip, max_vertices = 3) out;
|
||||
|
||||
in vec4 v_pos[];
|
||||
in vec3 v_normal[];
|
||||
in vec2 v_ground[];
|
||||
in uint v_material[];
|
||||
|
||||
out vec4 f_pos;
|
||||
out vec3 f_normal;
|
||||
out vec2 f_ground;
|
||||
out vec3 f_weights;
|
||||
flat out uvec3 f_materials;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec3 materials = uvec3( v_material[ 0 ], v_material[ 1 ], v_material[ 2 ] );
|
||||
for( int corner = 0; corner < 3; ++corner ) {
|
||||
f_pos = v_pos[ corner ];
|
||||
f_normal = v_normal[ corner ];
|
||||
f_ground = v_ground[ corner ];
|
||||
f_weights = vec3( corner == 0 ? 1.0 : 0.0, corner == 1 ? 1.0 : 0.0, corner == 2 ? 1.0 : 0.0 );
|
||||
f_materials = materials;
|
||||
gl_Position = gl_in[ corner ].gl_Position;
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
@@ -1,32 +1,52 @@
|
||||
// Terrain mesh, vertex stage.
|
||||
//
|
||||
// A vertex is a position and the ground material it belongs to, nothing else. The position is
|
||||
// measured from the middle of the terrain rather than from the scenery's origin, so that the whole
|
||||
// of a map fits in floats and every resident tile can live in one vertex buffer - which is what
|
||||
// lets a frame of terrain be one draw call. terrainorigin is where that middle is relative to the
|
||||
// camera, terrainworld where it is in the world.
|
||||
// A vertex is a position, the ground material it belongs to, and the ground's normal there, folded onto
|
||||
// an octahedron and kept in two bytes. The position is measured from the middle of the terrain rather
|
||||
// than from the scenery's origin, so that the whole of a map fits in floats and every resident tile can
|
||||
// live in one vertex buffer - which is what lets a frame of terrain be one draw call. terrainorigin is
|
||||
// where that middle is relative to the camera, terrainworld where it is in the world.
|
||||
//
|
||||
// There are no texture coordinates: ground textures tile in world space, so the coordinate follows
|
||||
// from where the vertex is and from how many metres one repeat of its material covers, which the
|
||||
// cook measured from the scenery and left in the material table.
|
||||
// There are no texture coordinates: ground textures tile in world space, so the coordinate follows from
|
||||
// where the vertex is and from how many metres one repeat of its material covers, which the cook
|
||||
// measured from the scenery and left in the material table.
|
||||
//
|
||||
// Everything arrives as a float rather than as an integer. The engine's vertex array wrapper sets
|
||||
// attributes up that way, and going around it would leave what the engine thinks is bound out of step
|
||||
// with what is.
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(location = 0) in vec3 in_position;
|
||||
layout(location = 1) in uint in_material;
|
||||
layout(location = 1) in float in_material;
|
||||
layout(location = 2) in vec2 in_normal; // octahedron-encoded, 0..255 each
|
||||
|
||||
uniform vec3 terrainorigin; // the terrain's middle, relative to the camera
|
||||
uniform vec2 terrainworld; // the terrain's middle, in the world
|
||||
|
||||
out vec4 f_pos;
|
||||
out vec2 f_ground; // world position on the ground, for texturing
|
||||
flat out uint f_material;
|
||||
out vec4 v_pos;
|
||||
out vec3 v_normal;
|
||||
out vec2 v_ground; // world position on the ground, for texturing
|
||||
out uint v_material;
|
||||
|
||||
// the square unfolded back onto the sphere. y is up, so it is y whose sign the fold carries
|
||||
vec3 unfold_normal( vec2 Encoded )
|
||||
{
|
||||
vec2 folded = Encoded / 255.0 * 2.0 - 1.0;
|
||||
vec3 normal = vec3( folded.x, 1.0 - abs( folded.x ) - abs( folded.y ), folded.y );
|
||||
if( normal.y < 0.0 ) {
|
||||
float wasx = normal.x;
|
||||
normal.x = ( 1.0 - abs( normal.z ) ) * ( wasx >= 0.0 ? 1.0 : -1.0 );
|
||||
normal.z = ( 1.0 - abs( wasx ) ) * ( normal.z >= 0.0 ? 1.0 : -1.0 );
|
||||
}
|
||||
return normalize( normal );
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 local = in_position + terrainorigin;
|
||||
f_ground = terrainworld + in_position.xz;
|
||||
f_material = in_material;
|
||||
f_pos = modelview * vec4( local, 1.0 );
|
||||
gl_Position = projection * f_pos;
|
||||
v_ground = terrainworld + in_position.xz;
|
||||
v_material = uint( in_material + 0.5 );
|
||||
v_normal = normalize( modelviewnormal * unfold_normal( in_normal ) );
|
||||
v_pos = modelview * vec4( local, 1.0 );
|
||||
gl_Position = projection * v_pos;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user