玩3D遊戲時,影子的真實度,往往是讓遊戲更顯真實的方式之一。    
而目前的主流是Shadow Map,Shadow Volume跟Planar Shadow已經逐漸少用。     
從簡單的方法開始,逐步介紹影子的兩三事。
  Planar Shadow的原理很簡單,三度空間上的一個點v,空間中的一盞光源,方向為L,由v為起點,做出一條射線,方向為光源方向,會與平面P相交於一點v’,而這點就是光線對這點在平面上的投影,而我們要做的就是把點v’算出來。
  為了簡單說明原理,簡化了很多東西,光源是Directional Light,只有Diffuse Color,只有一個平面跟一個物體。
  由於v’在射線上,且v’在平面上,所以我們可以導出以下公式。 
    
     
  
由這公式我們可以算出 
   
     
  
這樣子我們就能把所有頂點的投射點算出來了。     
不過為了配合電腦硬體的設計,我們使用一個Homogeneous Matrix來幫助我們算出每個投射點。     
 
 
  用一個簡單的程式來示範Planar Shadow︰ (由於OpenGL是Column Major,跟我們習慣的書寫方式不同,所以我們需要他的轉置矩陣。)
   
  #include "glut.h"
#include <gl/GL.h>
#include <gl/GLU.h>
// plane equation
GLfloat planeequation[4] = {0, 1, 0, 0};
// light0 declaration
GLfloat lightdirection[4] = {0.577, 0.577, 0.577, 0};
GLfloat lightDiffuse[4] = {1.0, 1.0, 1.0, 1.0};
//
GLfloat g_planeDiffuse[4] = {1.0, 1.0, 1.0, 1.0};
GLfloat g_objectDiffuse[4] = {0.0, 1.0, 0.0, 1.0};
GLfloat shadow_color[4] = { 0.3f, 0.3f, 0.3f, 1.0f};
GLfloat shadow_color_matrix[4][4];
void generate_shadow_color_matrix(GLfloat matrix[4][4], 
                                  GLfloat light[4], GLfloat plane[4])
{
    GLfloat dot = 0;
    for (int i = 0; i < 4; i++) dot += light[i] * plane[i];
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
            if (i == j) {
                matrix[i][j] = dot - plane[i] * light[j];
            } else {
                matrix[i][j] = -plane[i] * light[j];
            }
}
void DrawObject()
{
    glBegin(GL_QUADS);
        glNormal3f(0, 1, 0);
        glVertex3f(2.5, 0, -2.5);
        glVertex3f(-2.5, 0, -2.5);
        glVertex3f(-2.5, 0, 2.5);
        glVertex3f(2.5, 0, 2.5);
    glEnd();
}
void Display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, (float *) &g_planeDiffuse);
    
    glBegin(GL_QUADS);
        glNormal3f(0, 1, 0);
            glVertex3f(5, 0, -5);
            glVertex3f(-5, 0, -5);
            glVertex3f(-5, 0, 5);
            glVertex3f(5, 0, 5);
    glEnd();
    glDisable(GL_LIGHT0);
    glDisable(GL_LIGHTING);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ZERO, GL_SRC_COLOR);
    glPushMatrix();
        glMultMatrixf((GLfloat*) shadow_color_matrix);
        glTranslatef(0, 2.5, 0);
        glColor3fv(shadow_color);
        DrawObject();
    glPopMatrix();
    glDisable(GL_BLEND);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, (float *) &g_objectDiffuse);
    glPushMatrix();
        glTranslatef(0, 2.5, 0);
        DrawObject();
    glPopMatrix();
    glDisable(GL_LIGHT0);
    glDisable(GL_LIGHTING);
    glutSwapBuffers();
}
void Reshape(int width, int height)
{
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    gluPerspective(60.0f, (GLfloat)width / (GLfloat)height,    0.5f, 50.0f);
} 
void Keyboard(unsigned char key, int x, int y)
{
    switch(key)
    {
    case 27:
        exit(0);
        break;
    }
}
void init()
{
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0, 10, 0, 0, 0, 0, 0, 0, 1);
    glLightfv(GL_LIGHT0, GL_POSITION, lightdirection);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
    generate_shadow_color_matrix(shadow_color_matrix, lightdirection, planeequation);
}
int main()
{
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowPosition(100,100);
    glutInitWindowSize(640, 640);
    glutCreateWindow("Planar shadow_color");
    glutDisplayFunc(Display);
    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Keyboard);
    init();
    glutMainLoop();
}
優點嘛,不需要額外硬體支援。 
  
缺點倒是不少,除了Shadow Map也會遇到的Z-Fighting問題,還有組合爆炸的問題﹔也就是說,假設有L個光源,M個平面,你上面的陰影繪圖動作就必須做L*M次;最大的問題是,你必需要有個平面,如果是球面,曲面是無效的。 
  
如有更深入的研究,可翻閱參考文件。
參考文件︰ 
  
1. 即時 3D 繪圖的陰影效果 
  
2. Shadow Projection in OpenGL