shader_type spatial; // Texture Inputs uniform sampler2D flat_albedo : source_color, filter_linear_mipmap_anisotropic; // e.g., Grass uniform sampler2D flat_normal : hint_normal, filter_linear_mipmap_anisotropic; uniform sampler2D steep_albedo : source_color, filter_linear_mipmap_anisotropic; // e.g., Cliff/Rock uniform sampler2D steep_normal : hint_normal, filter_linear_mipmap_anisotropic; // Shader Control Knobs uniform float texture_scale = 0.05; uniform float slope_threshold : hint_range(0.0, 1.0) = 0.7; uniform float slope_blend : hint_range(0.01, 0.5) = 0.1; // Triplanar helper function to prevent stretching on procedural cliffs vec4 triplanar_sample(sampler2D tex, vec3 world_pos, vec3 normal) { vec3 blending = abs(normal); blending = normalize(max(blending, 0.00001)); // Avoid division by zero blending /= (blending.x + blending.y + blending.z); vec4 x_tex = texture(tex, world_pos.zy * texture_scale); vec4 y_tex = texture(tex, world_pos.xz * texture_scale); vec4 z_tex = texture(tex, world_pos.xy * texture_scale); return x_tex * blending.x + y_tex * blending.y + z_tex * blending.z; } varying vec3 world_position; varying vec3 world_normal; void vertex() { // Grab real-world coordinates from Terrainy's procedural vertices world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; world_normal = MODEL_NORMAL_MATRIX * NORMAL; } void fragment() { // Normalize vectors for accurate angle calculations vec3 normal = normalize(world_normal); // Sample textures using triplanar projection mapping vec4 grass_c = triplanar_sample(flat_albedo, world_position, normal); vec4 grass_n = triplanar_sample(flat_normal, world_position, normal); vec4 rock_c = triplanar_sample(steep_albedo, world_position, normal); vec4 rock_n = triplanar_sample(steep_normal, world_position, normal); // Calculate slope: dot product against the world UP vector (0, 1, 0) float slope = dot(normal, vec3(0.0, 1.0, 0.0)); // Create a smooth mask transitioning between flat areas and cliffs float weight = smoothstep(slope_threshold - slope_blend, slope_threshold + slope_blend, slope); // Blend the textures together based on the procedural terrain angles ALBEDO = mix(rock_c.rgb, grass_c.rgb, weight); NORMAL_MAP = mix(rock_n.rgb, grass_n.rgb, weight); ROUGHNESS = 0.8; // Uniform roughness default }