I have decided that since my game world is mostly flat I don't need better shadows than geometric projections - at least for now.

The only problem is I don't even know how to do those properly - that is to produce a 4x4 matrix which would render shadows for my objects (that is, I guess, project them on a horizontal XZ plane).

I would like a light source at infinity (e.g., the sun at some point in the sky) and thus parallel projection.

My current code does something that looks almost right for small flying objects, but actually is a very rude approximation, as it doesn't project the objects onto the ground, but simply moves them there (I think). Also it always wrongly assumes the sun is always on the zenith (projecting straight down).

    Gdx.gl20.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);


    for (ShellState state : shellStates.values()) {

        shader.setUniformMatrix("u_worldView", transform); 
        shader.setUniformi("u_texture", 0);

        shellMesh.render(shader, GL10.GL_TRIANGLES);        

// shadows
    for (ShellState state : shellStates.values()) {

        m4.translate(0, -v3.y + 0.5f, 0); // TODO HACK: + 0.5f is a hack to ensure the shadow appears above the ground; this is overall a hack as we are just moving the shell to the surface instead of projecting it on the surface!

        shader.setUniformMatrix("u_worldView", transform); 
        shader.setUniformi("u_texture", 0); // TODO: make shadow black somehow

        shellMesh.render(shader, GL10.GL_TRIANGLES);        


So my questions are:

a) What is the proper way to produce a Matrix4 to pass to openGL which would render the shadows for my objects?

b) I am supposed to use another fragment shader for the shadows which would paint them in semi-transparent grey, correct?

c) The limitation of this simplistic approach is that whenever there is some object on the ground (it is not flat) the shadows will not be drawn, correct?

d) Do I need to add something very small to the y (up) coordinate to avoid z-fighting with ground textures? Or is the fact they will be semi-transparent enough to resolve that problem?

  • 2
    \$\begingroup\$ You might find it easier, more efficient, and more correct to just use good ol' shadow mapping here. You can use a single downward-facing directional light (hardcode the math right into your shadow shadow if you want) and render all relevant shadow casting objects using the appropriate light-view matrix, and then you've got a ready-to-use shadow map. It'll deal with self-shadowing, shadows over objects on the ground, etc., and probably (profile to be sure) will be faster than computing and rendering individual geometrical shadows. \$\endgroup\$ – Sean Middleditch Dec 4 '12 at 22:25

(a) To produce a "squash" matrix that smashes things to the ground plane parallel to a given sun vector, I would build it by composing two matrices:

  1. A shear matrix that maps the sun vector to +Y (straight up) while leaving the X and Z axes unchanged.
  2. A scaling matrix that scales Y by zero while leaving X and Z unchanged.

Specifically, assuming you're using column vectors (since that's the usual OpenGL convention), after multiplying, this comes out to be:

[ 1 (-sun.x / sun.y) 0 0 ]
[ 0         0        0 0 ]
[ 0 (-sun.z / sun.y) 1 0 ]
[ 0         0        0 1 ]

where sun is the vector pointing toward the sun. I assume it is always in the upper half of the sky, i.e. sun.y > 0.

(b) There are various things you could do here. Drawing the squashed shadow geometry in semi-transparent black or gray is one possibility. If you do this, you should also use the stencil buffer to ensure that each pixel is darkened only once. (For instance, configure the stencil test to pass if stencil == 0, and write 1 to the stencil buffer when a shadow pixel is drawn.) Otherwise you'll get multiple darkening where there are multiple "layers" of the squashed object.

Another possibility is to do additive ambient and directional light passes, and use the stencil buffer to mask out the directional light whereever there are shadows.

(c) Correct.

(d) Yes, z-fighting is likely to be an issue here. You could add a small y-offset, or perhaps better, use hardware polygon offset.

| improve this answer | |
  • 1
    \$\begingroup\$ Great explanation and I made it (well, part a.) work within minutes. Thanks! \$\endgroup\$ – John M Dec 4 '12 at 9:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.