// 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 depth test, 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 whole // 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 ); }