How to make a Triplanar Shader in Godot

Here's how to make a simple triplanar shader that blends textures based on the surface angle (normal). You can find the full code at the end of this article.
Initialization
To make texture mapping UV-independent we needed to find some sort of alternative coordinates space. We've decided to use world-space vertex coordinates, initialized like so:
uniform vec3 _triplanar_pos;
void vertex()
{
// initialize the world space sampling coordinate by
// rotating, scaling and offsetting the vertex position.
_triplanar_pos = rotate(triplanar_angle) * VERTEX;
_triplanar_pos *= triplanar_scale;
_triplanar_pos += triplanar_offset;
//...
}
Snippet pseudo code
Considering that triplanar mapping projects textures along three axes, to avoid over-saturation we also calculate some blending weights as shown:
uniform vec3 _triplanar_weight;
void vertex()
{
//...
// compute the weights that will be used to get the correct
// final color after sampling the texture
vec3 _triplanar_weight = pow(abs(NORMAL), vec3(triplanar_sharpness));
_triplanar_weight /= dot(result, vec3(1.0));
}
Snippet pseudo code
Texture sampling
Texture mapping uses only two coordinates (u and v) but the _triplanar_pos is a vec3! However, if we use only two components per time, we are actually projecting the texture on a plane aligned with each axes (up, right and forward).
void fragment()
{
vec4 color = vec4(0.0);
// sample the texture three times, one for each axes in world space,
// and multiply each axes with the relative weight
color += texture(sampler, _triplanar_pos.xy) * _triplanar_weight.z;
color += texture(sampler, _triplanar_pos.xz) * _triplanar_weight.y;
color += texture(sampler, _triplanar_pos.zy * vec2(-1.0, 1.0)) * _triplanar_weight.x;
color *= triplanar_strength;
//...
}
Mixing
Finally, we can transition between two different textures based on the surface normal like so:
void fragment()
{
//...
// calulate the angle between up-vector and surface normal
float transition = dot(vec3(0.0, 1.0, 0.0), normal);
// calculate interpolation factor
float threshold = cos(degToRad(_Angle));
float interpolation = 1.0 - smoothstep(
threshold - _Transition,
threshold + _Transition,
transition);
// transition between two textures
vec3 color = mix(first_triplanar_texture.rgb,
second_triplanar_texture.rgb,
interpolation);
ALBEDO = color;
}
Putting it all together
The rest of this post is available for free!
Please create an account to continue reading! It helps us knowing that humans are reading us, and also helps us staying independent and keep posting what we love. Thanks! ❤️
✦ Create a Free Account & Continue ReadingMore Posts
Text Animator for Godot is Out in Early Access!
Text Animator for Godot is out now in Early Access! Start bringing your texts to life in a few clicks and help us shape the future of this tool in Godot! Cheers!
Text Animator is coming to Godot, Unity and Unreal.. with a fresh new look!
Text Animator is coming to Godot, Unreal and Unity UI Toolkit with a fresh new look! Read more.
I'll be doing a workshop at DevGAMM Lisbon this year!
I'll be doing a workshop at DevGAMM Lisbon this year! Designing tools from start to finish and prototyping with Godot, docs and much more. Looking forward to it!
Creating custom inspectors in Godot C# more easily
Creating custom inspectors in Godot can become a bit tedious in C#, so we have built a custom and free addon that handles this for you!
LinkedIn will use your data to train thier AI
Starting November 3rd, 2025, LinkedIn will start using your data to train their AI. Ughh.
The Best Gamescom so far! August 2025
We just attended Gamescom (and devcom) this year and they were the best one for us so far!! Here's a short update about the general mood and more thoughts.