• Create Account

## glsl area light (implementation)

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

19 replies to this topic

### #1ArKano22  Members

646
Like
0Likes
Like

Posted 03 November 2009 - 11:46 AM

I´ve implemented rectangular area lights in glsl (I missed them from Maya and 3dsMax :__ ). They´re not the real thing but an approximation, however they work quite well.

I´ve used a spotlight as a basis, so they´re very simple and fast, also. You can orient them and change their width and height. They cast no shadow, that will be a different thread :D (a very blurred pcss shadowmap should do the trick). I´m very excited about them because (at least for me) they open a new world of illumination schemes!

Screenies:

Code: (there is room for optimization but it´s usable as it is)
vec3 projectOnPlane(in vec3 p, in vec3 pc, in vec3 pn)
{
float distance = dot(pn, p-pc);
return p - distance*pn;
}
int sideOfPlane(in vec3 p, in vec3 pc, in vec3 pn){
if (dot(p-pc,pn)>=0.0) return 1; else return 0;
}
vec3 linePlaneIntersect(in vec3 lp, in vec3 lv, in vec3 pc, in vec3 pn){
return lp+lv*(dot(pn,pc-lp)/dot(pn,lv));
}
void areaLight(in int i, in vec3 N, in vec3 V, in float shininess,
inout vec4 ambient, inout vec4 diffuse, inout vec4 specular)
{
vec3 right = normalize(vec3(gl_ModelViewMatrix*gl_LightSource[i].ambient));
vec3 pnormal = normalize(gl_LightSource[i].spotDirection);
vec3 up = normalize(cross(right,pnormal));

//width and height of the area light:
float width = 1.0;
float height = 4.0;

//project onto plane and calculate direction from center to the projection.
vec3 projection = projectOnPlane(V,vec3(gl_LightSource[i].position.xyz),pnormal);// projection in plane
vec3 dir = projection-vec3(gl_LightSource[i].position.xyz);

//calculate distance from area:
vec2 diagonal = vec2(dot(dir,right),dot(dir,up));
vec2 nearest2D = vec2(clamp( diagonal.x,-width,width  ),clamp(  diagonal.y,-height,height));
vec3 nearestPointInside = vec3(gl_LightSource[i].position.xyz)+(right*nearest2D.x+up*nearest2D.y);

float dist = distance(V,nearestPointInside);//real distance to area rectangle

vec3 L = normalize(nearestPointInside - V);
float attenuation = calculateAttenuation(i, dist);

float nDotL = dot(pnormal,-L);

if (nDotL > 0.0 && sideOfPlane(V,vec3(gl_LightSource[i].position.xyz),pnormal) == 1) //looking at the plane
{
//shoot a ray to calculate specular:
vec3 R = reflect(normalize(-V), N);
vec3 E = linePlaneIntersect(V,R,vec3(gl_LightSource[i].position.xyz),pnormal);

float specAngle = dot(R,pnormal);
if (specAngle > 0.0){
vec3 dirSpec = E-vec3(gl_LightSource[i].position.xyz);
vec2 dirSpec2D = vec2(dot(dirSpec,right),dot(dirSpec,up));
vec2 nearestSpec2D = vec2(clamp( dirSpec2D.x,-width,width  ),clamp(  dirSpec2D.y,-height,height));
float specFactor = 1.0-clamp(length(nearestSpec2D-dirSpec2D)*shininess,0.0,1.0);
specular += gl_LightSource[i].specular * attenuation * specFactor * specAngle;
}
diffuse  += gl_LightSource[i].diffuse  * attenuation * nDotL;
}

ambient  += gl_LightSource[i].ambient * attenuation;
}

Specular is correctly calculated as you can see, along with diffuse and ambient.

I see lots of potential uses for this in any graphics application, for example:
-fluorescent bulbs (spot, omni or directional can´t even approximate this)
-windows
-glowing panels
-lava pits
...
You can even fake GI (fake because you are emitting light, not reflecting it) using some well-positioned area lights: in a room that has bright green floor, placing a greenish area light pointing upwards there can give quite a boost to the scene at a negligible cost.

[Edited by - ArKano22 on November 6, 2009 6:23:14 PM]

### #2bzroom  Members

647
Like
0Likes
Like

Posted 03 November 2009 - 12:25 PM

Nicely done. Haven't taken the time to understand the code. Some drawings and a more verbose write up would be nice. But nicely done!

### #3ArKano22  Members

646
Like
1Likes
Like

Posted 03 November 2009 - 12:45 PM

Quote:
 Original post by bzroomNicely done. Haven't taken the time to understand the code. Some drawings and a more verbose write up would be nice. But nicely done!

Thank you!

Basically what i´m doing is shading each pixel proportionally to its distance to an oriented rectangle. For this, my algorithm goes like this:

