There’s Nothing Wrong with OpenGL’s Specular Lighting!
Posted by gregd1024 on December 21, 2007
Most beginners to OpenGL make a common mistake implementing specular lighting when the camera can move around freely in a first-person perspective. What happens is this: a light beam illuminating some surface will move across that surface in the exact opposite direction of the camera’s viewing vector. I’ve written this OpenGL app to illustrate:
![]() |
Figure 1: green light beam shines across middle of left wall. Camera is looking straight ahead. Click for larger image. |
![]() |
Figure 2: now, when the camera is tilted up, the green light beam shines across the bottom of the left wall. Top beam on ceiling gets brighter while floor beam disappears. Click for larger image. |
Keep in mind, the camera’s x, y, and z coordinates did not change in any of these shots, only the viewing angles changed. Therefore it’s quite obvious that this is not how things should work. The light beam should not move regardless of where I’m looking in the 3D world.
So why does this happen? Well, think about it – am I really “looking” around in the 3D world? Is the camera’s position changing when I go forward, backwards, left, or right as far as OpenGL is concerned? If you answered yes, think again. Remember, OpenGL has no concept of a “camera” – we just track the camera’s location internally in our code and then transform every polygon in the opposite direction. If the camera moves forward, we transform all polygons backward. If the camera looks 20 degrees to the right, we transform all polygons 20 degrees to the left. In other words, the camera really stays at (0, 0, 0) while the polygons are transformed around it. Now here’s the kicker – OpenGL transforms the polygons and light positions around the camera, but by default does not rotate the specular reflection angles to match the current view transform (i.e. the light vectors are not transformed into camera/eye coordinates like everything else). When this happens the light vectors (not light positions) are just like the camera – they don’t move with the rest of the world, the world moves around them. It is like having a flashlight floating in the middle of a room pointing in one direction while the room rotates around it; naturally, the light’s beam would slide across the wall surfaces depending on how they were moving. On the other hand, if the flashlight rotates with the room then the beam of light would always be pointing to the same spot. It is the first scenario (where the light can’t rotate) that makes this OpenGL effect occur.
Why specular angles are not transformed by default, I don’t know, but if you do, please leave a comment on this post. Although I suspect it has something to do with the fact that after OpenGL’s inception in 1992 and up until the late 90’s, all of the OpenGL programs I saw were demos of object modeling where the camera’s viewpoint never changed. In this case, if you transformed the light vectors to match the object’s orientation you’re going to get the wrong effect – you want those vectors to stay static looking straight ahead (directly down the -z axis).
So how do you tell OpenGL to transform the specular angles along with everything else? Simple – you must add another lighting model by calling one of the glLightModel functions. I prefer glLightModelf() simply because floating points are OpenGL’s default data type. Set the first parameter to GL_LIGHT_MODEL_LOCAL_VIEWER, and the second parameter to “1” (or anything non-zero) like this:
glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, 1.0f);
I place this call whenever I need to initialize or reinitialize lighting in the engine. For the screenshots in this article I used the following lighting models:
GLfloat dim_light[] = {0.0f, 0.0f, 0.0f, 1.0f};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, dim_light);
glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, 1.0f);
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
The following screenshots show how the rendering looks with the new lighting model added.
![]() |
Figure 4: Now when looking upward notice how the green light beam on the left wall stays centered. Click for larger image. |
![]() |
Figure 5: just like Figure 4 but looking downward. The green light beam is still centered on the left wall. Click for larger image. |
That’s it! One small change does it all. š
However, there’s one big disadvantage of this method that you may have noticed from the previous two screenshots. The polygon tessellation becomes much more noticeable depending on what angle a light hits a surface – even if you’re not that close to a polygon. And believe me, these last two screens hide the problem pretty well. Solution? Write a shader so that we can have complete control of the rendering pipeline. That’s one of the things I’ve been wanting to improve on this engine but haven’t got around to it yet. I took a detour doing the Quake 2 Port which took a lot of time and have recently been doing intense research on graphics programming for the Windows Mobile 6 OS (i.e. Pocket PC and SmartPhone development). So I’ll write a separate article on the shader solution later on.
As always, you can get automatic updates whenever I post new articles via the RSS feed (subscribe here), or if you prefer email updates, click here.
Thanks for reading! š
-Greg Dolley
*Special thanks goes out to Luke Ahearn for those nice looking textures! š
Lisa said
I know this is a really old post and I assumed you figured out the answer to this already, but just in case here is answer to your question about why specular angles are “not transformed”:
Specular lighting is, by definition, view angle dependent.
“View angle dependent” means that the lighting should appear differently depending on the angle between the viewer and the surface.
Your article makes it sound as if specular reflections on objects shouldn’t move as the eye position or angle changes. That is incorrect. Put a shiny object on your desk and rotate your head left and right. You’ll note, the position of the highlights change as you rotate your head. Real-world reflections are view-angle dependent. It is this effect that GL is trying to emulate with the specular term.
Anyway, that’s why GL “doesn’t transform” the specular angle by default. You’re fixing something that’s not broken. The lights are supposed to move. š
You can read a more math-intensive explanation here: http://www.cs.nps.navy.mil/people/faculty/capps/4470/ads/specular.html
Dalai Felinto said
Thanks a lot for this post.
I’m currently implementing a fisheye camera for Blender Game Engine, and I couldn’t get the specular light to match each individual render (to make a fisheye I’m rendering the scene 4 times each frame).
Guess what, the problem is exactly the one you presented here š
Blender Game Engine code is very old. So we still have old bugs as this.
Thank you for that !!!
gregd1024 said
Dalani – sweet! Glad it helped. š
-Greg
Bart said
By now this is an even older post, but I felt like disagreeing with the first comment anyway. Specular differs depending on the angle between 3 points: the camera, surface and light locations. There is no camera angle/rotation needed to compute that, only the camera _location_. That’s obvious if you consider that the same light rays will still arrive at the same location even if the you rotate the object receiving them.
Also, rotating your head is not really the most reliable way to verify this in the real world. For nearby objects you would have to close one eye and rotate around the center of the other eye to keep the eye location fixed, not an easy exercise :). For far away objects it works better of course.
gregd1024 said
Bart – exactly! š The first comment (by Lisa) thought I was saying that OpenGL does lighting incorrectly. Actually, the title of my post (“There’s Nothing Wrong with OpenGL’s Specular Lighting”) says the opposite. But my point was that most programmers are confused as to why their specular light beams move across surfaces when the camera looks around at various angles. While I sit here typing in my office, there are three lights casting directional beams across my walls (similar to my screenshots) – these beams never move, no matter what angle my eyes are looking – they illuminate the same places on the walls all the time. However, setup the same scene in OpenGL, and depending on what angle the camera is looking, those beams of light will move and illuminate different places on the polygonal walls – obviously not what you’d expect to see. Hence, the reason for my post! š
-Greg Dolley
Tom said
Hi, I think I’m quite possibly being a bit thick, but I can’t see why you’re confused about OpenGL’s way of doing things. Specular lighting is determined by the vector from the viewer to the surface, the vector from the surface to the light source and the normal on the surface. In OpenGL you effectively transform everything so that the notional camera is fixed and the world moves around it. Using the OpenGL matrix stack you can maintain various different objects with different bases, such as a static tree and, separately, a car that drives around it.
With that in mind, stating that the light is at (x, y, z) when drawing the tree means a different thing than stating that it is at (x, y, z) when drawing the car, because they have different positions and orientations. If OpenGL were to reverse transform the light position into world space when drawing each object, then where should it transform back to on the matrix stack? And what if you want to be able to specify a light that is moving with an object, do you have to transform its position yourself?
The only meaningful solution is to have the light somehow capture how the information in the matrix at the top of the stack when it is specified. Which is exactly what it does, and clearly exactly the point you’re making in this post. But what could the alternative be? What frame of reference should OpenGL use when you specify a light position? And why should you have to implement your own transform if you want a moving light?
It’s very possible that I’m completely missing the point though.
Dalai Felinto said
Hi, another thought in that matter.
I’m strongly tempted to think that not only specular, but there is no reflection effect that should be “View angle dependent” in real world. I even ran some tests with raytracing softwares. Even if I rotate the camera the mapped result is always the same.
However, I see all around the net tutorials to implement Spherical Environment Mapping or Cube Environment Mapping which result in “View angle dependent” results.
On top of that I managed to get a cube map reflection GLSL code working already (no view dependent). However I still need to do the same to Spherical Envionment Mapping code (both openGL and GLSL).
Do you have any thoughts on that?
Should they be “View angle Dependent” or not?
Links:
SEM and CubeMap tutorials: http://www.ozone3d.net/tutorials/glsl_texturing_p04.php
The CubeMap GLSL solution I came out with (blender file): http://blenderartists.org/forum/showthread.php?t=153493
gregd1024 said
Dalai – if you’re rendering objects based on how it would look to a single point-source camera, then specular and/or reflection effects do not depend on the camera’s viewing angle. The reason so many people think it is view angle dependent is because of what we see as human beings in the real world. Take any reflective object (like the back of a DVD for example), hold it in your hand and now rotate your head while keeping your eyes focused on the reflection. What is reflected does shift a little bit as you move your head back and forth. The reason that the reflection shifts is because your eyes are in the front of your head and not in the center of your neck (i.e. they don’t sit exactly on the axis of rotation) – so when the head rotates around the neck in 3D space, the eyeballs are not only rotated but also moved to different (x,y) or (x,z) positions by a small amount. Since your human “camera” (eyeballs) is being shifted to different positions in 3D space, the reflection must shift also. It is this often overlooked fact that’s making reflections/specular effects seem view angle dependent.
-Greg Dolley
Jaxxa said
Just found this.
Thanks this was the problem I was having and thanks for explaining why is is happening rather than just, use this code.