OPENGL学习笔记——光照

1、隐藏表面消除

隐藏表面消除就是消除实心物体被其他物体所遮挡住的部分,最简单的方法就是使用深度缓冲区。

深度缓冲区的原理是把一个距离观察平面(通常是近侧裁剪平面)的深度值与窗口中的每一个像素相关联。首先使用glClear()函数,把所有像素的深度值设置为最大可能的距离,然后在场景中以任意顺序绘制所有的物体。

深度缓冲区测试可能会影响应用程序的性能,隐藏表面消除丢弃了一些信息,而不是将它们用来绘图,因此会稍稍提高性能,但是深度缓冲区会大大影响性能,用“软件”实现的深度缓冲区(用处理器内存实现)要比专用的硬件缓冲区要慢的多。

2、OpenGL光照

在OpenGL的光照模型中,只有当存在能够吸引和反射光线的表面时,光源才会产生效果。OpenGL假设每个表面时由一种具有某些属性的材料组成。

  • 环境光(ambient light):来自所有的方向,当环境光撞击表面时,它会向所有方向均匀散开;
  • 散射光(diffuse light):来自某个方向,当散射光撞击表面时,它会向所有方向均有散开;
  • 镜面光(specular light):来自一个特定的方向,并且倾向于从表面向某个特定的方向反射;
  • 发射光(emissive light):模拟源自某个物体的光,可以增加物体的强度。

(1)材料颜色

和光一样,材料也具有不同的环境、散射和镜面颜色,它们决定了材料对红、绿和蓝光的反射率。材料的反射属性与每种入射光的环境光成分组合,散射反射属性与入射光的散射成分组合,镜面反射属性与入射光的镜面成分组合。环境和散射属性定义了材料的颜色,它们一般很相似,却不相同。镜面反射属性通常是白色或灰色的,因此镜面亮点的颜色也就是光源的镜面光的颜色。

(2)光和材料的RGB值

如果一个OpenGL光线的成分是(LR, LG, LB),一种材料具有相应的成分(MR, MG, MB),那么反射进入眼睛的光就是(LR*MR, LG*MG, LB*MB)。类似的,如果两束光进入眼睛,把它们的成分叠加在一起做截取即可,(R1+R2, G1+G2, B1+B2)。

3、样例

void init(void)
{
    GLfloat mat_specular[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat mat_shininess[] = {50.0};
    GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};
    GLfloat white_light[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat lmodel_ambient[] = {0.1, 0.1, 0.1, 1.0};
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_SMOOTH);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light);
    glLightfv(GL_LIGHT0, GL_SPECULAR, white_light);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);
}

void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glutSolidSphere(1.0, 20, 16);
    glFlush();
}

void reshape(int w, int h)
{
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
        glOrtho(-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10, 10.0);
    else
        glOrtho(-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100, 100);
    glutCreateWindow(argv[0]);
    init();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();
    return 0;
}
  • 为每个物体的每个定点定义法线向量:

    物体的法线向量决定了它相对于光源的方向,对于物体的每个顶点,OpenGL使用法线判断这个顶点从每个光源接受的光线数量。

    为了进行正确的光照计算,表面法线必须为单位长度。还必须保证对物体所进行的模型视图变化并没有对表面法线进行缩放,最终的法线仍然保持为单位长度。为了保证法线仍然为单位长度,可能需要以GL_NORMALIZE或GL_RESCALE_NORMAL为参数调用glEnable()函数。

  • 创建、定位和启用光源:

    样例中使用了glLightfv()函数指定光源,每个光源都需要大量的计算,因此场景的渲染性能将受到场景中光源数量的影响。

  • 选择光照模型

    glLgihtModel*()函数描述了光照模型的参数。光照模型还需要定义场景的观察者应该位于无限远处还是位于场景的本地,并确定场景中物体的正面和背面是否应该执行不同的光照处理。样例采用了默认设置,即观察者位于无限远处(无限观察者),并且只有一个面接受光照。如果采用本地观察者,需要执行的计算会复杂很多,OpenGL需要计算观察点和每个物体之间的角度。无限观察者模式下这个角度可以忽略。

  • 为场景中的物体定义材料属性

    指定材料的环境、散射和镜面颜色以及它的光泽度。