Step 1.- Project the soon-to-be-shaded point on the plane that contains the area light.
Step 2.- Calculate distance from the center of the rectangle to the projected point.
Step 3.- Obtain 2D coordinates of the projected point on the plane (how do you call this? the area´s object space?) using the distance calculated in (2) along with area height and width.
Step 4.- Clamp the projected point in 2D so that it lies inside the area.
Step 5.- This is the area point that lies nearest to our point. Retrieve distance between both points, using the width and height of the area to take back the nearest point to eye-space. This is used to calculate attenuation as usual.
Step 6.- In an area light each point is shaded from several directions at once, to simulate this multiply the usual dot product between normal and light direction by a factor and clamp it, so that you get a range of normals that get maximum illumination, then a smooth decay as they turn away from the light.

Specular:
As usual, reflecting the light direction calculated previously. Clamp the specular so that it doesn´t appear outside the area rectangle.

I´ll add some drawings later to illustrate this.

EDIT: Here´s a video showing them in action:

[Edited by - ArKano22 on November 3, 2009 7:45:32 PM]

### #4bzroom  Members

647
Like
0Likes
Like

Posted 03 November 2009 - 01:17 PM

It seems like the line lights elsewhere on this forum could benefit from step 6.

I'm assuming that step makes a considerable difference.

Thanks for the writeup.

747
Like
0Likes
Like

Posted 03 November 2009 - 09:09 PM

This work looks fantastic, and I'll probably try and implement an HLSL version myself sometime over the weekend.

Could I get a definition of what i and V represent please? I'm guessing V is view space position, as for i I'm not really sure since your calculateAttenuation() function is not shown in the code in your post.

### #6krigs  Members

162
Like
0Likes
Like

Posted 03 November 2009 - 09:34 PM

Looks very nice! About shadows... I think we can adjust PCF filter accordingly to distance to the rectangle.

### #7ArKano22  Members

646
Like
0Likes
Like

Posted 03 November 2009 - 09:58 PM

Quote:
 Original post by adt7 This work looks fantastic, and I'll probably try and implement an HLSL version myself sometime over the weekend. Could I get a definition of what i and V represent please? I'm guessing V is view space position, as for i I'm not really sure since your calculateAttenuation() function is not shown in the code in your post.

Hello! I´m glad you like it :)

i is the light index. In my engine i use forward shading and for that i iterate over all lights adding their contribution. So usually i goes from 0 to GL_MAX_LIGHTS.

V is the view space position, calculated this way in the vertex shader:

// Calculate the normal (N)
normal = normalize(gl_NormalMatrix * gl_Normal);

// Transform the vertex position to eye space (V)
vertex = vec3(gl_ModelViewMatrix * gl_Vertex);

The calculateAttenuation function is just like the fixed function pipeline:

float calculateAttenuation(in int i, in float dist)
{
return(1.0 / (gl_LightSource[i].constantAttenuation +
gl_LightSource[i].linearAttenuation * dist +
}

### #8ArKano22  Members

646
Like
0Likes
Like

Posted 03 November 2009 - 10:01 PM

Quote:
 Original post by KRIGSSVINLooks very nice! About shadows... I think we can adjust PCF filter accordingly to distance to the rectangle.

That´s just what i was thinking of doing with pcss. However getting the light view point can be tricky because of the light´s variable size, depending on that you must render the shadowmap from a different perspective.

### #9ArKano22  Members

646
Like
0Likes
Like

Posted 04 November 2009 - 09:48 AM

As promised here are two drawings explaining the method:

First of all found the nearest point inside the area: for that project V on the plane that contains the area. Then take it to object space and clamp its position to the width and height of the area to find the nearest point. Calculate the distance between V and the nearest point:

After calculating the attenuation using that distance, we have to simulate somehow that in a rectangular area light does not come from a single point. I chose to use a simple approximation, so that the highest possible illumination is not only for normals directly facing the area but for a wider range of normals.

And that´s basically it. The specular is pretty simple and does not need to be explained, it´s the usual thing.

### #10zoret  Members

126
Like
0Likes
Like

Posted 04 November 2009 - 06:33 PM

an orthographic projection wouldn't have been simpler ?

### #11krigs  Members

162
Like
0Likes
Like

Posted 04 November 2009 - 09:24 PM

Quote:
 Original post by zoretan orthographic projection wouldn't have been simpler ?

Rectangular light illuminates half of the hemisphere, so no.

### #12ArKano22  Members

646
Like
0Likes
Like

Posted 04 November 2009 - 11:21 PM

Quote:
 Original post by zoretan orthographic projection wouldn't have been simpler ?

Any projection is not well suited to this task. Specially ortographic projections. Perspective ones can´t cover a 180 degrees fov, and you can´t change the size of the light unless you want your projection to be stretched.

You could use paraboloid projection but it is just as complicated as what i´m doing and the results would be worse because you can´t easily control light attenuation and other things (normal mapping? several lighting models? specular?).

[Edited by - ArKano22 on November 5, 2009 8:21:40 AM]

### #13InvalidPointer  Members

1824
Like
0Likes
Like

Posted 05 November 2009 - 02:50 AM

FWIW, ever read this? It's mathematically correct and should be entirely feasible for real-time use. You can even do spherical and n-sided polygonal lights :)

### #14krigs  Members

162
Like
0Likes
Like

Posted 05 November 2009 - 03:10 AM

Perspective can be made 179 but it won't be uniformly stretched shadow map... So you have to use half-cube (5 faces).

[Edited by - KRIGSSVIN on November 5, 2009 11:10:04 AM]

### #15ArKano22  Members

646
Like
0Likes
Like

Posted 05 November 2009 - 03:35 AM

Quote:
 Original post by InvalidPointerFWIW, ever read this? It's mathematically correct and should be entirely feasible for real-time use. You can even do spherical and n-sided polygonal lights :)

Wow! I didn´t even knew that document existed! thank you very much!! I was convinced that area lights weren´t used in games today because nobody had implemented them.

With my method you can also simulate disc area lights and basically any light with a polygonal shape that lies on a plane (triangle shaped, for example). But the method described there seems much more physically accurate (altough it seems that only works for roughly spherical or hemispherical shapes, am i wrong?EDIT: yes i´m wrong xD. It can be used for polygonal shapes defined by vertices). I will study that in depth :)

### #16InvalidPointer  Members

1824
Like
0Likes
Like

Posted 05 November 2009 - 05:16 AM

Quote:
Original post by ArKano22
Quote:
 Original post by InvalidPointerFWIW, ever read this? It's mathematically correct and should be entirely feasible for real-time use. You can even do spherical and n-sided polygonal lights :)

Wow! I didn´t even knew that document existed! thank you very much!! I was convinced that area lights weren´t used in games today because nobody had implemented them.

With my method you can also simulate disc area lights and basically any light with a polygonal shape that lies on a plane (triangle shaped, for example). But the method described there seems much more physically accurate (altough it seems that only works for roughly spherical or hemispherical shapes, am i wrong?EDIT: yes i´m wrong xD. It can be used for polygonal shapes defined by vertices). I will study that in depth :)

