您的位置:首页 > 运维架构

OpenGL光源位置

2011-01-26 16:40 267 查看
  OpenGL中的光照模型是一种简化的模型,这样做可以提高处理的实时性,因为复杂的光照模型或许能带来更好的光照效果,但是对系统的消耗也是很大的,况且这种简化的模型在大多数情况下也能得到不错的光照效果。

在openGL中,光照包括很多问题,比如定义光源、指定材质属性、确定光照模型等。这些问题经过看书基本都能理解,但是在openGL中控制光源的位置或许并不是个简单的问题,我花了不少时间,看了不少资料,包括网友们的文章,openGL的红宝书等。最后我认为我弄明白了如何在openGL中控制光源的位置,现在把它们总结一下。

如何控制光源位置

在openGL中,光源位置的控制也是通过模型视图矩阵实现的,包括平移、旋转、缩放(因为它会改变法向量,慎用)等。这样,光源可以看作一个定义在世界坐标系中的普通的几何对象。

但是,控制光源位置和控制几何对象位置有一点是不同的,这主要取决于 提交光源位置(glLight*())和视点变换(gluLookAt())的先后顺序。 如果在视点变换前提交光源位置,视点和光源将捆绑在一起,即二者相对位置不变,一起运动。此时,可以理解为提交的光源位置是在摄像机坐标系中度量的(位置参数的默认值是(0.0, 0.0, 1.0, 0.0), 就是在摄像机坐标系中度量的)。

如果在视点变换后提交光源位置,那么光源就可以看作一个普通的几何对象,提交的坐标是在世界坐标系中度量的,对普通几何对象的各种变换同样适用于光源。如果程序里没有视点变换,说明世界坐标系和摄像机坐标系重合,光源也可以看作一个普通的几何对象。

为什么可以这样控制

根据openGL流水线,在物体描述(世界坐标系)投影到观察平面(摄像机坐标系)之前,在世界坐标系中定义的几何对象必须转换到摄像机坐标系中,即实现坐标变换,它是通过视点变换完成的。因而投影过程得到的几何对象坐标是已经转换到摄像机坐标系中的坐标。实际上,glLight*()提交的坐标都是在世界坐标系中度量的(默认情况下除外)。

对于普通的几何对象,我们在世界坐标系里定义它们,平移、旋转等也是在改变它们的世界坐标。这些坐标在提交给投影过程前,openGL会根据当前世界坐标系和摄像机坐标系的关系把世界坐标转换为摄像机坐标,然后提交给投影过程。每一个几何对象的坐标都会经过这种坐标变换,而不论它在视点变换前,还是视点后定义的。

对于光源,我们也是在世界坐标系里定义它们,平移、旋转等也是在改变它们的世界坐标。与普通几何对象不同的是,如果这些定义、平移、旋转出现在视点变换前,那么openGL则直接把光源当前的坐标提交给摄像机坐标系,进入投影过程,而不再经过视点变换。比如视点变换前我们得到光源的坐标是(1.0, 1.0, 1.0, 1.0),那么每次视点变换后,投影过程得到的都是这个坐标。这就相当于光源固定在摄像机坐标系中了,它将随着视点的变化而变化。 如果这些定义、平移、旋转出现在视点变换后,那么在进入投影过程前它们都要经过坐标系变换,变成摄像机坐标,这点和普通几何对象是一样的。比如视点变换后我们得到光源的坐标是(1.0, 1.0, 1.0, 1.0),那么每次视点变换后,这个坐标都会依据当前世界坐标系和摄像机坐标系的关系被转换到摄像机坐标系中,视点和光源不再捆绑在一起,光源和普通几何对象一样运动。

一个例子

void init()

{

一些基本设定,指定光源和材质属性等,不涉及光源的位置。

}

void display()

{

GLfloat lightposition[] = {0.0, 0.0, 3.0, 1.0 };//光源位置

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//glLightfv(GL_LIGHT0, GL_POSITION, lightposition);........................................前

glMatrixMode(GL_MODELVIEW);

glPushMatrix();

glLoadIdentity();

gluLookAt( D*sin(alpha*PI/180), 0.0, D*cos(alpha*PI/180), 0.0, 0.0, 0.0, 0.0, 1.0, 1.0);

//glLightfv(GL_LIGHT0, GL_POSITION, lightposition);............................................后

glutSolidTeapot(0.5);

glPopMatrix();

glutSwapBuffers();

}

void mouse(int button, int state, int x, int y)

{

if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)

{

alpha = alpha + 45;

if(alpha >= 360) alpha -= 360;

glutPostRedisplay();

}

}

依据提交光源位置前、后的不同,有四种情况

1 前、后都被注释掉。这时启用默认位置(0.0, 0.0, 1.0, 0.0),在摄像机坐标系中度量的,我们将看到Teapot永远是亮的。

2 启用前,效果同上,光源和视点一起运动,可以理解为光源位置在摄像机坐标系中度量的。

3 启用后,我们会看到Teapot的明暗变化,视点运动,光源固定在世界坐标系中,光源等同于普通几何对象。

4 前、后都启用, 效果同3,这时前被后屏蔽,后起作用。

附 上例完整的程序(Ubuntu/gcc环境)

#include <GL/glut.h>

#include <stdlib.h>

#include<math.h>

#include<stdio.h>

#define PI 3.14159

#define D 4

float alpha = 0;

float spin = 0;

void init()