创建光源

  • 颜色:

    • GL_AMBIENT表示一个特定的光源在场景中所添加的环境光的RGBA强度。默认情况下是不存在环境光的;
    • GL_DIFFUSE可能最接近我们想象中光的颜色,定义了特定的光源在场景中所添加的散射光的RGBA强度。
    • GL_SPECULAR影响物体上镜面亮点的颜色。一般情况下,在现实世界中,像玻璃瓶这样的物体具有和照射它表面的光线颜色相同的镜面颜色。因此,如果想要创建类似的效果,可以把GL_SPECULAR参数设置为和GL_DIFFUSE参数的值相同。

在启用混合之前,这些颜色中的alpha成分是不会生效的,所以可以安全地忽略alpha值。

  • 位置和衰减

    • 方向性光源:光源位于无限远处,当光线到达物体表面时认为所有的光线都是平行的;
    • 位置性光源:在场景中有准确的位置。
      GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};
      glLightfv(GL_LIGHT0, GL_POSITION, light_position);
      

    这段代码把(x, y, z, w)的向量提供给GL_POSITION参数。如果最后一个值(w)为0.0,表示对应的光源是方向性光源,(x, y, z)值描述了它的方向,这个位置通过模型视图矩阵进行变换,并以视觉坐标的形式进行存储。默认情况下,GL_POSITION是(0, 0, 1, 0),它定义了一个指向z轴负方向的方向性光源。如果w是非零值,对应的光源就是位置性光源,默认情况下,位置性光源向所有的方向发射光线,但可以通过把光源定义为聚光灯,把它限制在一个锥体里。

    OpenGL把光源的强度乘以衰减因子,对它实行衰减。环境光、散射光和镜面光的强度都进行了衰减,只有发射光和全局环境光的强度没有衰减。另外,由于衰减需要在每次经过计算产生的颜色上再进行一次除法,可能会影响应用程序的性能。

  • 聚光灯
  • 多光源
  • 控制光源的位置和方向
    • 静止的光源:在使用视图和模型变换之后设置光源的位置。
    • 独立地移动光源:在模型变换之后设置光源位置
      static int spin = 0;
      
      void init(void)
      {
          glClearColor(0.0, 0.0, 0.0, 0.0);
          glShadeModel(GL_SMOOTH);
          glEnable(GL_LIGHTING);
          glEnable(GL_LIGHT0);
          glEnable(GL_DEPTH_TEST);
      }
      
      void display(void)
      {
          GLfloat position[] = {0.0, 0.0, 1.5, 1.0};
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
          glPushMatrix();
          glTranslatef(0.0, 0.0, -5.0);
          glPushMatrix();
          glRotated((GLdouble)spin, 1.0, 0.0, 0.0);
          glLightfv(GL_LIGHT0, GL_POSITION, position);
          glTranslated(0.0, 0.0, 1.5);
          glDisable(GL_LIGHTING);
          glColor3f(0.0, 1.0, 1.0);
          glutWireCube(0.1);
          glEnable(GL_LIGHTING);
          glPopMatrix();
          glutSolidTorus(0.275, 0.85, 8, 15);
          glPopMatrix();
          glFlush();
      }
      
      void reshape(int w, int h)
      {
          glViewport(0, 0, (GLsizei)w, (GLsizei)h);
          glMatrixMode(GL_PROJECTION);
          glLoadIdentity();
          gluPerspective(40.0, (GLfloat)w/(GLfloat)h, 1.0, 20.0);
          glMatrixMode(GL_MODELVIEW);
          glLoadIdentity();
      }
      
      void mouse(int button, int state, int x, int y)
      {
          switch (button) {
              case GLUT_LEFT_BUTTON:
                  if (state == GLUT_DOWN) {
                      spin = (spin + 30) % 360;
                      glutPostRedisplay();
                  }
                  break;
              default:
                  break;
          }
      }
      
      int main(int argc, char** argv)
      {
          glutInit(&argc, argv);
          glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
          glutInitWindowSize(500, 500);
          glutInitWindowPosition(100, 100);
          glutCreateWindow(argv[0]);
          init();
          glutDisplayFunc(display);
          glutReshapeFunc(reshape);
          glutMouseFunc(mouse);
          glutMainLoop();
          return 0;
      }
      
    • 光源与观察点一起移动:需要在视图变换之前设置光源位置,接着进行的视图变换就会以相同的方式同时影响光源和观察点。

