Course contentsShow
← Unity Game Development
Lesson 738 of 82811. Performance and Data PersistenceFree lesson

Writing Efficient Custom Shader Code

Learners will optimize shader arithmetic and minimize texture lookups to ensure custom visual effects do not bottleneck the rendering pipeline.

In this lesson, you will learn how to optimize your custom HLSL shader code by reducing redundant arithmetic and minimizing expensive texture lookups to keep your rendering pipeline running smoothly.

When writing shaders, every operation performed in the fragment shader executes for every single pixel on the screen. Small inefficiencies here scale up rapidly. The GPU is designed for massive parallelism, but it can easily be bottlenecked by "expensive" instructions.

Reducing Arithmetic Complexity

The most common mistake is performing calculations inside the fragment shader that could have been handled in the vertex shader or passed as a pre-calculated uniform. For example, if you are calculating a power function or a sqrt on a value that doesn't change across the polygon face, move it to the vertex stage.

Consider this inefficient pattern:

// Inefficient: Re-calculating every pixel
float4 frag(v2f i) : SV_Target {
    float3 lightDir = normalize(_LightPos - i.worldPos);
    float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
    // ... rest of lighting
}

If your light is directional (constant across the object), pass the lightDir as a varying from the vertex shader. Pre-computing values in the vertex stage (where there are fewer vertices than pixels) significantly reduces the per-pixel math burden.

Minimizing Texture Lookups

Texture sampling (tex2D) is an expensive operation due to latency and cache misses. If you need the same texture value multiple times, sample it once and store it in a local variable. Furthermore, avoid conditional logic (like if statements) that causes "branching," as GPUs handle these poorly by often executing both paths and discarding the result.

Instead of branching, use the lerp or saturate functions, which are often implemented as specialized hardware instructions:

// Avoid: if (mask > 0.5) color = texA; else color = texB;
// Use:
float4 finalColor = lerp(texB, texA, step(0.5, mask));

By replacing branching with mathematical interpolation, you allow the GPU to maintain its high-throughput pipeline without stalling for conditional checks.

Key Takeaway: Shift as much logic as possible to the vertex shader and replace conditional branching with built-in interpolation functions to maximize GPU performance.