Terrain Tree Billboard have bright outline against dark background

This is a question I have the answer for, and it’s more fitting to Unity Answers than the forums.

When placing trees on the terrain using the terrain engine, the trees, when becoming billboards, have a bright ‘glow’ outline to them.

This is something that bothered my team for a long time, and we haven’t found any information about it online. To illustrate the problem:

tree outline problem and solution

We figured out the problem was with the alpha-blending part of the shader, and it was extremely easy to fix. We just downloaded the built-in terrain billboard shader and added the following line of code: AlphaTest Greater 0.9 after the blending mode definition Blend SrcAlpha OneMinusSrcAlpha

You can find the Fixed shader here: Just copy and paste this into a shader file and you’re done.

I just ran into this problem when switching to linear lighting in unity 5.

The above solution didn’t solve it, after much digging it turns out the background color of the render texture unity uses for tree billboards isn’t black (it’s (0.025,0.025,0.025)).

In gamma space this doesn’t present itself as a problem. However in linear space, this is converted to gamma space at the end of the render pipeline bringing it to a much more visible grey (0.19,0.19,0.19)

To solve this I modified the TreeCreatorLeavesRendertex.shader to convert the billboard render texture to an approximation of gamma color space with

c.rgb = sqrt(c.rgb);
c.rgb = max(c.rgb,0.025); //Ensure our tree is never rendered darker than the background color

towards the end of the fragment shader

Then do the opposite operation in the BillboardTree.shader

col.rgb = col.rgb * col.rgb;

Not a perfect solution, but will work for us until speedtree stops performing so poorly

[UPDATED 22.03.17] I’ve used this shader, plus:

  1. TOD_TreeBillboardState. Means need to Colorize or not (Night or Day), set through Shader.SetGlobalFloat

  2. TOD_TreeBillboardColor. Which Color to use for fill. Uses white-to-black Gradient, based on Night-time. Set through Shader.SetGlobalColor

Gradient t instructions:

if (Cycle.Hour > 17f)
       t = Mathf.Lerp(0f, .5f, (Cycle.Hour - 17f) / 7f);
else
       t = Mathf.Lerp(.5f, 1f, Cycle.Hour / 7f);

!All is Updating every frame, with day-night-cycle script. No performance problems.

Shader "Hidden/TerrainEngine/BillboardTree" {
	Properties {
		_MainTex ("Base (RGB) Alpha (A)", 2D) = "white" {}
	}
	
	SubShader {
		Tags { "Queue" = "Transparent-1" "IgnoreProjector"="True" "RenderType"="TransparentCutout" }
		Pass {
			Tags { "RequireOption" = "SoftVegetation" }

			// Dont write to the depth buffer
			ZWrite off
		
			// Set up alpha blending
			Blend SrcAlpha OneMinusSrcAlpha
			Cull Front
			CGPROGRAM
			#pragma vertex vert 
			#pragma fragment frag
			#pragma multi_compile_fog
			#include "UnityCG.cginc"
			#include "TerrainEngine.cginc"

            Float TOD_TreeBillboardState;
            fixed4 TOD_TreeBillboardColor;

			struct v2f {
				float4 pos : SV_POSITION;
				fixed4 color : COLOR0;
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
			};

			v2f vert (appdata_tree_billboard v) {
				v2f o;
				TerrainBillboardTree(v.vertex, v.texcoord1.xy, v.texcoord.y);	
				o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
				o.uv.x = v.texcoord.x;
				o.uv.y = v.texcoord.y > 0;
				o.color = v.color;
				UNITY_TRANSFER_FOG(o,o.pos);
				return o;
			}

			sampler2D _MainTex;
			fixed4 frag(v2f input) : SV_Target
			{
				fixed4 col = tex2D( _MainTex, input.uv);
                if (TOD_TreeBillboardState > .5)
                    if (col.r < .18 && col.a < .9)
                        col.rgb *= TOD_TreeBillboardColor.rgb;
				col.rgb *= input.color.rgb;
				clip(col.a);
				UNITY_APPLY_FOG(input.fogCoord, col);
				return col;
			}
			ENDCG			
		}
		Pass {
			Cull Front
			CGPROGRAM
			#pragma vertex vert 
			#pragma fragment frag
			#pragma multi_compile_fog
			#include "UnityCG.cginc"
			#include "TerrainEngine.cginc"

			struct v2f {
				float4 pos : SV_POSITION;
				fixed4 color : COLOR0;
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
			};

			v2f vert (appdata_tree_billboard v) {
				v2f o;
				TerrainBillboardTree(v.vertex, v.texcoord1.xy, v.texcoord.y);	
				o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
				o.uv.x = v.texcoord.x;
				o.uv.y = v.texcoord.y > 0;
				o.color = v.color;
				UNITY_TRANSFER_FOG(o,o.pos);
				return o;
			}

			sampler2D _MainTex;
			fixed4 frag(v2f input) : SV_Target
			{
				fixed4 col = tex2D( _MainTex, input.uv);
				col.rgb *= input.color.rgb;
				clip(col.a - .99);
				UNITY_APPLY_FOG(input.fogCoord, col);
				return col;
			}
			ENDCG			
		}
	}
	Fallback Off
}

