Back to projects

Rendering Optimization

Terrain Shadow Optimization

A real-time shadow optimization scheme for an SNS community game. The approach uses light-space render textures, baked directional shadow channels, shader-side sampling, and batching-friendly UV data to reduce dynamic shadow cost while preserving readable building shadows.

Unity Shader RenderTexture Realtime Shadow Patent

Overview

The system was designed for an SNS community scene where buildings, decorative elements, and terrain need convincing shadows without the full runtime cost of general dynamic shadows. The solution separates terrain projection shadows, element directional shadows, and batching data so the scene can remain efficient at runtime.

Cabin scene using terrain shadow optimization
Cabin scene using the optimized terrain shadow solution.

Case 1

Terrain Shadow RenderTexture

A camera is placed at the light position to render a shadow texture from light space. The terrain shader projects this render texture into world space and blends the sampled shadow with the base terrain lighting.

Building shadow case
Building shadow case.
Cabin render texture shadow map
Cabin RenderTexture used for projected shadow sampling.
// Project RenderTexture in world position.
fixed4 frag(v2f i) : COLOR
{
    fixed4 tex = tex2D(_MainTex, i.texcoord);
    fixed spec = tex.a;

    float2 shadowUV = mul(_ShadowVPMatrix, i.worldPos).xy * 0.5 + 0.5;
    fixed atten = tex2D(_CurrentShadowmap, shadowUV).r;
    fixed4 shadowColor = lerp(_ShadowCol, fixed4(1, 1, 1, 1), atten);

    tex = _MainColor * _DirectionalLightColor * shadowColor * tex;

    half3 normalDir = normalize(UnpackNormal(tex2D(_BumpMap, i.texcoord)));
    half3 viewDir = normalize(i.viewDir);
    half3 lightDir = normalize(i.lightDir);
    half3 halfDir = normalize(viewDir + lightDir);

    float specular = max(0, dot(halfDir, normalDir));
    tex += _SpecularCol * spec * pow(specular, 48);
    return tex;
}

Case 2

Element Shadow Channels

For decorative elements, four directional shadows are baked into separate texture channels. At runtime, the shader samples the RGBA shadow texture and combines it with a direction selector to shade the element efficiently.

Cabin building element shadow case
Cabin building element shadow case.
Four directional shadow channels
Four directional shadows baked into channels.
fixed4 frag(v2f i) : COLOR
{
    fixed4 tex = tex2D(_MainTex, i.texcoord.xy);
    fixed4 lm = tex2D(_ShadowTex, i.texcoord.zw);

    fixed shadowValue = dot(lm, _ShadowDir);

    tex = lerp(tex * shadowValue * _ShadowCol, tex, shadowValue);
    tex = lerp(tex, tex * _LightPower, shadowValue * 1.8);

    #ifdef _GRADIENT_ON
        tex *= i.gradient;
    #endif

    return tex;
}

Batching

UV3 Shadow Data and Static Batch

To keep scene rendering batching-friendly, extra UV data is generated in UV3 to carry shadow channel information. This allows decorative elements to participate in static batching while still sampling the correct shadow data in the shader.

Player building batch case
Player building shadow batch case.
Static batched building shadow case
Running with static batching.