选择光照模型

OpenGL的光照模型包括下面4个部分:

* 全局环境光强度
* 观察点的位置是位于场景还是位于无限远处;
* 物体的正面和背面是否应该执行不同的光照计算;
* 镜面颜色是否应该从环境和散射颜色中分离出来,并在纹理操作之后再应用。
  • 全局环境光

    GL_LIGHT_MODEL_AMBIENT,产生的时少量白色的环境光。

  • 局部观察点或无限远的观察点

    观察点的位置影响镜面反射所产生的亮点计算,一个特定顶点上的亮点的强度计算取决于这个顶点的法线、这个顶点和光源的方向以及这个顶点和观察点的距离。如果观察点在无限远处,它和场景中任何顶点的方向都是固定的,不过每个顶点都是必须计算它和观察点的方向,所以程序的性能会受到影响。

    无限远处:glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE)

    局部:glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE)

  • 双面光照

    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)

  • 镜面辅助颜色

    对于典型的光照计算,环境、散射、镜面和发射成分经过计算之后简单地叠加到一起。默认情况下纹理贴图是在光照之后应用的,因此,镜面亮点会被削弱。为了把镜面颜色的应用推迟到纹理贴图之后,可以

    glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);

    这种模型下,光照会为每个顶点产生两种颜色:一种是主颜色,由所有非镜面光照颜色组成,另一种是辅助颜色,是所有镜面光照颜色的总和。

  • 启用光照

定义材料属性

不管我们向其他参数提供的alpha值是什么,所有顶点的alpha值都是材料的散射颜色的alpha值,也就是在glMaterial*()函数中提供给GL_DIFFUSE的alpha值。

  • 散射和环境反射

    glMaterial*()设置的GL_DIFFUSE和GL_AMBIENT参数影响物体反射和环境光的颜色。对人眼来说,散射光扮演了最为重要的角色,人眼对物体颜色的知觉受到入射散射光的颜色以及入射光相对于法线方向角度的影响(当入射光垂直于表面时,物体的颜色看上去最深)。

    环境光的颜色影响物体的整体颜色。当物体被直接照亮时,散射颜色占据主导地位,反之环境光占据主导地位,并且两者都不受观察者位置的影响。对于现实中的物体,散射和环境颜色通常是相同的。

  • 镜面反射

    物体的镜面反射将会产生亮点,OpenGL允许在不存在反射光(GL_SPECULAR)的情况下设置材料的效果,并控制亮点的大小和亮度(使用GL_SHININESS)。

  • 反射光颜色

    GL_EMISSION参数指定一个RGBA颜色值。

  • 更改材料属性
  • 颜色材料模式

    为了减少修改材料属性所带来的开销,可以使用glColorMaterial()来根据当前颜色快速地对材料属性进行更新。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-12 22:48:36

OPENGL学习笔记——光照的相关文章

【OpenGL 学习笔记02】宽点画线

我们要知道,有三种绘图操作是最基本的:清除窗口,绘制几何图形,绘制光栅化对象. 光栅化对象后面再解释. 1.清除窗口 比如我们可以同时清除颜色缓冲区和深度缓冲区 glClearColor (0.0, 0.0, 0.0, 0.0);//指定颜色缓冲区清除为黑色 glClearDepth(1.0);//指定深度缓冲区的清除值为1.0 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//指定要清除的缓冲区并清除 2.绘制几何图形 先要设置绘制颜色,

