// #include "res://addons/rokojori_action_library/Runtime/Shading/Library/Wind.gdshaderinc" #include "res://addons/rokojori_action_library/Runtime/Shading/Library/Math.gdshaderinc" #include "res://addons/rokojori_action_library/Runtime/Shading/Library/Transform.gdshaderinc" /* uniform sampler2D terrainHeightMap; uniform vec2 terrainCenterXZ = vec2( 0.0, 0.0 ); uniform vec2 terrainSizeXZ = vec2( 100.0, 100.0 ); uniform float terrainMinHeight = 0.0; uniform float terrainMaxHeight = 100.0; uniform sampler2D terrainNormalMap; */ void addTerrainOffset( mat4 _MODEL_MATRIX, inout vec3 _VERTEX, sampler2D terrainHeightMap, vec2 terrainCenterXZ, vec2 terrainSizeXZ, float terrainMinHeight, float terrainMaxHeight ) { vec3 worldVertex = localToWorld( _VERTEX, _MODEL_MATRIX ); vec2 mapOffset = terrainCenterXZ - terrainSizeXZ/2.0; vec2 uv = ( worldVertex.xz - mapOffset ) / terrainSizeXZ; float height = mix( terrainMinHeight, terrainMaxHeight, texture( terrainHeightMap, uv ).r ); worldVertex.y += height; _VERTEX = worldToLocal( worldVertex, _MODEL_MATRIX ); } /** The world-space normal HeightMapGenerator baked into terrainNormalMap. Stored encoded, n * 0.5 + 0.5, so the map also works in an 8-bit format - see Procedural/HeightMaps/Shaders/HeightMapNormals.gdshader. World space, not tangent space: a heightfield over world XZ has no tangent frame to be relative to. This replaces taking a central difference per fragment. Besides being cheaper, it is the only way the smoothing can exist at all - a consumer sampling its own two neighbours has nothing to widen the stencil with, and a clipmap's triangles are far coarser than the map, so a vertex normal is not an option either. */ vec3 getTerrainNormal( sampler2D terrainNormalMap, vec3 worldPosition, vec2 terrainCenterXZ, vec2 terrainSizeXZ ) { // An unassigned sampler is Godot's 1x1 default and reads black, and // normalize( black * 2 - 1 ) is a diagonal rather than up - so a material // whose generator has no normalMapTarget would get wrong lighting instead of // flat lighting. A real normal map is never 1x1, so the size is a safe test. if ( textureSize( terrainNormalMap, 0 ).x <= 1 ) { return vec3( 0.0, 1.0, 0.0 ); } vec2 mapOffset = terrainCenterXZ - terrainSizeXZ/2.0; vec2 uv = ( worldPosition.xz - mapOffset ) / terrainSizeXZ; return normalize( texture( terrainNormalMap, uv ).rgb * 2.0 - 1.0 ); }