I'm attempting to improve the performance of a shader that changes the colour of a region of the world that is inside a "zone".

I am using a deferred lighting system, so the colour and world-space position of each pixel on the screen are stored in two separate textures, gColor and gPosition.

  • The zonePos uniform stores the two corners of each zone.
  • The zoneColor uniform stores the colour of each zone.
  • The totalZones uniform stores the amount of zones in the current game.

Here is the fragment shader:

out vec4 FragColor;

in vec2 texCoords;

layout (binding = 0) uniform sampler2D gColor;
layout (binding = 1) uniform sampler2D gPosition;

uniform vec3 zonePos[10];
uniform vec3 zoneColor[5];
uniform int totalZones;

void main()
    vec3 FragPos = texture(gPosition, texCoords).rgb;
    vec3 Albedo = texture(gColor, texCoords).rgb;

    vec3 j;
    vec3 k;

    for (int i = totalZones; i > 0; i--)
        j = zonePos[i * 2];
        k = zonePos[i * 2 + 1];

        if (FragPos.x >= j.x && FragPos.x <= k.x &&
            FragPos.y >= j.y && FragPos.y <= k.y &&
            FragPos.z >= j.z && FragPos.z <= k.z)
                Albedo += zoneColor[i];

    FragColor = vec4(Albedo, 1.0);

The changes I have made so far are:

  • Using j and k, rather than accessing zonePos 6 times in the if statement
  • Looping down from totalZones to 0, as a comparison against 0 is faster

Any suggestions are greatly appreciated.

  • \$\begingroup\$ is it a regular compact grid? or it is sparse and or each zone has different dimensions? \$\endgroup\$
    – Nadir
    Sep 27 '18 at 8:28
  • \$\begingroup\$ Each zone has different dimensions. I was thinking of moving it to the vertex shader but prefer the hard-edge the fragment shader provides. \$\endgroup\$
    – Vercidium
    Sep 27 '18 at 13:20
  • \$\begingroup\$ Can the zones overlap? As in, can a point be in more than one zone at the time? \$\endgroup\$ Sep 27 '18 at 13:52

Your check method inside the loop doesn't have any obvious inefficiencies for the way you've chosen to set up the problem.

But, we can take advantage of the deferred strategy to set up the problem in a simpler way.

Think of how we handle lights in deferred shading: Rather than looping over a bunch of lights in one shader like we might do in forward rendering (doing redundant computations for fragments that happen to be outside a given light's range), instead we render each light as a bounding volume. That way the shape of the geometry and depth testing help us limit our drawing to just the fragments that might be illuminated.

We can do the same with these zones. Render each one as a cube, sized & positioned to match the desired volume. Z-testing should be enabled but z-writing disabled.

The shader can then be very simple: read the world position of the fragment drawn there and test it against this single cube's bounds. If it passes, output your zone colour, otherwise abort or output black, and let additive blending do the rest (no need to sample the albedo texture manually inside your shader).

You can still render all this in a single pass by combining your cube meshes into a single vertex buffer, and encoding each one's zone colour/bounds in the vertex colours / texture coordinates. Or use instancing for a similar effect.

This lets us use the geometry & depth culling powers of the GPU to handle a pile of our checks, excludes redundant checks outside the screenspace volume of our zones, and simplifies our shader. We break out of the serial loop and let the GPU do what it does best, attacking the problem in parallel.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.