Jump to content
  • Advertisement
Sign in to follow this  

2D Flashlight

This topic is 4231 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey all, I'm struggling to come up with a way of implementing a 2D flashlight. Currently I have a post-process shader performing a simple "point in triangle" test with the following results: Two things stand out: - The lack of gradient softness around the edges. It needs to be a much smoother fade to darkness. - I find the starting point too "pointy". I'd like something more "\_/" and less "\/". I figured I could sample from a reference texture but I have no idea how you would rotate the sample data to match the on-screen rotation of the player. I'd prefer all the work to be done in a post-process shader but at this point I just want something that works. Any ideas?

Share this post

Link to post
Share on other sites
Considering I already have projective texturing in place I would just map the special 2D case to a 3D one and have it going with a texture.

For a 2D postprocessing effect I would consider the two infinite lines and find how much of the current fragment is inside both, then multiply the background color with this coefficient.
In other words, since the two lines are "r=mx+q" and the fragment does have coordinate "x=k" i would intersect the two lines...

* (intersection point)
| .
| .
| (in/out ray mx+q)
(fragment "left" coord "x=k")

Then, there are 4 points to find which sum up to an area. There are various cases to consider (intersection partially in, totally out, left, right) but I bet you can simplify it and have nice results anyway... I still like projecting textures more ;)

EDIT: for some reason, the code likes to screw up my ascii art. Is there a special tag for it?

Share this post

Link to post
Share on other sites
Maybe you could use a distorted textured quad for this effect? It sounds pretty simple, what you're asking for, unless it really needs to be post-processed. A simple square gradient texture distorted amongst 4 vertices should give the results you want; you could fade the alpha of the far vertices and/or use some blending tricks to make it look cooler.

Determining the orientation of the vertices is simple trigonometry (just rotate the basic shape that you want); I'm sure we can give you help with that if you need it. I tend to think finding geometric solutions to the lines is a bit overkill for the effect!

Share this post

Link to post
Share on other sites
Sticking with mathematical tests, you could use a dot-product to create the attenuated, cone shape, using a few trigonometric and rational profiles to create the attenuations:

Suppose the torch lies at O (the apex of the cone) and the point being rendered is at P. Let D be a unit-vector in the direction the torch is shining.
Define the normalised incidence vector as I = (P - O)/|P - O|

Now we can get a measure of the lightness of the pixel with:
s = dot(I, D);     // Resolve directions
This will produce a soft sweeping arc, but it gives the torch a view-angle of 180°.

180° Cone

We can change this with the following:
float a = acos(s) * (PI / fov);
a = clamp(a, 0, PI / 2);
s = cos(a);
Here, fov is the view-angle of the torch (in radians).

30° Cone

We can further adjust the lightness as a function of the angle (to create a sharper or smoother arc) by raising s to a power:
s = pow(s, ang_falloff);
If ang_falloff > 1, we well get a sharper peak down the central line. Conversely, if If ang_falloff < 1, the beam will be more uniform.

The biggest remaining problem is that the torch will illuminate anything in front of it by the same amount, regardless of how far away it is. We can taper things off semi-realistically with a logistic falloff:

a = far_attenuation / (L + far_attenuation)

far_attenuation is a measure of how quickly the light decays with distance (smaller values decay sooner) and L is the distance to the light source. As it is, this falls off too quickly at first and too slowly in the distance. We can give it more of a sigmoidal form by raising its complement to a positive power (and multiplying in with our cumulative lightness, s):

s *= 1 - (1 - a)far_apex_power

far_apex_power determines the distance at which the attenuation is maximal. This gets us here:

Attenuated Cone

So the next move is to cut off that sharp, bright apex. I think a simple clamped linear function looks good enough:

n = L * near_gradient - near_plane;
n = clamp(n, 0, 1);
s *= n;

near_plane determines how far away from the apex the light is first visible, and near_gradient dictates how sharply it is introduced at this point. After a little tweaking, we get the final result:

Doubly-attenuated Cone

The parameters can be tweaked, but it's not all that obvious how to get things perfect, as they are all interdependent. Proceed with caution. Here's the code I used to create the sample images. The first few lines should probably be ignored, as I used to them to convince FX Composer to use your coordinates.

float4 PS_Textured(vertexOutput IN): COLOR
// Constants and parameters
const float PI = 3.141592f;

const float fov = 3.141592f / 4;
const float ang_falloff = 2.5f;
const float far_attenuation = 3.0f;
const float far_apex_power = 10.0f;
const float near_plane = 1.5f;
const float near_gradient = 0.3f;

// Initialisation
float x = IN.texCoordDiffuse.x * 100;
float y = IN.texCoordDiffuse.y * 100;
float2 P = float2(x, y);
float2 O = float2(10, 10);
float2 D = normalize(float2(2, 3));

float2 I = P - O;
float dist = length(I);
I = normalize(I);

// Resolve angle
float s = dot(I, D);

// Scale and clamp for view-angle
float a = acos(s) * (PI / fov);
a = clamp(a, 0, PI / 2);
s = cos(a);

// Scale in the logarithmic domain to adjust angular attenuation
s = pow(s, ang_falloff);

// Attenuate far
s *= 1 - pow(1 - far_attenuation / (dist + far_attenuation), far_apex_power);

// Attentate near
float n = dist * near_gradient - near_plane;
n = clamp(n, 0, 1);
s *= n;

return float4(s, s, s, 1);


Share this post

Link to post
Share on other sites
SNES programmers had a neat way of implementing effects such as this though the use of clipping windows.

A clipping window used a left and right value which told the PPU which portion of a scanline to clip during rasterization. The clipping window also had a IN/OUT attribute which meant to either clip area between the two values or outside of them. For the flashlight you would set the attribute to OUT. There were also options of modifying the pixels in the clipped regions. So instead of clipped areas always being a solid color, you could apply a color tint to the pixels or apply a logical operator to them.

Of course if each scanline used the same left/right clip value you can only get boring rectangular clip regions. So programmers used the HDMA to set different left/right clip values per scanline during the H-Blank. This allowed non-clipped regions that could take the shape of any convex polygon and even circular shapes.

The result was all those cool screen transitions and lamp/flashlight effects. One example is the lamp effect in Zelda:LTTP where Link is exploring the sewers. It looks like two clip windows were used to get the lamp's crude falloff effect.

The method probably doesn't translate well with shader and cannot do smooth falloffs. But your picture got me thinking of SNES games and how they did it...

Good Luck

Share this post

Link to post
Share on other sites
Sign in to follow this  

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!