Posts
Search
Contact
Cookies
About
RSS

2D Spotlight shader with RayLib

Added 24 Mar 2020, 11:30 a.m. edited 18 Jun 2023, 5:41 p.m.

My initial thought for this was just to use simple alpha blending, while this did work (with caveats) I wasn't really satisfied with it, so I decided to look at implementing it with a shader... In the end this was much simpler than I expected - it almost wrote itself and nearly worked perfect first time!

The basic idea is first render everything we want on the screen and then render a fully opaque rectangle over the screen (you could have a slightly less opaque rectangle so you can only just see stuff in darkness or blank it out entirely)

The colour and alpha of this rectangle are completely ignored by the shader, its basically used so that the shader visits the whole screen area.

The shader has an array of spotlight positions

  // Inputs
  // array of spotlight positions
  uniform vec2 spots[MAX_SPOTS];

In order to access these positions we need to get the shader locations for them, as you can see by the sucky way I'm manipulating the name string you're limited to a maximum of 9 spotlights but frankly much more than 4 starts to get crowded.

// get the locations of spots in shader 
char spotName[32] = "spots[x]\0"; 
for (int i = 0; i<MAXSPOT; i++) {
     spotName[6] = '0' + i;
     spotLoc[i] = GetShaderLocation(spotShader, spotName);
}

This is basically a simplified version of the way the simple light shader handles multiple items, instead of an array of structs I'm accessing an array of Vec2's

As the positions of the spotlights change each frame we have to update the shaders values

for (int i=0; i<MAXSPOT; i++) {
    // ... positions updated here (code removed for clarity)
    SetShaderValue(spotShader, spotLoc[i], &spotPos[i].x, UNIFORM_VEC2);
}

The actual guts of the shader is really very simple, once the distance to the nearest spotlight is known (thanks to gl_FragCoord) that value can be used to calculate the alpha.

// d now equals distance to nearest spot... 
if (d > RADIUS) {
     alpha = 1.0; 
} else {
     if (d < INNER) {
         alpha = 0.0;
     } else {
         alpha = (d - INNER) / (RADIUS - INNER);
     }
}

if the distance is greater than the spotlight radius then you need fully opaque, if its less than the inner radius then its fully transparent otherwise you need to graduate from the edge of the inner radius to the outer radius.

see told you it was simple!!!

You can grab the code here

Enjoy!