I am doing procedural environment generation using Houdini and Unity. There are two main shader features I want:

  1. Vertex-Color Height Blending - I currently have a shader for this which I purchased. It supports Diffuse/Normal/Metallic/Smoothness/Height/Ambient Occlusion and has the following "pragma" (I don't exactly know what this means but maybe it's useful info):

     // Physically based Standard lighting model, and enable shadows on all light types
     #pragma surface surf Standard fullforwardshadows
     #pragma target 4.0
  2. Triplanar shader - Since the environment is procedurally generated, I don't want to do manual UV mapping. I have just been using automatic UVs so far and it doesn't look that great with the Vertex-Color shader. I do have a Triplanar shader that I downloaded but it doesn't support the Vertex-Color stuff.

I would love to find a way to shader combine these two functionalities. How can I support all those types of textures?


1 Answer 1


Somewhere in your shader you'll have a series of texture samples that look something like this...

fixed4 diffuse = tex2D(_DiffuseTexture, i.uv);
fixed4 normal = tex2D(_NormalTexture, i.uv);
fixed4 metalness = tex2D(_MetalTexture, i.uv);
// ...

You can replace those with a function that does triplanar mapping. Something like...

fixed4 sampleTriplanar(sampler2D texture, float3 position, float3 weights) {
    return weights.x * tex2D(texture, position.yz)
         + weights.y * tex2D(texture, position.zx)
         + weights.z * tex2D(texture, position.xy);


fixed4 diffuse = sampleTriplanar(_DiffuseTexture, triplanarPos, triplanarWeight);
fixed4 normal  = sampleTriplanar(_NormalTexture, triplanarPos, triplanarWeight);
// ...

Here triplanarPos is your worldspace position, scaled to your desired tiling frequency.

triplanarWeight is a set of weight coefficients for each projection plane, based on the normal vector. You could calculate it something like...

// Give more value to the axes the normal is facing.
float3 triplanarWeight = normal * normal;

// Squaring this can help tighten the blending zone,
// making the result look less feathery.
triplanarWeight *= triplanarWeight;

// Dividing by the total ensures the three weights sum to 1.0.
triplanarWeight /= dot(triplanarWeight, 1.0f);

Note that this triples the amount of texture taps in your shader, so it will have a substantial performance impact. You may want to consider whether you need all those separate textures, or if you can pack some of them together to save samples at runtime.

There's also a neat trick CD Projekt RED uses, using conditional branching to save some texture taps in areas where one or two of the projections dominate. They found the branching overhead was worth the savings in texture bandwidth in their case, so that may be worth investigating if you find you need that optimization.

  • \$\begingroup\$ This was way beyond what I hoping for, thanks so much. I will give it a try soon \$\endgroup\$ Mar 24, 2021 at 1:16
  • \$\begingroup\$ This is a pretty naive version of triplanar mapping — you can find better ones in published shaders. In particular, the blending might not be ideal for normal maps, and you may want to use a different blending scheme for that purpose. \$\endgroup\$
    – DMGregory
    Mar 24, 2021 at 1:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .