// 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 ); } }