{

glClearColor(1.0,1.0,1.0,0.0);

glShadeModel(GL_SMOOTH);

glEnable(GL_DEPTH_TEST);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

GLfloat lightambient[] = {1.0,1.0,1.0,1.0};//环境光

GLfloat lightdiffuse[] = {1.0,1.0,1.0,1.0};//漫反射

GLfloat lightspecular[] = {1.0,1.0,1.0,1.0 };//镜面反射光

glLightfv(GL_LIGHT0,GL_AMBIENT,lightambient);

glLightfv(GL_LIGHT0,GL_DIFFUSE,lightdiffuse);

glLightfv(GL_LIGHT0,GL_SPECULAR,lightspecular);

glLightModelf(GL_LIGHT_MODEL_AMBIENT, (0.0, 0.0, 0.0));

GLfloat emission[] = {0.0, 0.0, 0.0, 1.0};//发射光

GLfloat ambient[] ={0.2,0.2,0.2,0.0};//环境光

GLfloat diffuse[] ={1.0,0.5,0.5,0.5};//漫反射特性

GLfloat specular[] ={0.5,0.5,0.5,0.0 };//镜面反射光色

GLfloat shininess[] ={100.0}; //镜面反射的光亮度

glMaterialfv(GL_FRONT,GL_AMBIENT,ambient);

glMaterialfv(GL_FRONT,GL_DIFFUSE,diffuse);

glMaterialfv(GL_FRONT,GL_SPECULAR,specular);

glMaterialfv(GL_FRONT,GL_SHININESS,shininess);

glMaterialfv(GL_FRONT, GL_EMISSION, emission);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

}

void display()

{

GLfloat lightposition[] = {0.0, 0.0, 3.0, 1.0 };//光源位置

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glLightfv(GL_LIGHT0, GL_POSITION, lightposition);

glMatrixMode(GL_MODELVIEW);

glPushMatrix();

glLoadIdentity();

gluLookAt( D*sin(alpha*PI/180),0.0, D*cos(alpha*PI/180), 0.0, 0.0, 0.0, 0.0, 1.0, 1.0);

//glLightfv(GL_LIGHT0, GL_POSITION, lightposition);

glutSolidTeapot(0.5);

glPopMatrix();

glutSwapBuffers();

}

void reshape(int w, int h)

{

glViewport(0, 0, (GLsizei)w, (GLsizei)h);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 1.0, 20.0);

glMatrixMode(GL_MODELVIEW);

}

void mouse(int button, int state, int x, int y)

{

if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)

{

alpha = alpha + 45;

if(alpha >= 360) alpha -= 360;

spin = spin + 45;

if(spin >= 360) spin -= 360;

glutPostRedisplay();

}

}

void myidle()

{

alpha = alpha + 0.01;

if(alpha >= 360) alpha -= 360;

glutPostRedisplay();

}

int main(int argc, char** argv)

{

glutInit(&argc, argv);

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RED | GLUT_DEPTH);

glutInitWindowSize(500, 500);

glutInitWindowPosition(100, 100);

glutCreateWindow("move light");

//glutIdleFunc(myidle);

init();

glutDisplayFunc(display);

glutReshapeFunc(reshape);

glutMouseFunc(mouse);

glutMainLoop();

return 0;

}

光源的位置就应该是其在世界坐标系中的位置,不管经过什么变换,光源和场景中物体的相对位置是不应该变的。openGL中的这个现象是openGL的一个bug,跟坐标变换没有关系。

在openGL中,顶点每被提交一次,就被模型视图矩阵变换一次,而变换后的坐标,即,眼睛坐标系中的坐标,被存储起来,用以绘图。所谓“提交”就是指调用说明顶点坐标的函数,比如glVertex3d。对于光源,这个提交函数就是glLightfv函数。如果在MyInit函数中指定光源位置,提交的坐标值就是世界坐标值。如果在此之后才调用gluLookAt函数,则在提交光源坐标时,模型视图矩阵是单位阵。经此单位阵变换后的光源坐标被存储起来。假如在MyDisplay函数中不再设置光源的位置,也就是不再“提交”光源的位置,则openGL不会再重新计算光源的眼睛坐标值,仍然使用MyInit中计算出来的值。那么,就会出现这种现象:在MyInit后,调用gluLookAt设置了眼睛坐标系,从而也修改了模型视图矩阵,按道理来讲,光源的眼睛坐标值应该随着模型视图矩阵而变,但因为没有再次“提交”光源坐标,openGL仍然沿用使用单位矩阵计算出来的光源位置,这就造成了眼睛(坐标系)移动,光源也移动的现象发生。因为openGL中存储的光源坐标不随着眼睛位置的变化而被重新计算,总是一个定值。在绘图时,不管眼睛在哪里,openGL总是在当时的眼睛坐标系中按照一个固定的坐标值安置光源,所以,光源就随着眼睛移动了。

而,网上流行的什么光源随眼睛移动是因为被模型视图矩阵变换了坐标值的说法是完全错误的。试想,世界坐标系中的一个有固定坐标的点,不论经过了怎样的坐标变换,只是对这个点的位置的描述变了,但这个点在空间中的位置不会变。而引起大家疑惑的现象是:光源的位置在空间中发生了变化,这决不是模型视图矩阵能够做到的。

我前边说了一句:“在glLightfv之后,调用gluLookAt”,好像这个顺序有什么要紧,其实无所谓。我这次特地来说明这点。其实,不管是在gluLookAt之前还是之后调用glLightfv函数,只要是程序中在MyDisplay()之外指定了光源位置,那么光源位置都会按照当时的模型视图矩阵的值被计算为一个固定的值。那么当眼睛位置变换时,光源总会按照这个固定坐标值被绘制到当前的眼睛坐标系中,因此光源就会随着眼睛移动了。而MyDisplay()函数由于会被不断地调用,因此如果在该函数中制定光源位置,则每次光源坐标都会被重新提交,与之对应的眼睛坐标也会被重新计算,这就可以反映当前最新的模型视图矩阵的变化,因此光源位置就固定了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: