75 lines
3.3 KiB
Plaintext
75 lines
3.3 KiB
Plaintext
|
|
// The mesh half of a spline height map layer - a G-buffer for a ribbon.
|
||
|
|
//
|
||
|
|
// This runs inside HeightMapMeshAtlas's SubViewport, on the ribbon mesh that
|
||
|
|
// HeightMapRibbonBuilder generates, and writes nothing but the ribbon's
|
||
|
|
// parametrization. The height itself is looked up later, in the blit, out of
|
||
|
|
// the texture atlas this buffer's UV points into - which is the whole reason
|
||
|
|
// the pass exists in this shape:
|
||
|
|
//
|
||
|
|
// - overlapping ribbons resolve by the <marked>depth test</marked>, because the
|
||
|
|
// mesh is real 3D geometry whose Z is the ribbon's own height. The highest
|
||
|
|
// fragment wins, with no blending and no accumulation pass, which is the one
|
||
|
|
// thing the blit path cannot do - there is no Max among the native blit
|
||
|
|
// blend modes.
|
||
|
|
// - a ribbon's UV comes off the mesh, so a start / middle / end texture is
|
||
|
|
// just three spans of the same strip pointing into three atlas slots. No
|
||
|
|
// branching per texel and no limit of three.
|
||
|
|
// - changing a texture does not invalidate this buffer at all, since the
|
||
|
|
// lookup is not in here.
|
||
|
|
//
|
||
|
|
// Channels, read back by heightMapShape_ribbon in HeightMapShape.gdshader:
|
||
|
|
//
|
||
|
|
// R, G - UV in the texture atlas, slot offset already baked in
|
||
|
|
// B - 0.5 + 0.5 * W, where W runs 0..1 over the <marked>whole</marked>
|
||
|
|
// spline, for effects that need to know where along the line they are
|
||
|
|
// A - 1, and not usable: an opaque spatial material cannot write alpha
|
||
|
|
//
|
||
|
|
// B carries the +0.5 bias so it also works as the stencil. The buffer is
|
||
|
|
// cleared transparent black, so B == 0 means "no ribbon here", and biasing W
|
||
|
|
// keeps every written value inside 0..1 - which an unbiased 1 + W would not,
|
||
|
|
// and a low dynamic range render target would then clamp it away.
|
||
|
|
//
|
||
|
|
// Unshaded and opaque on purpose. Opaque because a transparent material in
|
||
|
|
// Godot blends its colour by its own alpha and skips the depth write, and both
|
||
|
|
// of those would destroy a G-buffer; unshaded because ALBEDO then reaches the
|
||
|
|
// render target untouched by any lighting.
|
||
|
|
|
||
|
|
shader_type spatial;
|
||
|
|
|
||
|
|
render_mode unshaded, cull_disabled, depth_draw_opaque,
|
||
|
|
shadows_disabled, ambient_light_disabled, fog_disabled;
|
||
|
|
|
||
|
|
// The ribbon's own region in the atlas, as x, y, width, height in texels.
|
||
|
|
//
|
||
|
|
// Not a nicety: a layer's texel rect is clipped to the map, so a spline running
|
||
|
|
// off the map's edge has geometry outside the region its rect got - and the
|
||
|
|
// atlas packs other layers right next to it. Without this clip, a road leaving
|
||
|
|
// the map would overwrite the neighbouring layer's region.
|
||
|
|
//
|
||
|
|
// One material per ribbon rather than an instance uniform, because a material
|
||
|
|
// is a proven call shape here and there are only ever a handful of them.
|
||
|
|
uniform vec4 ribbon_region = vec4( 0.0, 0.0, 1.0, 1.0 );
|
||
|
|
|
||
|
|
// Atlas texel position, reconstructed from the vertex position - which is in
|
||
|
|
// atlas texels by construction, with Y negated so image row 0 ends up at the
|
||
|
|
// top of the camera's view. Interpolating it is exact, since the mapping from
|
||
|
|
// here to the render target is 1:1 and affine.
|
||
|
|
varying vec2 atlas_texel;
|
||
|
|
|
||
|
|
void vertex()
|
||
|
|
{
|
||
|
|
atlas_texel = vec2( VERTEX.x, -VERTEX.y );
|
||
|
|
}
|
||
|
|
|
||
|
|
void fragment()
|
||
|
|
{
|
||
|
|
vec2 from = atlas_texel - ribbon_region.xy;
|
||
|
|
|
||
|
|
if ( from.x < 0.0 || from.y < 0.0 || from.x > ribbon_region.z || from.y > ribbon_region.w )
|
||
|
|
{
|
||
|
|
discard;
|
||
|
|
}
|
||
|
|
|
||
|
|
ALBEDO = vec3( UV, 0.5 + 0.5 * UV2.x );
|
||
|
|
}
|