要模擬真實世界,僅有環境光是不夠的,需要指定更多的光源來提升真實感。OpenGL至少提供8種光源,可以在場景中的任意位置甚至是可視區域之外。你可以指定光源位於無限遠處以獲得平行光束,或者光源位於近處向外發射光束。還可以制造出聚光燈的效果。
你可以指定一個光源位於哪裡朝哪個方向發射光線。通常光源朝所有方向發射光線,但也可以為它指定方向。在指定的方向的光源環境下,並非每個多邊形都需要被照射到。我們可以制造物體的陰影效果。OpenGL可以計算光線和物體的角度。
如下圖:光源發射出的光線照射到四邊形上,形成一個入射角,再反射到觀察者眼中形成一個反射角。根據這個入射角和反射角結合光源和材料的屬性我們可以計算出該點應呈現出什麼樣的顏色。
在編程的角度上看,每一個多邊形由一系列的頂點創建的。多邊形就是由點組成的,那麼如何計算光線與點之間的角度。我們無法在3D空間中找到一個切確的線與點之間的角度,因此我們為每一個點設置一個法線。三維平面的法線是垂直於該平面的三維向量。曲面在某點P處的法線為垂直於該點切平面的向量。(wiki)
法線向量是一條垂直於真實平面或虛擬平面的線。下圖是2D和3D中的法線向量。
為什麼需要為每個頂點設置法線向量,而不是為一個多邊形指定一個法線向量。其中的原因是,並不是所有的物體的表面都是平的,有時法線向量並不需要精確地垂直於物體的表面。通過扭曲平面的法線向量可以制造出光滑的曲面的視覺效果。
如上圖:為多邊形的頂點(1,1,0)處指定一個法線向量,我們可以選定一另個點(1,10,0),並以(1,1,0)為起點連接第二個點(1,10,0),所形成的線就是法線向量。第二個點說明了法線向量的方向是y軸向上。法線向量的方向還可以用於表明多邊形的正面和反面。法線是由正面指向外面的(或者說從法線的箭頭方向往下看到的就是多邊形的正面)。
用法線的第二個點減去第一個頂點,所得的結果就是相對於x、y和z的單位距離。
(1,10,0) - (1,1,0) = (0,9,0)
如果把這個頂點移動到原點,上面兩點相減得出的點指定了和表面呈90度角的方向。如下圖:
向量的方向告訴OpenGL頂點所在多邊形的面朝哪個方向的。下面的例子演示如何指定法線向量
glBegin(GL_TRIANGLES);
glNormal3f(0.0f, -1.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 60.0f);
glVertex3f(-15.0f, 0.0f, 30.0f);
glVertex3f(15.0f, 0.0f, 30.0f);
glEnd();
glNormal3f接受3個表示坐標的值,指定一條垂直於三角形表面的法線向量。上面的例子的三個頂點的法線具有相同的方向。
單位法線即長度為1的法線向量。已知法線向量(x,y,z)求單位法線,先求出法線向量的長度即,再用(x,y,z)除以這個長度,就得到單位法線。這個過程叫歸一化(normalization)。在光照計算的中所有的法線向量都會歸一化。
可以讓OpenGL自動把法線向量轉換為單位法線,通過調用glEnable接受一個參數GL_NORMALIZE;
glEnable(GL_NORMALIZE);
這種做法在某些實現上,會帶來性能的開銷。最好的方式在指定法線向量的時候,就直接指定為單位法線,而不是依靠OpenGL來幫你做轉換。
PS:glScale縮放函數也會縮放法線的長度。如果一起使用glScale和光照,有可能會得到不是你想要的光照效果。
如果每個頂點指定的法線都是單位法線,並且glScale使用相同的比例進行縮放的情況下,有一個替代方案是使用GL_RESCALE_NORMALS替代GL_NORMALIZE.
glEnable(GL_RESCALE_NORMALS);
這樣就告訴了OpenGL,你的法線向量不是單位長度的,但是可以通過相同比例因子的縮放來把它轉化為單位長度(單位法線)。這樣OpenGL會檢查你的模型視圖變換矩陣來逆轉換。這樣在每個頂點所做的數學操作要少一些。
個人理解:
一個單位法線向量(1.0,1.0,1.0),在進行過glScalef(2.0, 2.0, 2.0)之後,就變為了(2.0,2.0,2.0),那麼在啟用了GL_RESCALE_NORMALS之後,OpenGL會檢查模型視圖矩陣得知,可以通過反向的縮放把法線向量轉換會單位法線即等比例的縮小2倍乘以0.5.又得到了(1.0, 1.0, 1.0)。
PS:最佳實踐是在一開始為頂點指定法線時,就指定為單位法線。
當一個多邊形不平行於任何一個軸面時,通過簡單的觀察來指定一個法線會比較困難。所以需要一個簡單的方法來計算3D空間中任意多邊形的法線。
可以通過多邊形上的任意三個點來計算法線。
如上圖,知道了P1,P2,P3點之後。由P1和P2可以求出向量V1,P1和P3可以求出向量V2,再用V1 X V2(叉乘)計算得出正交於V1和V2的法線向量。
math3d代碼示例:
void m3dFindNormal(M3DVector3f vNormal, const M3DVector3f vP1, const M3DVector3f vP2, const M3DVector3f vP3)
{
M3DVector3f v1, v2;
v1[0] = vP2[0] - vP1[0];
v1[1] = vP2[1] - vP1[1];
v1[2] = vP2[2] - vP1[2];
v2[0] = vP3[0] - vP1[0];
v2[1] = vP3[1] - vP1[1];
v2[2] = vP3[2] - vP1[2];
m3dCrossProduct(vNormal, v1, v2);
}
創建一個位於左上角的溫和的白光光源。
GLfloat ambientLight[] = {0.3f, 0.3f, 0.3f, 1.0f};
GLfloat diffuseLight[] = {0.7f, 0.7f, 0.7f, 1.0f};
//設置和開啟光源light0
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
//開啟光照
glEnable(GL_LIGHT0);
//設置光源位置
GLfloat lightPos[] = {-50.f, 50.f, 100.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
lightPos中最後一個值1.0說明這個lightPos指定了光源的位置,如果這個值是0.0,則指定了光源在無限遠處。
開啟顏色追蹤
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
...噴氣式飛機的完整代碼:
...
{
M3DVector3f vPoints[3] = {{ 15.0f, 0.0f, 30.0f},
{ 0.0f, 15.0f, 30.0f},
{ 0.0f, 0.0f, 60.0f}};
// 計算法線向量
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
...
...
// LitJet.cpp
// OpenGL SuperBible
// Demonstrates OpenGL Lighting
// Program by Richard S. Wright Jr.
#include "gltools.h" // gltools library
#include "math3d.h" // 3D Math Library
// Rotation amounts
static GLfloat xRot = 0.0f;
static GLfloat yRot = 0.0f;
// Called to draw scene
void RenderScene(void)
{
M3DVector3f vNormal; // Storeage for calculated surface normal
// Clear the window with current clearing color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Save the matrix state and do the rotations
glPushMatrix();
glRotatef(xRot, 1.0f, 0.0f, 0.0f);
glRotatef(yRot, 0.0f, 1.0f, 0.0f);
// Nose Cone - Points straight down
// Set material color
glColor3ub(128, 128, 128);
glBegin(GL_TRIANGLES);
glNormal3f(0.0f, -1.0f, 0.0f);
glNormal3f(0.0f, -1.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 60.0f);
glVertex3f(-15.0f, 0.0f, 30.0f);
glVertex3f(15.0f,0.0f,30.0f);
// Verticies for this panel
{
M3DVector3f vPoints[3] = {{ 15.0f, 0.0f, 30.0f},
{ 0.0f, 15.0f, 30.0f},
{ 0.0f, 0.0f, 60.0f}};
// Calculate the normal for the plane
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{ 0.0f, 0.0f, 60.0f },
{ 0.0f, 15.0f, 30.0f },
{ -15.0f, 0.0f, 30.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
// Body of the Plane ////////////////////////
{
M3DVector3f vPoints[3] = {{ -15.0f, 0.0f, 30.0f },
{ 0.0f, 15.0f, 30.0f },
{ 0.0f, 0.0f, -56.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{ 0.0f, 0.0f, -56.0f },
{ 0.0f, 15.0f, 30.0f },
{ 15.0f,0.0f,30.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
glNormal3f(0.0f, -1.0f, 0.0f);
glVertex3f(15.0f,0.0f,30.0f);
glVertex3f(-15.0f, 0.0f, 30.0f);
glVertex3f(0.0f, 0.0f, -56.0f);
///////////////////////////////////////////////
// Left wing
// Large triangle for bottom of wing
{
M3DVector3f vPoints[3] = {{ 0.0f,2.0f,27.0f },
{ -60.0f, 2.0f, -8.0f },
{ 60.0f, 2.0f, -8.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{ 60.0f, 2.0f, -8.0f},
{0.0f, 7.0f, -8.0f},
{0.0f,2.0f,27.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{60.0f, 2.0f, -8.0f},
{-60.0f, 2.0f, -8.0f},
{0.0f,7.0f,-8.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{0.0f,2.0f,27.0f},
{0.0f, 7.0f, -8.0f},
{-60.0f, 2.0f, -8.0f}};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
// Tail section///////////////////////////////
// Bottom of back fin
glNormal3f(0.0f, -1.0f, 0.0f);
glVertex3f(-30.0f, -0.50f, -57.0f);
glVertex3f(30.0f, -0.50f, -57.0f);
glVertex3f(0.0f,-0.50f,-40.0f);
{
M3DVector3f vPoints[3] = {{ 0.0f,-0.5f,-40.0f },
{30.0f, -0.5f, -57.0f},
{0.0f, 4.0f, -57.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{ 0.0f, 4.0f, -57.0f },
{ -30.0f, -0.5f, -57.0f },
{ 0.0f,-0.5f,-40.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{ 30.0f,-0.5f,-57.0f },
{ -30.0f, -0.5f, -57.0f },
{ 0.0f, 4.0f, -57.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{ 0.0f,0.5f,-40.0f },
{ 3.0f, 0.5f, -57.0f },
{ 0.0f, 25.0f, -65.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{ 0.0f, 25.0f, -65.0f },
{ -3.0f, 0.5f, -57.0f},
{ 0.0f,0.5f,-40.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
{
M3DVector3f vPoints[3] = {{ 3.0f,0.5f,-57.0f },
{ -3.0f, 0.5f, -57.0f },
{ 0.0f, 25.0f, -65.0f }};
m3dFindNormal(vNormal, vPoints[0], vPoints[1], vPoints[2]);
glNormal3fv(vNormal);
glVertex3fv(vPoints[0]);
glVertex3fv(vPoints[1]);
glVertex3fv(vPoints[2]);
}
glEnd();
// Restore the matrix state
glPopMatrix();
// Display the results
glutSwapBuffers();
}
// This function does any needed initialization on the rendering
// context.
void SetupRC()
{
// Light values and coordinates
GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, 1.0f };
GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, 1.0f };
glEnable(GL_DEPTH_TEST); // Hidden surface removal
glFrontFace(GL_CCW); // Counter clock-wise polygons face out
glEnable(GL_CULL_FACE); // Do not calculate inside of jet
// Enable lighting
glEnable(GL_LIGHTING);
// Setup and enable light 0
glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);
glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight);
glEnable(GL_LIGHT0);
// Enable color tracking
glEnable(GL_COLOR_MATERIAL);
// Set Material properties to follow glColor values
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
// Light blue background
glClearColor(0.0f, 0.0f, 1.0f, 1.0f );
glEnable(GL_NORMALIZE);
}
/////////////////////////////////////////////////////
// Handle arrow keys
void SpecialKeys(int key, int x, int y)
{
if(key == GLUT_KEY_UP)
xRot-= 5.0f;
if(key == GLUT_KEY_DOWN)
xRot += 5.0f;
if(key == GLUT_KEY_LEFT)
yRot -= 5.0f;
if(key == GLUT_KEY_RIGHT)
yRot += 5.0f;
if(key > 356.0f)
xRot = 0.0f;
if(key < -1.0f)
xRot = 355.0f;
if(key > 356.0f)
yRot = 0.0f;
if(key < -1.0f)
yRot = 355.0f;
// Refresh the Window
glutPostRedisplay();
}
//////////////////////////////////////////////////////////
// Reset projection and light position
void ChangeSize(int w, int h)
{
GLfloat fAspect;
GLfloat lightPos[] = { -50.f, 50.0f, 100.0f, 1.0f };
// Prevent a divide by zero
if(h == 0)
h = 1;
// Set Viewport to window dimensions
glViewport(0, 0, w, h);
// Reset coordinate system
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
fAspect = (GLfloat) w / (GLfloat) h;
gluPerspective(45.0f, fAspect, 1.0f, 225.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLightfv(GL_LIGHT0,GL_POSITION,lightPos);
glTranslatef(0.0f, 0.0f, -150.0f);
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800,600);
glutCreateWindow("Lighted Jet");
glutReshapeFunc(ChangeSize);
glutSpecialFunc(SpecialKeys);
glutDisplayFunc(RenderScene);
SetupRC();
glutMainLoop();
return 0;
}
效果
OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見 http://www.linuxidc.com/Linux/2013-10/91413.htm
OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm
OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm
Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm
OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm
Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm
如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm
更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581