Hello! I have a terrain with a lot of trees in URP, Unity 2022.3.10f1. As the soft occlusion shader doesn’t work in URP, I got a fixed version of it here by zeroyao. This works for the trees, but their billboards were black, so I modified the builtin billboard shader and it looks great now, however I would like to add shadows to it, because there’s still a fairly obvious seam between trees and their billboards. The fade length of the terrain helps but the shadows still pop in noticeably even at distance. The “faded” meshes have no shadow either interestingly and I could not find where this fading is calculated or what it even does.
Why are shadows disappearing on the faded meshes and why can’t I get shadows to show up for the billboards? I am aware that the shadow caster would need to rotate to the direction light for consistent shadows, that would a piece of cake if shadows could show in the first place as I already use rotation for the tree shader itself. Also what performance implications would this have? I imagine it’s better than increasing mesh tree distance, right?
Nothing relevant was changed in TerrainEngine and UnityRandomRotation is just a RotateAroundYInDegrees.
BillboardTree.shader
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
Shader "Hidden/TerrainEngine/BillboardTree" {
Properties{
_MainTex("Base (RGB) Alpha (A)", 2D) = "white" {}
}
SubShader{
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "TreeBillboard" }
Pass {
ColorMask rgb
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
#include "TerrainEngineCustom.cginc"
struct v2f {
float4 pos : SV_POSITION;
fixed4 color : COLOR0;
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert(appdata_tree_billboard v) {
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(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) : SV_Target
{
fixed4 col = tex2D(_MainTex, input.uv);
col.rgb *= input.color.rgb;
clip(col.a);
UNITY_APPLY_FOG(input.fogCoord, col);
return col;
}
ENDCG
}
}
Fallback Off
}
TreeSoftOcclusionBark.shader (it works more like the leaves shader and is used for the whole tree)
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Nature/Tree Soft Occlusion Bark URP" {
Properties{
_Color("Main Color", Color) = (1,1,1,1)
_MainTex("Main Texture", 2D) = "white" {}
_Cutoff("Alpha cutoff", Range(0.25,0.9)) = 0.5
_BaseLight("Base Light", Range(0, 1)) = 0.35
_AO("Amb. Occlusion", Range(0, 10)) = 2.4
_Occlusion("Dir Occlusion", Range(0, 20)) = 7.5
// These are here only to provide default values
[HideInInspector] _TreeInstanceColor("TreeInstanceColor", Vector) = (1,1,1,1)
[HideInInspector] _TreeInstanceScale("TreeInstanceScale", Vector) = (1,1,1,1)
[HideInInspector] _SquashAmount("Squash", Float) = 1
}
SubShader{
Tags {
"Queue" = "AlphaTest"
"IgnoreProjector" = "True"
"RenderType" = "TreeTransparentCutout"
"DisableBatching" = "True"
}
Cull Off
ColorMask RGB
Pass {
CGPROGRAM
#pragma vertex bark
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityBuiltin2xTreeLibraryCustom.cginc"
sampler2D _MainTex;
fixed _Cutoff;
fixed4 frag(v2f input) : SV_Target
{
fixed4 col = tex2D(_MainTex, input.uv.xy);
col *= input.color;
clip(col.a - _Cutoff);
UNITY_APPLY_FOG(input.fogCoord, col);
UNITY_OPAQUE_ALPHA(col.a);
return col;
}
ENDCG
}
Pass {
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
#include "TerrainEngineCustom.cginc"
#include "UnityRandomRotation.cginc"
struct v2f {
V2F_SHADOW_CASTER;
float2 uv : TEXCOORD1;
UNITY_VERTEX_OUTPUT_STEREO
};
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
fixed4 color : COLOR;
float4 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
v2f vert(appdata v)
{
float4 wPos = mul(unity_ObjectToWorld, float4(0, 0, 0, 1));
float x = wPos.r * wPos.g;
v.vertex = float4(RotateDegrees(v.vertex, x), 1);
v.normal = RotateDegrees(v.normal, x);
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
TerrainAnimateTree(v.vertex, v.color.w);
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
o.uv = v.texcoord;
return o;
}
sampler2D _MainTex;
fixed _Cutoff;
float4 frag(v2f i) : SV_Target
{
fixed4 texcol = tex2D(_MainTex, i.uv);
clip(texcol.a - _Cutoff);
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
Dependency "BillboardShader" = "Hidden/Nature/Tree Soft Occlusion Bark Rendertex URP"
Fallback Off
}
UnityBuiltin2xTreeLibraryCustom.cginc
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: commented out 'float4x4 _CameraToWorld', a built-in variable
// Shared tree shader functionality for Unity 2.x tree shaders
#include "HLSLSupport.cginc"
#include "UnityCG.cginc"
#include "TerrainEngineCustom.cginc"
#include "UnityRandomRotation.cginc"
float _Occlusion, _AO, _BaseLight;
fixed4 _Color;
CBUFFER_START(UnityTerrainImposter)
float3 _TerrainTreeLightDirections[4];
float4 _TerrainTreeLightColors[4];
CBUFFER_END
CBUFFER_START(UnityPerCamera2)
// float4x4 _CameraToWorld;
CBUFFER_END
float _HalfOverCutoff;
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
half4 color : TEXCOORD1;
UNITY_FOG_COORDS(2)
UNITY_VERTEX_OUTPUT_STEREO
};
v2f leaves(appdata_tree v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
TerrainAnimateTree(v.vertex, v.color.w);
float3 viewpos = UnityObjectToViewPos(v.vertex);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
float4 lightDir = 0;
float4 lightColor = 0;
lightDir.w = _AO;
float4 light = UNITY_LIGHTMODEL_AMBIENT;
for (int i = 0; i < 4; i++) {
float atten = 1.0;
lightDir.xyz = _TerrainTreeLightDirections[i];
lightColor = _TerrainTreeLightColors[i];
lightDir.xyz *= _Occlusion;
float occ = dot(v.tangent, lightDir);
occ = max(0, occ);
occ += _BaseLight;
light += lightColor * (occ * atten);
}
o.color = light * _Color * _TreeInstanceColor;
o.color.a = 0.5 * _HalfOverCutoff;
UNITY_TRANSFER_FOG(o, o.pos);
return o;
}
v2f bark(appdata_tree v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
TerrainAnimateTree(v.vertex, v.color.w);
float4 wPos = mul(unity_ObjectToWorld, float4(0, 0, 0, 1));
float x = wPos.r* wPos.g;
v.vertex = float4(RotateDegrees(v.vertex, x), 1);
v.normal = RotateDegrees(v.normal, x);
float3 viewpos = UnityObjectToViewPos(v.vertex);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
float4 lightDir = 0;
float4 lightColor = 0;
lightDir.w = _AO;
float4 light = UNITY_LIGHTMODEL_AMBIENT;
for (int i = 0; i < 4; i++) {
float atten = 1.0;
lightDir.xyz = _TerrainTreeLightDirections[i];
lightColor = _TerrainTreeLightColors[i];
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float diffuse = dot(worldNormal, lightDir.xyz);
diffuse = max(0, diffuse);
diffuse *= _AO * v.tangent.w + _BaseLight;
light += lightColor * (diffuse * atten);
}
light.a = 1;
o.color = light * _Color * _TreeInstanceColor;
#ifdef WRITE_ALPHA_1
o.color.a = 1;
#endif
UNITY_TRANSFER_FOG(o, o.pos);
return o;
}