Game Development Community

Experiment: Trilight Lighting Model

by Tom Spilman · in Torque 3D Professional · 06/08/2009 (11:37 am) · 20 replies

Hey All.

Pat Wilson originally added Tom Forsyth's "Trilight" model like 6-7 months ago into the directional light shader. Its never really been exposed right and because of time constraints we may or may not include it in the first Torque 3D release.

Yet i took some time to experiment with it last night and wanted to share my results and get feedback from you guys.

So here is the stock Lambert lighting from the Sun with a non-black ambient light.


farm4.static.flickr.com/3645/3607096369_6f043fbbc7.jpg

farm4.static.flickr.com/3324/3607096751_63ccfc9021.jpg

You can see how the lighting in the shadows is flat with the only color coming from the diffuse texturing. In these areas your normal maps disappear because there is no contrasting color. This is what you get with TGE, TGEA, Torque 3D Beta 2, and just about every other engine out there.

Before we continue you should read about 3 point lighting as its what Forsyth's Trilight model is approximating. Also read Three-Point Lighting for 3D Renderings to see a 3D rendering example.

Its hard to apply the 3 point lighting terms to the Trilight model as the camera generally moves interactively in games. At different camera angles the "Back Light" and/or the "Fill Light" provide the rim lighting effect as described in the links above.

So back to our lighting. If we add some light in the shadowed areas then you'll have some contrast which enhances the details. Here is the same scene with a back light in direct opposite direction from the "Key Light" (the sun light).


farm4.static.flickr.com/3374/3607915518_c43ce9c912.jpg

farm4.static.flickr.com/3556/3607098003_088c39a2a7.jpg

The back light in these images is providing contrasting light in your shadows reducing the flat look you normally would get. Usually one would use a back light that is not directly opposed to the key light, but for this approximation it works well.

So now to the "Fill Light" which in our scene contributes the non-Sun sky lighting.


farm4.static.flickr.com/3332/3607917336_d4ce825aee.jpg

farm4.static.flickr.com/3593/3607098651_8b513f6a9e.jpg

Depending on the angle of the camera and the angle of the Sun the fill light either provides a rim lighting effect or just an alternate color to your directional lighting. Either way it does help the scene pop when it's used well.

So here are some shots with a white sun light color, a yellow fill light color, a red-orange back light, and a blueish ambient color.


farm4.static.flickr.com/3376/3607919068_e8fed76fa9.jpg

farm4.static.flickr.com/3596/3607917980_e0b47ef288.jpg

Here is a good example where normally the broken dock would be totally flat light, but the back light gives it definition while its in shadow.


farm3.static.flickr.com/2475/3607919960_bc46255e99.jpg

farm3.static.flickr.com/2465/3607920498_a360bf02e5.jpg

Another shot ... same thing...


farm4.static.flickr.com/3393/3607104829_b66999cb57.jpg

farm4.static.flickr.com/3350/3607921488_d1cf0a4c67.jpg

And here is a comparison between stock Lambert....


farm4.static.flickr.com/3648/3607930386_f94a1c0860.jpg

There you can see the cart is completely in shadow and lacks details. Its much better defined with some well picked Trilight colors...

farm4.static.flickr.com/3410/3607113439_2413aba89b.jpg

farm4.static.flickr.com/3592/3607932180_51a0eecc07.jpg


So all that said while you could get the same or better results with stock Torque 3D by placing 3 lights. The trilight model gives us nearly the same look without the cost and complexity of a 3 seperate lights in the scene. It is also flexible in that you can emulate many different lighting effects by changing the fill and back colors.

Later tonight i plan to experiment with adding the trilight model to our point and spotlights in the Undercity level.

About the author

Tom is a programmer and co-owner of Sickhead Games, LLC.


#1
06/08/2009 (11:53 am)
That adds much more depth to the world.

Also, is there a console command for hiding the textures (like you have in the 3 colour/lighting/flat surface shots)?
#2
06/08/2009 (11:58 am)
@Steve

