纠结了很久,终于算是可以完全控制灯光位置了,好开心~
我的理解是这样的:(可能和实际的计算过程有出入,不过这么理解的确可以正确地进行编程。)
1、其实OpenGL只有一个坐标系,叫做世界坐标系,后面说的都是世界坐标系中的坐标。
2、摄像机始终在原点。
3、我们在程序里面写的物体的坐标或者灯光的坐标,是原始坐标。
4、物体和灯光在的 最终坐标 = 其原始坐标 * 变换矩阵。变换矩阵就是 GL_MODELVIEW,模型视图矩阵。
5、修改变换矩阵的方法是用glMultiMatrix*函数乘以另外一个矩阵。当然,自己计算矩阵较麻烦,可以用 glTranslate* glRotate* glScaled* 等来自动生成一些矩阵。
举几个例子:
1、让灯光随着镜头移动,例如CS里面枪的灯光跟随着人移动!
前面说了,摄像机是不动的(始终在原点),我们希望灯光在镜头那儿,所以灯光也要在原点呀~
灯光最终坐标 = (0,0,0) 即可!
因为我们只能设置原始坐标,所以将变换矩阵设置为单位矩阵以排除其影响,于是 最终坐标 = 原始坐标 * 单位矩阵 = 原始坐标。
设置灯光位置的代码大概是下面这样的。这段代码放到哪里都行,只要保证被执行一次就行 :)
glPushMatrix()
GLfloat final_pos[] = {0,0,0,1.0};
glLoadIdentity();
glLightfv( GL_LIGHT0, GL_POSITION, final_pos);
glPopMatrix()
2、一个绕着球体旋转的灯光。
球体放到(0,0,-10)处。
void Render(){
glMatrixMode( GL_MODELVIEW);// 重置模型视图矩阵为单位阵
glLoadIdentity();
glTranslate( 0, 0, -10); // 将球和灯光都等除了摄像机外的东西都移动到(0,0,-10)处
float radius = 10;
static float angle = 0; // 让灯光绕着(0,0,-10)旋转。angle记录当前旋转的角度。
angle += 0.2; // 每次循环旋转一点角度
float x = radius * cos( angle);
float z = radius * sin( angle);
GLfloat pos[] = { x, 0, z, 1.0};
glLightfv( GL_LIGHT0, GL_POSITION, pos);
// 这里画一个半径不超过10的球体(不要把灯光包到球里面去了)
}
解释一下:
首先需要开启灯光,设置灯光颜色等,设置球体材质。
Render里面首先是重置了变换矩阵,然后设置变换矩阵为(0,0,-10),即将所有东西都移动到(0,0,-10)的位置上,所以现在如果画球,
则球体在(0,0,-10)处,灯光也是哦!即灯光在球体中心,当然是照不亮球体的。
我们计算灯光绕着球心的偏移(x,z),再将灯光从球心按照这个偏移移动到球体外面。每次计算的偏移就构成了绕球旋转的轨道,于是灯光就绕着球体转起来了。
3、一个静止的灯光(例如CS中墙壁上的灯光)
其实也不难。
我们每次渲染前都glTranslate*( x, y, z)将所有物体移动一下,这个时候灯光也被移动了。
譬如我们想设置灯光的最终位置为固定的(1,2,3),那么我们就设置其原始坐标为(-x+1,-y+2,-z+3)。
(-x,-y,-z)用于把灯光移回原点,然后再通过(1,2,3)把灯光移动到(1,2,3),合并起来就是(-x+1,-y+2,-z+3)了。
代码大概就是:
glTranslate*( x, y, z);
GLfloat origin_pos[] = { -x + 1, -y + 2, -z + 3};
glLightfv( GL_LIGHT0, GL_POSITION, origin_pos);
// 接下来画别的东西。例如在(x,y,z)到(x,y,z+1)之间画一条线!
glBegin( GL_LINES);
glVertex3f( 0, 0, 0);// 这里要写原始坐标哦!然后会被变换矩阵变成(0+x,0+y,0+z)的哦!
glVertex3f( 0, 0, 1);
glEnd();
当然啦,也可以用别的方法来理解。比如平时编程时经常将变换矩阵拆分为视图矩阵变换和模型矩阵变换,视图变换矩阵用来控制摄像机位置,然后模型变换矩阵用来改变模型的方向、位置等。我自己觉得还是不用拆开来理解好,更简单一点 :)