【opengl 学习笔记01】HelloWorld示例

<<OpenGL Programming Guide>>这本书是看了忘,忘了又看,赶脚还是把笔记做一做心里比较踏实,哈哈. 我的主题是,好记性不如烂笔头. ================================================================ 1. 下载glut库 glut库地址为:www.opengl.org/resources/libraries/glut/glutdlls37beta.zip glut全称为:OpenGL Utilit

OpenGL学习笔记:拾取与选择

转自:OpenGL学习笔记:拾取与选择 在开发OpenGL程序时,一个重要的问题就是互动,假设一个场景里面有很多元素,当用鼠标点击不同元素时,期待作出不同的反应,那么在OpenGL里面,是怎么知道我当前鼠标的位置是哪一个物体呢? OpenGL有一套机制,叫做Picking, 里面涉及到几个核心概念: 1. selection mode. 选择模式 2. name stack. 名字栈 3. hit record. 命中记录 4. viewing volume. 视角范围 在OpenGL的pick

OpenGL学习笔记3 —— 绘制3D物体、鼠标交互、反向变换

/* reference http://nehe.gamedev.net/article/using_gluunproject/16013/ */ #include <windows.h> // windows系统要加这个.因为下面2个头文件的一些宏是在这个文件中定义的 #include <gl/Gl.h> #include <gl/glut.h> //这两个头文件在OpenGL程序中几乎必加. #include <cstdio> //标准输入输出,用来打印

【OpenGL 学习笔记04】顶点数组

通过之前的学习,我们知道,如果要绘制一个几何图形,那就要不断的调用绘制函数,比如绘制一个20条边的多边形,起码要调用22条函数(包含glBegin和glEnd). 所以OpenGL提供了一系列的顶点数组函数减少函数调用的次数来提高性能.而且使用顶点还可以避免顶点共享的冗余处理. 1.简单示例 先来回顾一下之前我们是怎么画直线的: void drawOneLine(GLfloat x1,GLfloat y1,GLfloat x2,GLfloat y2) { glBegin(GL_LINES); g

【OpenGL 学习笔记03】点画多边形

1.点画多边形 //定义填充多边形的点画模式.mask为32 x 32 的位图指针,1画0不画,使用前必须启用多边形点画功能 void glPloygonStipple(const GLubyte* mask); //绘制一个矩形 void glRectf(GLfloat x1,GLfloat y1,GLfloat x2,GLfloat y2); 2.示例 #include <GL/glut.h> #include <stdlib.h> void display(void) { G

OpenGL学习笔记1 —— 画点

#include <windows.h> // windows系统要加这个.因为下面2个头文件的一些宏是在这个文件中定义的 #include <gl/Gl.h> #include <gl/glut.h> //这两个头文件在OpenGL程序中几乎必加. //<<<<<<<<<<<<<<<<<<<<<<< myInit >>&

OpenGL学习笔记2 —— 画立方体

#include <windows.h> // windows系统要加这个.因为下面2个头文件的一些宏是在这个文件中定义的 #include <gl/Gl.h> #include <gl/glut.h> //这两个头文件在OpenGL程序中几乎必加. //<<<<<<<<<<<<<<<<<<<<<<< myInit >>&

OpenGL学习笔记4:纹理

原始图像数据 像素包装 图像数据在内存中很少以紧密包装的形式存在.在许多硬件平台上,处于性能上的考虑,一幅图像的每一行都应该从一种特定字节对齐地址开始.绝大多数编译器会自动把变量和缓冲区放置在一个针对该架构对齐优化的地址上. 例如一个包含3个分量的rgb图像,每个分量存储在一个字节中,如果图像有199个像素,那么一行需要597个像素.如果硬件本身的体系结构是4字节排列,那么图像每一行的末尾将由额外的3个空字符进行填充达到600字节. 我们可以使用下列函数改变或者回复像素的存储方式. void g