I'm using the "lightinfo" buffer view.... toggleLightPrePassViz().

You also have toggleNormalsViz() and toggleDepthViz().
#3
06/08/2009 (12:24 pm)
Wow, it really adds a lot of dimensionality to dark areas.
Quote:Its hard to apply the 3 point lighting terms to the Trilight model as the camera generally moves interactively in games. At different camera angles the "Back Light" and/or the "Fill Light" provide the rim lighting effect as described in the links above.
The way I've seen this handled in a few other games is to orient a player-only back light to the camera, so no matter which way the camera is facing, the player always gets a rim light/back light. Of course it is technically wrong to mount it to the camera's direction, but when done with subtlety, works pretty well to add that separation from the background that back lighting is for. I think I noticed this in the latest Resident Evil 5. I also think I saw a talk at GDC a few years ago about the same thing, a shader-simulated, player-only, three point lighting setup.

(Edit: or maybe I only saw it in my imagination... can't seem to find anything about a GDC talk on that subject.)
#4
06/08/2009 (8:06 pm)
Well i'm sure that talk existed... it does make sense to have a camera aligned rim light in some situations. But... i don't think its appropriate for it to be a stock feature of the Sun light.

I would thing that light would only effect the player and would be specialized for you game and not tied to any other lights. I could see it changing the color and intensity of this rim light as you move from area to area.

I need to look at RE5 again.
#5
06/09/2009 (6:39 am)
Didn't CoD4 do the same sort of thing on characters/AI using a shader? There was always a slight white/glow/outline mimicking reflective ambient lighting. It did occaisonally look a bit odd with an outlined figure against a shadowed background and there being no obvious source for the reflected light.
#6
06/09/2009 (6:55 am)
Looks very interesting.
Especially for evening and night scenarios it offers a significant raise in quality and atmosphere.
#7
06/09/2009 (7:16 am)
RE5? Heck, RE4's character lighting was nearly entirely based on direction lights that only affected the characters and were fixed to the camera direction. Well, on the GC/Wii version, at least (the PS2 version got one or 2 fixed directional lights at most).
#8
06/09/2009 (5:13 pm)
Awesome Tom, this is one of my biggest dis-likes issues in the Advanced Lighting. 0-50% not facing the light looks blan.

Any update on your progress of adding it to UnderCity?
#9
06/10/2009 (3:04 am)
Yea i've been experimenting with using this same technique in Undercity.

The results have been interesting, but it makes me question if we really need this model on Spot and Point lights. With point lights in particular the results are so non-physical that it makes me think its just more work to do per-pixel for something which will be rarely used.

Anyway... i'll have more details and screenshots soon.

Still i would love to see more discussion and suggestions on where we should take the dynamic lighting in Torque 3D.
#10
06/10/2009 (6:26 am)
Quote:
suggestions on where we should take the dynamic lighting in Torque 3D.

From a performance point of view, how about being able to give dynamic lights casting shadows a light/simgroup or independant variable that can be toggled on/off from the Options menu?

eg:
In Options, if you check "dynamic lights", all of the pointlights in the specific group would cast shadows, and the ones not in that group wouldn't. I know I could probably script all this myself but it might be nice to have it as stock

Also:

Whilst using pointlights without cast shadows, I've noticed that there is a performance hit if the radius' overlap (obviously because the objects in the overlapping zones - eg:player - then has to work out being hit by multiple light reflections). But just thought I'd mention it though I don't see anyway of avoiding that

Just as a thought, before Tom started this thread, I'd been playing around with a trianglular ambient lighting idea I used to do back when modding to get the same sort of ambient light reflection effect (though previously I just tended to use a single backlight), but I hadn't thought of using 3 primary colours (red, yellow, blue) before, I'd always used a cold blue to work as an opposite to the warm sunlight. 3 colour approach is so much nicer, and with the lights set to not cast shadows, the effect penetrates all buildings with a very low brightness setting and gives everything more depth.

Not exactly the same as sticking 3 dynamic lights around the player, but certainly a cheap and effective workaround for getting more depth into the scene.
#11
06/10/2009 (6:34 am)
I don't know how T3D currently manages shadowmap updates, but I'd like the option of being able to disable shadowmap updates on a per-light basis and having the option to turn on/off the plain old per-object projected shadow.

Also, while full real-time is nice, I'd also like to discuss mixed real-time/baked lighting and others ways to provide better ambient lighting to objects (node-based ambient cubemaps, spherical harmonics, or dual-paraboloid maps, etc.)
#12
06/10/2009 (9:01 am)
@Russell

I swear Gears of War does the same thing.

@Tom

Awesome work on showing off this lighting technique. I think its something that people would really want to see in Torque3D by default since like you said it adds more robust lighting to the environment (and arguably is one of the most impressive and important portions of Torque3D, the fact that the lighting doesn't look like ass and thus the games look infinately better and thus it turns peoples opinions of Torque on their head).

Out of curiousity how much of a hit is it with the Trilight versus the current stock method?

BTW now that you have done this extra bit of lighting, you know you will need to create a material shader that utilizes rim-lighting in the specular and shading calculations ;)
#13
06/10/2009 (9:36 am)
Quote:@Russell

I swear Gears of War does the same thing.
After reading this thread, I think I'm seeing it a lot. Just yesterday I though I saw it on Infamous. Is every AAA developer doing this, but no one is talking about it? Or have I just lost my mind?
#14
06/10/2009 (10:43 am)
Quote:
Also, while full real-time is nice, I'd also like to discuss mixed real-time/baked lighting and others ways to provide better ambient lighting to objects (node-based ambient cubemaps, spherical harmonics, or dual-paraboloid maps, etc.)
Check out the pureLIGHT thread. Keep your ears open for official announcements.
#15
06/10/2009 (3:48 pm)
The really nice thing with the trilight and the reason that Forsyth recommends it is it lets you smoothly interpolate between different lighting models in different situations. So you can have different parts of the world causing different lighting environments, but transition between them without popping.

Combined with some of the SH work he describes for coping with lots of lights in the scene, this ends up giving you a really powerful framework for implementing game-specific lighting in an artist-friendly way.

By itself the trilight is kind of neat, but if you can put it in the right context it goes from "neat" to "amazingly powerful."
#16
06/10/2009 (5:34 pm)
Quote:and others ways to provide better ambient lighting to objects

I'm looking at those things as well... how to do it in realtime.

For instance the Crytek guys are doing amazing things again.

farm4.static.flickr.com/3646/3580625858_c7f30646f9.jpg

They'll be presenting this technique at Siggraph 2009 and i plan to be there.
#17
06/10/2009 (7:06 pm)
Thess boys been playing with it for two years.
lightsprint.com
www.youtube.com/watch?v=lB5_x2BVRH0
www.youtube.com/watch?v=M0ylvjrbwEc

And this guy just messing with it in GLSL
Shader Post Effect:

/*
*CSSGI shader (Coherent Screen Space Global Illumination)
*This shader requires a depth pass and a normal map pass.
*/


#define NUM_SAMPLES 8

uniform sampler2D som;
uniform sampler2D normal;
uniform sampler2D color;

//noise producing function to eliminate banding (got it from someone else´s shader):
float rand(vec2 co){

        return 0.5+(fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453))*0.5;

}

