OpenGL超级宝典学习笔记——镜面光与法线平均


光照效果

仅仅使用环境光和漫反射光的光照效果,喷气式飞机表面的颜色看起来比较平淡。在渲染木材,泥土,布料,纸箱上等这些表面粗糙的物体上,使用环境光和漫反射光的光照效果就基本足够了。但是在为光滑的金属物体建模时,为了使其显得更加逼真,仅仅使用环境光和漫反射光是不够的,还需要镜面光的效果。

镜面亮点

镜面光照和材料属性可以为物体表面添加光泽和亮斑的效果。当入射光与观察者的角度较小时,可以看到镜面加亮的效果。镜面亮点就是几乎所有的光照射在物体表面上并被反射开来。添加镜面光的成分:

GLfloat ambientLight = {0.3f, 0.3f, 0.3f, 1.0f};
Glfloat diffuseLight = {0.7f, 0.7f, 0.7f, 1.0f};
//specular light 
GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
....
//Enable Lighting
glEnable(GL_LIGHTING);
//set up and enable light0glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
glEnable(GL_LIGHT0);

specular[]为镜面光成分指定了一个非常亮的白光。模拟太阳当空照的效果微笑。下面的语句就是指定镜面光的成分。

glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

仅仅添加镜面光的效果,我们不会再喷气式飞机上看到什么变换。我们还需要为其指定材料的镜面光反射属性。

镜面发射

给材料添加镜面反射属性的代码段如下:

GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};
....//Enable color tracking 
glEnable(GL_COLOR_MATERIAL);
//set material properties to follow 
 glColorglColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
//使后面的材料具有完全的镜面反射效果以及强光泽 
glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
glMateriali(GL_FRONT, GL_SHININESS, 128);
代码在设置镜面反射属性之前,为材料的环境光和漫反射光属性开启颜色追踪。材料的镜面光反射属性我们另外单独指定了一个固定不变的值。指定材料的镜面光反射属性为(1.0f, 1.0f, 1.0f, 1.0f)代表着完全发射入射的镜面光。在glMaterialfv(GL_FRONT, GL_SPECULAR, specref);之后的多边形将采用此材料属性。 在没有重新调用glMaterial之前,所有的材料都将具有此属性。  

镜面指数

在上面的例子中在强烈的镜面光照射和材料镜面反射(完全发射)的效果下导致飞机显示为纯白,只有远离光源的表面除外(未受到光照,显示黑色)。

glMateriali(GL_FRONT, GL_SHININESS, 128);

GL_SHININESS属性设置材料的镜面指数,指定了镜面加亮的大小和集中性。如果指定为0,即没有聚焦,即整个多边形均匀加亮。通过设置这个值可以缩小镜面加亮的范围,增加镜面加亮的集中度,制造亮点的效果。这个值越小,表示材质越粗糙,点光源发射的光线照射到上面,产生较大的亮点。这个值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。这个参数值的范围为1-128。

设置为128的效果如下:

image

设置为0时效果如下:(被照射到的多边形整个都加亮了,没被照射到的显示黑色)

image

设置环境的代码如下:
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 };  
  GLfloat  specular[] = {1.0f, 1.0f, 1.0f, 1.0f};  
  glEnable(GL_DEPTH_TEST);   
  // Hidden surface removal  
  glFrontFace(GL_CCW);        
  // Counter clock-wise polygons face out  
  glEnable(GL_CULL_FACE);        
 
  // Enable lighting  
  glEnable(GL_LIGHTING);  
  // Setup and enable light 0  
  glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);  
  glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight);  
  glLightfv(GL_LIGHT0, GL_SPECULAR, specular);  
  glEnable(GL_LIGHT0);  
  GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};  
  // Enable color tracking  
  glEnable(GL_COLOR_MATERIAL);  
  // Set Material properties to follow glColor values  
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);    
  glMaterialfv(GL_FRONT, GL_SPECULAR, specref);  
  glMateriali(GL_FRONT, GL_SHININESS, 128);  
  // Light blue background  
  glClearColor(0.0f, 0.0f, 1.0f, 1.0f );  
  glEnable(GL_NORMALIZE);
}

法线平均

image

由四边形和三角形组成的球体,如果为其的每一个表面都单独指定了法线,那这个球体看起来就是充满棱角。如果我们为每个顶点指定“真实的”法线,那么OpenGL光照计算的所产生的值将会平滑地分布在多边形表面上。这些平面的多边形经过着色之后就像平滑的表面一样。

