2009年6月9日 星期二

影子傳說之Planar Shadow

玩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

1 則留言:

  1. OpenGL應該問大長老你的= =
    不然ARToolkit文章也不會生不出來

    回覆刪除

 

我們搬家了

新部落格在http://b-plurkers.com/