It's a woefully under-researched topic, I'm afraid. That doesn't mean that some games don't use them, but effective techniques are fairly difficult to come by.

### #17ArKano22  Members

646
Like
0Likes
Like

Posted 05 November 2009 - 05:49 AM

I´m thinking about a cheap-ass way to render GI in an scene (its just a rough scheme): Let´s suppose a cornell box. You would add two area lights, one to each colored wall, covering its entirety.

Then, you would modify the area light code so that the light color and intensity would depend on a projected texture.

First render the cornell box, direct lighting + diffuse, don´t use the area lights. Store the result in a buffer and project it from the camera onto the scene. Then render the scene area lighting only using the projected lighting. Add together the two results (direct+area).

This would give a pretty convincing one-bounce semi-screen space local GI at the cost of two additional lights. If you wanted global GI, you would need to render a direct light only cube map from the camera position, which i think is feasible in realtime too.

If using deferred lighting you would be able to create absurd amounts of area lights around the scene. The two downsides are that you would have to place them by hand or write an algorithm to place them automatically, ala instant radiosity (most brute force being one light per polygon face), and that backfaces would not contribute unless you captured them in a projection.. The good news is that it has no precomputation, its entirely dynamic and that for big planar surfaces like walls (which are pretty common) its enough with one light. What do you think? would it be worth to implement and give it a try?

### #18n00body  Members

344
Like
0Likes
Like

Posted 06 November 2009 - 02:32 AM

Have you tested this trick in scenes with complex objects?

[Hardware:] Falcon Northwest Tiki, Windows 7, Nvidia Geforce GTX 970

[Websites:] Development Blog
[Unity3D :] Alloy Physical Shader Framework

### #19ArKano22  Members

646
Like
0Likes
Like

Posted 06 November 2009 - 04:04 AM

Quote:
 Original post by n00bodyHave you tested this trick in scenes with complex objects?

You mean the area light? Yes, i have tested it in the sponza atrium scene. It looks just like it should, maybe the transition from light to darkness between the front and back sides of the light is a bit too pronounced but it works ok. It works just like a spotlight or point light does so you can use normal mapping and all that stuff.

### #20ArKano22  Members

646
Like
0Likes
Like

Posted 06 November 2009 - 07:08 AM

I´ve changed the code in the first post because it had a bug and a little quirk:
-bug: nDotL is not dot(N,L), it should be dot(pnormal,-L). This takes out the hard edges and corrects an ugly artifact when the light was perpendicular to a plane.
-incorrectness: using dynamic branching to cull out the specular. It is better both visually and efficiency-wise to just vanish it out smoothly.

EDIT: specular calculations rewritten. Now they are mathematically correct, until now they weren´t proper reflections but only projection.

[Edited by - ArKano22 on November 6, 2009 6:08:03 PM]

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.