Another pretty simple solution for that is changing the alpha cutout directly in the shader. Simply go to “clip(col.a)” and add -0.9 to it. This will increase the cutout and removes the grey outline.

An easy way to fix this is to go to terrain settings and change the billboard to about halfway, and if it still appears, put it up all the way.

After hours of searching and having no clue how to write shaders myself I managed to find a fix.

The original solution posted by the topic creator did two things wrong for me (2018.4.1f1):

  • Using their modified billboard code
    the trees still had a white outline

  • Fog was not being applied to the
    billboard trees

So to fix the issue with the outline I implemented TimNick151297’s advice, adding -0.9 to “clip(col.a)”.

To fix the fact that fog is not being applied I took the code for Unity’s default billboardtree shader which I found online here: Unity-Built-in-Shaders/DefaultResourcesExtra/TerrainShaders/Trees/BillboardTree.shader at master · TwoTailsGames/Unity-Built-in-Shaders · GitHub

and merged it with the code provided above (along with the outline fix).

I appreciate this is a pretty old topic but it’s one of the first results on google when searching this issue, figured it would save someone out there the headache I had trying to fix this today.

To implement this fixed shader, simply go to any of your project folders and Right Click - Create - Shader - Standard Surface Shader. Name the shader BillboardTree. Unity will automatically pick this up and use it instead of the default shader it’s currently using.

Here’s the fixed code, working perfectly in 2018.4.1f1:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Hidden/TerrainEngine/BillboardTree" {
	Properties {
		_MainTex ("Base (RGB) Alpha (A)", 2D) = "white" {}
	}
	
	SubShader {
		Tags { "Queue" = "Transparent-100" "IgnoreProjector"="True" "RenderType"="TreeBillboard" }
		
		Pass {
			ColorMask rgb
			Blend SrcAlpha OneMinusSrcAlpha
			AlphaTest Greater 0.9
			ZWrite Off Cull Off
			
			CGPROGRAM
			#pragma vertex vert
			#include "UnityCG.cginc"
			#include "TerrainEngine.cginc"
			#pragma fragment frag
			#pragma multi_compile_fog

			struct v2f {
				float4 pos : POSITION;
				fixed4 color : COLOR0;
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
			};

			v2f vert (appdata_tree_billboard v) {
				v2f o;
				TerrainBillboardTree(v.vertex, v.texcoord1.xy, v.texcoord.y);	
				o.pos = UnityObjectToClipPos (v.vertex);
				o.uv.x = v.texcoord.x;
				o.uv.y = v.texcoord.y > 0;
				o.color = v.color;
				UNITY_TRANSFER_FOG(o,o.pos);
				return o;
			}

			sampler2D _MainTex;
			fixed4 frag(v2f input) : COLOR
			{
				fixed4 col = tex2D( _MainTex, input.uv);
				col.rgb *= input.color.rgb;
				clip(col.a-0.9);
				UNITY_APPLY_FOG(input.fogCoord, col);
				return col;
			}
			ENDCG			
		}
	}

	SubShader {
		Tags { "Queue" = "Transparent-100" "IgnoreProjector"="True" "RenderType"="TreeBillboard" }
		
		Pass {

			CGPROGRAM
			#pragma vertex vert
			#pragma exclude_renderers shaderonly
			#include "UnityCG.cginc"
			#include "TerrainEngine.cginc"

			struct v2f {
				float4 pos : POSITION;
				fixed4 color : COLOR0;
				float2 uv : TEXCOORD0;
			};

			v2f vert (appdata_tree_billboard v) {
				v2f o;
				TerrainBillboardTree(v.vertex, v.texcoord1.xy, v.texcoord.y);	
				o.pos = UnityObjectToClipPos (v.vertex);
				o.uv.x = v.texcoord.x;
				o.uv.y = v.texcoord.y > 0;
				o.color = v.color;
				return o;
			}
			ENDCG			

			ColorMask rgb
			Blend SrcAlpha OneMinusSrcAlpha
			
			ZWrite Off Cull Off
			
			AlphaTest Greater 0.9
			SetTexture [_MainTex] { combine texture * primary, texture }
		}
	}
	
	Fallback Off
}