News:
2004-06-22: The paper was presented at the Eurographics Symposium on Rendering.
2004-06-15: I have created a web page for the
paper (demo + sourcecode + videos +
images)
(http://www.cg.tuwien.ac.at/research/vr/lispsm/index.html).
2004-06-08: The paper Light Space Perspective
Shadow Maps is
finished
(http://www.cg.tuwien.ac.at/research/vr/lispsm/wimmer-egsr04-lispsm.pdf).
Abstract:
shadow maps are the common source for shadows in detail rich environments. recent approaches, like perspective shadow maps (http://www-sop.inria.fr/reves/) have emphasized the importance of a view depend technique which results in increased shadow map resolution near the eye view point. however the implementation of such techniques, namely before mentioned perspective shadow maps, has been proven to be difficult and error prone. one possible cause for the problems was the use of a post-perspective-space to calculate the light matrices. in this space singularities and great image distortions can cause severe complications and reduce the ability to understand, even simple, relationships.
therefor i propose a slightly different approach: light space optimized shadow maps (liso-sm). this approach tries to increase the utilization of the square shadow map buffer with geometric transformations in the light space. the goal is to transform the projected eye view frustum into a nearly cubic shape in the light space.
Overview:
if you look at a scene from the point of view of a directional light source(white transparent trapezoid is the current eye view frustum) you get something like this:

most
of the space of the shadow map buffer is wasted.
to increase the utilization of the shadow map buffer you can take the world view frustum coordinates and transform them to the light space and use the extreme points of the transformed coordinates to set up the orthogonal light projection, which results in something like this:

optimization
of the shadow map buffer has increased, but is still not very good.
the next step is to use a practical up vector for the light view transformation. when i rotate into the light direction i can choose my up vector freely, so i decided to use one who is perpendicular to the view direction and the light direction:
(lx,ly,lz) dot (vx,vy,vz) = 0 doesn't work
(lx,ly,lz) dot (vx,y,vz) = 0 so i used this form and calculated y
lx*vx+ly*y+lz*vz = 0 -> y = (-lz*vz-lx*vx)/ly in code this leads to the up vector:
const Vector3& up = Vector3(viewDir.x,(-lightDir.z*viewDir.z-lightDir.x*viewDir.x)/lightDir.y,viewDir.z);
which leads to this render result:

it
is obvious that we want to use the lower part of the shadow map
buffer. so what we need to do is to enlarge the lower part in x
direction. this step increases the shadow map buffer utilization and
additionally gives us an increased resolution near the eye view
point.
The Algorithm:
we need a matrix like
[1 0 0 0]
[0 0 0 1]
[0 0 1 0]
[0 1 0 0]
to use the perspective division to make a mapping of
[ x y z 1] -Matrix-> [x 1 z y] -perspective division-> [x/y 1/y z/y 1]
which doesn't work.
so we need a more exact approach:
we are now in the orthogonal space after the light view transformation and the light orthogonal matrix, so the x,y and z coordinates are ranging from [-1,1] to use a division we must get rid of the singularity at 0.
so first we need to scale and translate the y coordinate:
[1 0 0 0]
[0 a 0 b]
[0 0 1 0]
[0 0 0 1]
to map from [-1,1] to [n,f]
y' = a*y+b use the mapping prerequisites -> n = a*-1+b -> a=b-n and f=(b-n)*f+b -> b = (f+n)/2 -> a = (f-n)/2
->
[1 0 0 0]
[0 (f-n)/2 0 (f+n)/2]
[0 0 1 0]
[0 0 0 1]
as the translateScaleYmatrix
after this step we can use a perspective projection matrix for the y coordinate:
[1 0 0 0]
[0 (f+n)/(f-n) 0 -2*f*n/(f-n)]
[0 0 1 0]
[0 1 0 0]
the result of all this trouble is a very narrow stripe in the middle of the screen. now we needto reenlarge this stripe in x direction with a standard scaleMatrix(x=f,y=1,z=1) to make it fit the square shadow map.
with n=1 and f=30 this leads to (as seen from the light source)

the
shadow buffer utilization hasn't increased much, but the increase in
shadow mapping quality is tremendous. this shows the previous result
(as seen from the eyes point of view):

and
the result with the additional matrices and n=1 and f=30

the setup code so far:
//choose a “good” up vector
const Vector3& up = Vector3(viewDir.x,(-lightDir.z*viewDir.z-lightDir.x*viewDir.x)/lightDir.y,viewDir.z);
//stay at center but choose a valid eye point
shadow.mtxLightView.rotateAt(lightDir,up);
//take the world coordinates of the view frustum and transform them to light space
Vector3x8 v;
pmv.calcViewFrustumWorldCoord(v);
for(uint i = 0; i < 8; i++) {
v[i] = shadow.mtxLightView.mulIgnoreHomogen(v[i]);
}
//make a AABB box out of the light space view frustum coordinates
AABox eyeBox(v[0],v[0]);
for(i = 1; i < 8; i++) {
eyeBox.expandToContain(v[i]);
}
const Vector3& min = eyeBox.getMin();
const Vector3& max = eyeBox.getMax();
const Real f = 30;
const Real n = 1;
//setup an orthogonal projection with the borders of the AABB of the view frustum coordinates
Matrix4 ort = Matrix4().ortho(min.x,max.x,min.y,max.y,min.z,max.z);
Matrix4 scaleTransY = Matrix4::IDENTITY;
scaleTransY.a22 = (f-n)/2;
scaleTransY.a24 = (f+n)/2;
Matrix4 shearY = Matrix4::ZERO;
shearY.a11 = 1;
shearY.a22 = (f+n)/(f-n);
shearY.a24 = -2*f*n/(f-n);
shearY.a33 = 1;
shearY.a42 = 1;
Matrix4& l = (shadow.mtxLightProjection = Matrix4::IDENTITY);
l *= Matrix4().setScaleMatrix(f,1,1);
l *= shearY;
l *= scaleTransY;
l *= ort;
this works fine if the eye view frustum is centered as in the previous pictures, but what happens in this case?

the view point escapes after the scaling out of the screen and the near shadow values are lost. an quick solution for this problem is to use another translation just after the orthogonal projection to guarantee that the view point stays in the middle-bottom position (position = eye view point):
position = shadow.mtxLightView.mulIgnoreHomogen(position);
position = ort.mulIgnoreHomogen(position);
Matrix4 scaleTransY = Matrix4::IDENTITY;
scaleTransY.a22 = (f-n)/2;
scaleTransY.a24 = (f+n)/2;
Matrix4 shearY = Matrix4::ZERO;
shearY.a11 = 1;
shearY.a22 = (f+n)/(f-n);
shearY.a24 = -2*f*n/(f-n);
shearY.a33 = 1;
shearY.a42 = 1;
Matrix4& l = (shadow.mtxLightProjection = Matrix4::IDENTITY);
l *= Matrix4().setScaleMatrix(f,1,1);
l *= shearY;
l *= scaleTransY;
l *= Matrix4().setTranslationMatrix(Vector3(-position.x,0,-position.z));
l *= ort;
this method of improving the resolution of the standard shadow map is simple and very robust and contains much room for further improvements. A paper covering this approach should be finished real soon :)
links:
general perspective transformation between two planes:
http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/EPSRC_SSAZ/epsrc_ssaz.html
Daniel Scherzer , student, technical university Vienna
please send your email to: e9727072@student.tuwien.ac.at