mesh-terrain blending

A basic mesh-terrain blending with triplanar mapping. Works the best on flat terrains. Works for Godot 4.0 as well.


How to use:

– add a flat terrain with triplanar mapping enabled for proper blending with the ground texture

– add the shader to the mesh you want to blend with the ground

– add to the main_”textures” the mesh texture and to the blend_”textures” the ground texture

– add to “transition_mask” a noise texture or any mask texture

Shader code
shader_type spatial;

uniform vec3 n = vec3(0.0, 1.0, 0.0);

uniform float triplanar_scale = 1.0;
uniform float blend_size = 1.0;
uniform float blend_offset = 0.0;

uniform sampler2D transition_mask;

uniform sampler2D main_albedo : hint_albedo;
uniform sampler2D main_normal : hint_normal;
uniform sampler2D main_roughness : hint_white;
uniform sampler2D main_ao: hint_white;

uniform sampler2D blend_albedo : hint_albedo;
uniform sampler2D blend_normal : hint_normal;
uniform sampler2D blend_roughness : hint_white;
uniform sampler2D blend_ao: hint_white;

varying vec3 uv_triplanar_pos;
varying vec3 uv_power_normal;

void vertex(){
	TANGENT = vec3(0.0,0.0,-1.0) * abs(NORMAL.x);
	TANGENT+= vec3(1.0,0.0,0.0) * abs(NORMAL.y);
	TANGENT+= vec3(1.0,0.0,0.0) * abs(NORMAL.z);
	TANGENT = normalize(TANGENT);
	BINORMAL = vec3(0.0,1.0,0.0) * abs(NORMAL.x);
	BINORMAL+= vec3(0.0,0.0,-1.0) * abs(NORMAL.y);
	BINORMAL+= vec3(0.0,1.0,0.0) * abs(NORMAL.z);
	BINORMAL = normalize(BINORMAL);
	uv_power_normal = pow(abs(mat3(WORLD_MATRIX) * NORMAL), vec3(1.0));
	uv_triplanar_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0f)).xyz * triplanar_scale;
	uv_power_normal /= dot(uv_power_normal, vec3(1.0));
	uv_triplanar_pos *= vec3(1.0, -1.0, 1.0);

vec4 triplanar_texture(sampler2D p_sampler,vec3 p_triplanar_pos, vec3 p_weights) {
	vec4 samp = vec4(0.0);
	samp+= texture(p_sampler,p_triplanar_pos.xy) * p_weights.z;
	samp+= texture(p_sampler,p_triplanar_pos.xz) * p_weights.y;
	samp+= texture(p_sampler,p_triplanar_pos.zy * vec2(-1.0,1.0)) * p_weights.x;
	return samp;

float overlay(float a, float b){
	if(a < 0.5){
		return a * b * 2.0;
		return 1.0- 2.0 * (1.0 - a)*(1.0 - b);

void fragment(){
	vec3 p = (CAMERA_MATRIX * vec4(VERTEX, 1.0)).xyz;
	float dist = dot(p, n);
	dist = max(min((dist + blend_offset) * blend_size - 0.5, 1.0), 0.0);
	vec4 m_col = texture(main_albedo, UV);
	vec3 m_norm = texture(main_normal, UV).rgb;
	float m_rough = texture(main_roughness, UV).r;
	float m_ao = texture(main_ao, UV).r;
	vec4 b_col = triplanar_texture(blend_albedo, uv_triplanar_pos, uv_power_normal);
	vec3 b_norm = triplanar_texture(blend_normal, uv_triplanar_pos, uv_power_normal).rgb;
	float b_rough = triplanar_texture(blend_roughness, uv_triplanar_pos, uv_power_normal).r;
	float b_ao = triplanar_texture(blend_ao, uv_triplanar_pos, uv_power_normal).r;
	float blend_mask = texture(transition_mask, UV).r;
	float blend = overlay(dist, blend_mask);
	ALBEDO = mix(b_col.rgb, m_col.rgb, blend);
	NORMALMAP = mix(b_norm, m_norm, blend);
	ROUGHNESS = mix(b_rough, m_rough, blend);
	AO = mix(b_ao, m_ao, blend);
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from kmitt91

Stylized Wings

Stylized Water Shader

Related shaders

Terrain Mesh Blending with Dithering

Mesh Blending with Alpha

Cheap terrain with fake normal map or POM


1 Comment
Newest Most Voted
Inline Feedbacks
View all comments
1 month ago

Thanks for the shader. Really helpful for beginners like me