在理论上,如果我们使用足够多的多边形来绘制球体,那球体表面就会显得平滑,这些多边形就近似于真实的表面。就像使用足够多的小短线来模拟平滑的曲线一样。如果把每个顶点都当成真实的表面上的顶点的话,那么这个表面的实际法线值就代表着真实表面的法线。在球体中,法线从球体的中心指向各个顶点。

下图图5.30中的,每一个平面片段都有法线垂直指向它的表面。就像在喷气式飞机中的例子一样。在图5.31中法线并不正交于多边形的平面,而是正交于真实球体的表面,或者球体表面的切线。

image

在球体中,计算真实法线较为简单,球体中心和多边形顶点的连线即是。但在一些复杂的物体中,就没那么简单了。需要取得共享一个顶点的多边形的法线,对其进行平均,来获得更加真实的效果。

综合例子

设置光源位置

//光源位置 GLfloat lightPos[] = {0.0f, 0.0f, 70.0f, 1.0f};
..
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

lightPos数组的前三个值x,y,z指定了光源的位置或者方向。如果最后一个值为1.0,说明光源的真实位置就位于lightPos上,是位置性光源,光线从光源发散开来。如果最后一个值为0.0则代表光在无限远处,光源是方向性光源,所有光线是平行的。  

创建聚光灯

GLfloat ambientLight[] = {0.5f, 0.5f, 0.5f, 1.0f};
GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};
//光源位置 
GLfloat lightPos[] = {0.0f, 0.0f, 70.0f, 1.0f};
//聚光灯光照方向朝z轴负方向 
GLfloat spotDir[] = {0.0f, 0.0f, -1.0f};
void SetupRC()
{  
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  
  //开启深度测试  
  glEnable(GL_DEPTH_TEST);  
 
  //剔除掉不必要的背面  
  glFrontFace(GL_CCW);  
  glEnable(GL_CULL_FACE);  
  glCullFace(GL_BACK);  
 
  //开启光照  
  glEnable(GL_LIGHTING);  
 
  //设置light0光照参数  
  glLightfv(GL_LIGHT0, GL_AMBIENT_AND_DIFFUSE, ambientLight);  
  glLightfv(GL_LIGHT0, GL_SPECULAR, specular);  
 
  //设置光源位置和方向  
  glLightfv(GL_LIGHT0, GL_POSITION, lightPos);  
  glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);  
  glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0f);  
 
  //设置全局环境光  
  glLightModelfv(GL_AMBIENT, ambientLight);  
  //开启light0  
  glEnable(GL_LIGHT0);    
 
  //开启和设置颜色追踪  
  glEnable(GL_COLOR_MATERIAL);  
 
  //设置多边形正面的材料的环境光和漫反射光属性为颜色追踪  
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); 
 
  //设置镜面光的反射属性  
  glMaterialfv(GL_FRONT, GL_SPECULAR, specref);  
  glMateriali(GL_FRONT, GL_SHININESS, 128);  
}

glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0f);设置聚光灯的半夹角,使得聚光灯发射出一个光锥。在此光锥之外的物体不会被照射到。 image
 

在这个例子中,我们使用一个黄色的小灯泡的方式来模拟光源的位置,通过方向键可以调整其位置。并通过右键菜单来调整,物体的着色模式,和球体的近似程度(由多少多边形组成球体)。

image

GL_FLAT着色模式

image

GL_SMOOTH着色模式

image

完整代码示例:

#include "gltools.h"
GLfloat ambientLight[] = {0.5f, 0.5f, 0.5f, 1.0f};
GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};
//光源位置 
GLfloat lightPos[] = {0.0f, 0.0f, 70.0f, 1.0f}; 
//聚光灯光照方向朝z轴负方向
GLfloat spotDir[] = {0.0f, 0.0f, -1.0f}; 
//用于旋转 
static GLfloat xRot = 0.0f; 
static GLfloat yRot = 0.0f; 
#define FLAT_MODE 1 
#define SMOOTH_MODE 2 
#define LOW_LEVEL 3 
#define MEDIUM_LEVEL 4 
#define HIGH_LEVEL 5 
int iShade = FLAT_MODE; 
int iLevel = LOW_LEVEL; 
void SetupRC()
{
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 
  //开启深度测试 
  glEnable(GL_DEPTH_TEST); 
  //剔除掉不必要的背面 
  glFrontFace(GL_CCW);
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK); 
  //开启光照 
  glEnable(GL_LIGHTING); 
  //设置light0光照参数 
  glLightfv(GL_LIGHT0, GL_AMBIENT_AND_DIFFUSE, ambientLight);
  glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
  //设置光源位置和方向 
  glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
  glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);
  glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0f); 
  //设置全局环境光 
  glLightModelfv(GL_AMBIENT, ambientLight); 
  //开启light0 
  glEnable(GL_LIGHT0); 
  //开启和设置颜色追踪 
  glEnable(GL_COLOR_MATERIAL); 
  //设置多边形正面的材料的环境光和漫反射光属性为颜色追踪 
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); 
  //设置镜面光的反射属性 
  glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
  glMateriali(GL_FRONT, GL_SHININESS, 128);

  glEnable(GL_NORMALIZE);
} 