void main()
{    
//calculate sampling rates:
float ratex = (1.0/800.0);
float ratey = (1.0/600.0);

//initialize occlusion sum and gi color:
float sum = 0.0;
vec3 fcolor = vec3(0,0,0);

//far and near clip planes:
float zFar = 80.0;
float zNear = 0.5;

//get depth at current pixel:
float prof = texture2D(som, gl_TexCoord[0].st).x;
//scale sample number with depth:
int samples = round(NUM_SAMPLES/(0.5+prof));
prof = zFar * zNear / (prof * (zFar - zNear) - zFar);  //linearize z sample

//obtain normal and color at current pixel:
vec3 norm = normalize(vec3(texture2D(normal,gl_TexCoord[0].st).xyz)*2.0-vec3(1.0));
vec3 dcolor1 = texture2D(color, gl_TexCoord[0].st);

int hf = samples/2;

//calculate kernel steps:
float incx = ratex*30;//gi radius
float incy = ratey*30;

float incx2 = ratex*8;//ao radius
float incy2 = ratey*8;

//do the actual calculations:
for(int i=-hf; i < hf; i++){
      for(int j=-hf; j < hf; j++){

      if (i != 0 || j!= 0) {
 
      vec2 coords = vec2(i*incx,j*incy)/prof;
      vec2 coords2 = vec2(i*incx2,j*incy2)/prof;

      float prof2 = texture2D(som,gl_TexCoord[0].st+coords*rand(gl_TexCoord[0])).x;
      prof2 = zFar * zNear / (prof2 * (zFar - zNear) - zFar);  //linearize z sample

      float prof2g = texture2D(som,gl_TexCoord[0].st+coords2*rand(gl_TexCoord[0])).x;
      prof2g = zFar * zNear / (prof2g * (zFar - zNear) - zFar);  //linearize z sample

      vec3 norm2g = normalize(vec3(texture2D(normal,gl_TexCoord[0].st+coords2*rand(gl_TexCoord[0])).xyz)*2.0-vec3(1.0)); 

      vec3 dcolor2 = texture2D(color, gl_TexCoord[0].st+coords*rand(gl_TexCoord[0]));

      //OCCLUSION:

      //calculate approximate pixel distance:
      vec3 dist2 = vec3(coords2,prof-prof2g);

      //calculate normal and sampling direction coherence:
      float coherence2 = dot(normalize(-coords2),normalize(vec2(norm2g.xy)));

      //if there is coherence, calculate occlusion:
      if (coherence2 > 0){
          float pformfactor2 = 0.5*((1.0-dot(norm,norm2g)))/(3.1416*pow(abs(length(dist2*2)),2.0)+0.5);//el 4: depthscale
          sum += clamp(pformfactor2*0.2,0.0,1.0);//ao intensity; 
      }

      //COLOR BLEEDING:

         if (length(dcolor2)>0.3){//color threshold
           vec3 norm2 = normalize(vec3(texture2D(normal,gl_TexCoord[0].st+coords*rand(gl_TexCoord[0])).xyz)*2.0-vec3(1.0)); 
           
           //calculate approximate pixel distance:
           vec3 dist = vec3(coords,abs(prof-prof2));

           //calculate normal and sampling direction coherence:
           float coherence = dot(normalize(-coords),normalize(vec2(norm2.xy)));

           //if there is coherence, calculate bleeding:
           if (coherence > 0){
              float pformfactor = ((1.0-dot(norm,norm2)))/(3.1416*pow(abs(length(dist*2)),2.0)+0.5);//el 4: depthscale
              fcolor += dcolor2*(clamp(pformfactor,0.0,1.0));
           }
        }
      
     }
   }
}

vec3 bleeding = (fcolor/samples)*0.5;
float occlusion = 1.0-(sum/samples);
gl_FragColor = vec4(vec3(dcolor1*occlusion+bleeding*0.5),1.0);
}

Video of Shader:
www.youtube.com/watch?v=o7n4hc82k3g

Some talk about the shader:
www.gamedev.net/community/forums/topic.asp?topic_id=517130

Just some thoughts.
#18
06/10/2009 (7:23 pm)
Well LightSprint i believe is precomputed and probe based.

It seems Crytek like ArKano22's is based on image post processing which is why i'm interested in it.

If someone feels like being a hero today... port the shader above to a PostEffect. :)
#19
06/11/2009 (10:26 am)
Quote:# *CSSGI shader (Coherent Screen Space Global Illumination)
# *This shader requires a depth pass and a normal map pass.
Depth and normal pass? Got em! Opens up so many doors...
#20
06/24/2009 (1:05 pm)
A little something about three-point lighting and XNA. Caveman artist not know if this useful.