OpenGL超级宝典学习笔记——使用光源


使用光源

要模拟真实世界,仅有环境光是不够的,需要指定更多的光源来提升真实感。OpenGL至少提供8种光源,可以在场景中的任意位置甚至是可视区域之外。你可以指定光源位于无限远处以获得平行光束,或者光源位于近处向外发射光束。还可以制造出聚光灯的效果。

选择哪种方式?

你可以指定一个光源位于哪里朝哪个方向发射光线。通常光源朝所有方向发射光线,但也可以为它指定方向。在指定的方向的光源环境下,并非每个多边形都需要被照射到。我们可以制造物体的阴影效果。OpenGL可以计算光线和物体的角度。

如下图:光源发射出的光线照射到四边形上,形成一个入射角,再反射到观察者眼中形成一个反射角。根据这个入射角和反射角结合光源和材料的属性我们可以计算出该点应呈现出什么样的颜色。

image

在编程的角度上看,每一个多边形由一系列的顶点创建的。多边形就是由点组成的,那么如何计算光线与点之间的角度。我们无法在3D空间中找到一个切确的线与点之间的角度,因此我们为每一个点设置一个法线。三维平面的法线是垂直于该平面的三维向量。曲面在某点P处的法线为垂直于该点切平面的向量。(wiki)

平面法线

法线向量是一条垂直于真实平面或虚拟平面的线。下图是2D和3D中的法线向量。

image

为什么需要为每个顶点设置法线向量,而不是为一个多边形指定一个法线向量。其中的原因是,并不是所有的物体的表面都是平的,有时法线向量并不需要精确地垂直于物体的表面。通过扭曲平面的法线向量可以制造出光滑的曲面的视觉效果。

指定法线

image

如上图:为多边形的顶点(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度角的方向。如下图:

Image[1]

向量的方向告诉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)求单位法线,先求出法线向量的长度即image,再用(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空间中任意多边形的法线。

image

可以通过多边形上的任意三个点来计算法线。

image

如上图,知道了P1,P2,P3点之后。由P1和P2可以求出向量V1,P1和P3可以求出向量V2,再用V1 X V2(叉乘)计算得出正交于V1和V2的法线向量。

image

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;
}

效果

image

OpenGL超级宝典 第4版 中文版PDF+英文版+源代码 见 

OpenGL编程指南(原书第7版)中文扫描版PDF 下载

OpenGL 渲染篇

Ubuntu 13.04 安装 OpenGL

OpenGL三维球体数据生成与绘制【附源码】

Ubuntu下OpenGL编程基础解析

如何在Ubuntu使用eclipse for c++配置OpenGL  

更多《OpenGL超级宝典学习笔记》相关知识 见 

相关内容