rokojori_action_library/Runtime/Procedural/HeightMaps/Shaders/HeightMapShape.gdshader

99 lines
3.2 KiB
Plaintext

// Content pass of a HeightMapGeneratorLayer's shape.
//
// Writes one procedural shape's height contribution into the compositor's
// scratch buffer with blending disabled. The scratch buffer is then blended onto
// the height map by a plain BlitMaterial, which is where Add / Subtract / Replace
// actually happen - so this shader never reads the map and no ping-pong buffer is
// involved.
//
// Height lives in the R channel (G and B carry the same value so the map is
// readable as a greyscale preview), matching addTerrainOffset in
// Shading/Library/Terrain.gdshaderinc, which samples .r and maps 0..1 onto
// terrainMinHeight..terrainMaxHeight.
//
// Everything is evaluated in world XZ, not UV: HeightMapGenerator hands in the
// world-space rect this blit covers, so a shape that the map's edge clipped is
// still drawn at its true position and size. ( The TextureStack had a bug here
// once - clipping the rect while keeping rect-local UV silently rescaled the
// content. Deriving world space from the clipped rect cannot have that problem. )
shader_type texture_blit;
render_mode blend_disabled;
// World-space XZ rect this blit covers, from the (already clipped) pixel rect.
uniform vec2 shape_rect_world_min = vec2( 0.0, 0.0 );
uniform vec2 shape_rect_world_size = vec2( 1.0, 1.0 );
// HeightMapShapeType. Only Circle exists so far; the switch is the extension
// point for the primitives that follow.
uniform int shape_type = 0;
// HeightMapBlendMode - 0 Add, 1 Subtract, 2 Replace.
uniform int shape_blend_mode = 0;
// Normalized 0..1 height, already converted by the generator: a delta of the
// min-to-max range for Add/Subtract, an absolute level for Replace.
uniform float shape_value = 0.0;
uniform vec2 shape_center = vec2( 0.0, 0.0 );
uniform float shape_inner_radius = 0.0;
uniform float shape_outer_radius = 1.0;
uniform bool shape_smooth_falloff = true;
/** 1 inside the inner radius, falling to 0 at the outer radius. */
float heightMapShape_circle( vec2 worldXZ )
{
float distance = length( worldXZ - shape_center );
float inner = min( shape_inner_radius, shape_outer_radius );
float outer = max( shape_inner_radius, shape_outer_radius );
// A zero-width band is a hard edge, not a division by zero.
if ( outer <= inner )
{
return distance <= outer ? 1.0 : 0.0;
}
float t = clamp( ( distance - inner ) / ( outer - inner ), 0.0, 1.0 );
float weight = 1.0 - t;
if ( shape_smooth_falloff )
{
weight = weight * weight * ( 3.0 - 2.0 * weight );
}
return weight;
}
float heightMapShape_weight( vec2 worldXZ )
{
switch ( shape_type )
{
case 0: return heightMapShape_circle( worldXZ );
}
return 0.0;
}
void blit()
{
vec2 worldXZ = shape_rect_world_min + UV * shape_rect_world_size;
float weight = heightMapShape_weight( worldXZ );
if ( shape_blend_mode == 2 )
{
// Replace: the height is flat and the falloff rides on alpha, so the Mix
// blend crossfades from the layer's height back to what is underneath.
COLOR0 = vec4( vec3( shape_value ), weight );
}
else
{
// Add / Subtract: alpha has to be 1, because the hardware blend multiplies
// the source by its own alpha before summing. The falloff is applied here
// instead.
COLOR0 = vec4( vec3( shape_value * weight ), 1.0 );
}
}