void RenderScene()
{ if (iShade == FLAT_MODE)
  {
    glShadeModel(GL_FLAT);
  } else {
    glShadeModel(GL_SMOOTH);
  }
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
  //矩阵压栈 
  glPushMatrix();

    glRotatef(xRot, 1.0f, 0.0f, 0.0f);
    glRotatef(yRot, 0.0f, 1.0f, 0.0f); 
   //旋转后重新设置光源位置
   glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);

    glTranslatef(lightPos[0], lightPos[1], lightPos[2]);
    glColor3ub(255, 0, 0);
    glutSolidCone(4.0, 6.0, 15, 15);

    glPushAttrib(GL_LIGHTING_BIT); 
   //关闭光照画小黄球 
      glDisable(GL_LIGHTING);
      glColor3ub(255, 255, 0);
      glutSolidSphere(3.0, 15, 15);
    glPopAttrib(); 
	//矩阵出栈
	glPopMatrix();
  glColor3ub(0, 0, 255); 
  if (iLevel == LOW_LEVEL)
  {
    glutSolidSphere(30.0f, 8, 8);
  } else if (iLevel == MEDIUM_LEVEL)
  {
    glutSolidSphere(30.0f, 10, 10);
  } else {
    glutSolidSphere(30.0f, 15, 15);
  }

  glutSwapBuffers();
}

 void ChangeSize(int w, int h)
{ 
if (h == 0)
  {
    h = 1;
  } 
  //设置视口 
  glViewport(0, 0, w, h);
  GLfloat faspect = (GLfloat)w/(GLfloat)h; 
  //透视投影变换 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(35.0, faspect, 1.0, 350.0); 
  //模型视图矩阵 
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity(); 
  //先往显示器里平移 
  glTranslatef(0.0f, 0.0f, -250.0f);

  glutPostRedisplay();
} 

void SpecialKeys(int keys, int x, int y)
{ 
//改变旋转角度 
  if (keys == GLUT_KEY_UP)
  {
    xRot -= 5.0f;
  } if (keys == GLUT_KEY_DOWN)
  {
    xRot += 5.0f;
  } if (keys == GLUT_KEY_LEFT)
  {
    yRot += 5.0f;
  } if (keys == GLUT_KEY_RIGHT)
  {
    yRot -= 5.0f;
  } if (xRot > 356.0f)
  {
    xRot = 0.0f;
  } else if (xRot < -1.0f)
  {
    xRot = 355.0f;
  } if (yRot > 356.0f)
  {
    yRot = 0.0f;
  } else if (yRot < -1.0f)
  {
    yRot = 355.0f;
  }

  glutPostRedisplay();
} 

void ProcessMenu(int key)
{ 
switch(key)
  { case 1:
    iShade = FLAT_MODE; break; case 2:
    iShade = SMOOTH_MODE; break; case 3:
    iLevel = LOW_LEVEL; break; case 4:
    iLevel = MEDIUM_LEVEL; break; case 5:
    iLevel = HIGH_LEVEL; break; default: break;
  }
  glutPostRedisplay();
} 

int main(int args, char *arv[])
{
  glutInit(&args, arv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(800, 600);
  glutCreateWindow("spot light");
  glutDisplayFunc(RenderScene);
  glutReshapeFunc(ChangeSize);
  glutSpecialFunc(SpecialKeys); 
  //创建菜单
  int menuID = glutCreateMenu(ProcessMenu);
  glutAddMenuEntry("Flag Mode", 1);
  glutAddMenuEntry("SMOOTH Mode", 2);
  glutAddMenuEntry("Low Level", 3);
  glutAddMenuEntry("Midum Level", 4);
  glutAddMenuEntry("High Level", 5);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
  SetupRC();
  glutMainLoop(); 
  return 0;
}

 

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

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

OpenGL 渲染篇

Ubuntu 13.04 安装 OpenGL

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

Ubuntu下OpenGL编程基础解析

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

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

相关内容