Table of Contents
When rendering using the OpenGL we try to get results as close as possible to the VRML 2.0 lighting equations and X3D lighting equations. We set OpenGL lights and materials properties to achieve the required look.
Note that there are bits when it is not possible to exactly match VRML 2.0 / X3D requirements with fixed-function rendering:
VRML 2.0 / X3D specify the spot light falloff by a
beamWidth
field. This cannot be precisely translated to a standard OpenGL spotlight exponent.Let α be the angle between the spot light's direction and the ray from spot light's position to the considered geometry point.
OpenGL spot light uses cosinus drop-off, which means that the light intensity within the spot
cutOffAngle
is calculated as a Cos(α)spotExponent.VRML 2.0 / X3D have a
beamWidth
. When α < beamWidth, the light intensity is constant (1.0). For larger angles, the intensity is linearly interpolated (down to 0.0) until angle reachescutOffAngle
.
There is just no sensible translation from
beamWidth
idea to OpenGLspotExponent
.An exception is the case when beamWidth >= cutOffAngle. Then spot has constant intensity, which has be accurately expressed with GL_SPOT_EXPONENT = 0. Fortunately, this is the default situation for all spot lights.
We have considered an extension to define
SpotLight.dropOffRate
as an extension for VRML >= 2.0 lights. With definition like “default value of dropOffRate = -1 means to try to approximate beamWidth, otherwise dropOffRate is used as an exponent”. But it didn't prove useful enough, especially since it would be our own extension.Looking at how other VRML/X3D implementations handle this:
Seems that most of them ignore the issue, leaving spot exponent always 0 and ignoring beamWidth entirely.
One implementation http://arteclab.artec.uni-bremen.de/courses/mixed-reality/material/ARToolkit/ARToolKit2.52vrml/lib/libvrml/libvrml97gl/src/vrml97gl/old_ViewerOpenGL.cpp checks beamWidth < cutOffAngle and sets spot_exponent to 0 or 1. This is what we were doing in engine versions <= 3.0.0.
Xj3D (see
src/java/org/web3d/vrml/renderer/ogl/nodes/lighting/OGLSpotLight.java
) setsGL_SPOT_EXPONENT
to 0.5 / beamWidth.It's not “more precise” in any way, the value 0.5 is just a "rule of thumb" as far as we know. But at least it allows to control exponent by
beamWidth
. This is an important advantage, as you can at least change the drop off rate by changing the beamWidth. Even if beamWidth is not interpreted following the specification, at least it's interpreted somehow, and allows to achieve a range of different effects.FreeWRL (see http://search.cpan.org/src/LUKKA/FreeWRL-0.14/VRMLRend.pm,
freewrl-1.22.13/src/lib/scenegraph/Component_Lighting.c
in later version) uses approach similar to Xj3D, settingGL_SPOT_EXPONENT
to 0.5 / (beamWidth + 0.1).For example, this results in
beamWidth = 0 => GL_SPOT_EXPONENT = 5
beamWidth = Pi/4 => GL_SPOT_EXPONENT =~ 0.5 / 0.9 =~ 1/2
beamWidth = Pi/2 => GL_SPOT_EXPONENT =~ 0.5 / 1.67 =~ 1/3
It's similar to Xj3D, and the +0.1 seems to be just to prevent division by (something close to) zero in case beamWidth is very very small. Unfortunately, this addition also limits the possible values of
GL_SPOT_EXPONENT
: it's at most 5 (0.5 / 0.1 = 5, as beamWidth must be > 0), and sometimes larger values would be useful.In our engine current version, we do it like this:
If beamWidth >= cutOffAngle, then GL_SPOT_EXPONENT is 0.
Otherwise we follow Xj3D version:
GL_SPOT_EXPONENT
is 0.5 / max(beamWidth, epsilon)
If you want to convert VRML 1.0
dropOffRate
to VRML 2.0 / X3DbeamWidth
precisely:If dropOffRate = 0, then leave beamWidth at default Pi/2. This makes beamWidth >= cutOffAngle (because cutOffAngle must be <= Pi/2 according to spec), which means no smooth falloff.
Otherwise beamWidth := 0.5 / (128 * dropOffRate) = 1 / (256 * dropOffRate).
The exponential fog of VRML 2.0 also uses different equations than OpenGL exponential fog and cannot be matched perfectly. See VRML and OpenGL specifications for details.
Fixed-function renderer uses Gouraud shading, with it's limitations.
Shader pipeline overcomes above problems. We program spot falloff
ourselves in GLSL, honoring beamWidth
correctly.
We also do per-pixel lighting calculation (Phong shading).
See lighting.
You can also use classic ray-tracer of our engine to see the correct lighting.
VRML/X3D lights are translated to the appropriate OpenGL calls using
the TGLRendererLights
class.
This is used internally by the TGLRenderer
discussed in next sections.
For now if you implement custom OpenGL rendering of 3D stuff,
for have to also implement custom handling of OpenGL lights.
(This is scheduled to be improved in engine 2.6.0, by making an instance
of TGLRendererLights
more widely available.)
When you render 3D models using our engine classes,
like TCastleScene
, everything related to lights
is automatically taken care of.
All lights (including the headlight, see https://castle-engine.io/x3d_extensions.php#section_ext_headlight)
can be described and animated inside the VRML/X3D model. Programmers
can also control lights by code.
Some useful things are TCastleSceneCore.HeadlightOn
and TCastleSceneCore.CustomHeadlight
to control the headlight
of given scene. You can also control headlight globally by overriding
the viewport and scene manager method TCastleAbstractViewport.Headlight
.
TCastleSceneCore.Attributes.UseSceneLights
controls normal
scene lights.
To use main scene lights on other 3D objects as well,
you have a comfortable TCastleAbstractViewport.UseGlobalLights
.
You can also override TCastleAbstractViewport.InitializeLights
.
For example, in games you may want to render various 3D things:
for example you have one mostly static 3D model for level
and various creature models. And it may be desirable to use level lights
for everything. Using TCastleAbstractViewport.UseGlobalLights = true
does this for you.
I use this technique in my games. For example see “The Castle” levels.