前言
前段时间在工程中会用到opengl来进行一些渲染和建模的操作,未来一段时间会重点更新这个专栏,用最高信噪比的方式来讲解opengl典型函数和结构的应用。通过这种手把手的方式我相信是新手朋友学习最快的路径。这个专栏会照顾到大家,让大家能够自己上手去改一些东西并且看到效果,这样在我看来是最高效率的学习方法。
OpenGL的渲染和建模代码大结构
鉴于OpenGL一般只是一段代码中的中间处理过程,所以把整个的OpenGL操作放到一个函数当中,未来的代码构造也会与这里大致相同。
一般一个典型的OpenGL处理片段包括以下几个步骤:
- 初始化显示模式(RGB模式,双缓冲模式)。
- 初始化显示窗口的位置。
- 初始化显示窗口的大小。
- 设置OpenGL显示函数。
- 需要动态变化的调用相关的计数、计时
- 主函数循环。
在设置OpenGL显示函数的时候,又有以下几个典型步骤:
- 清除屏幕及缓存
- 定义变换类型(之前的文章有讲)
- 设置相机参数(视口变换)
- 设置观察位置
- 设置光源
- 定义物体材质
- 设置缓冲区交换
重点函数精析
设置相机参数(视口变换)
gluPerspective(90.0f, 1.0f, 1.0f, 20.0f); //视口变换
设置相机参数 void gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
aspect表示裁剪面的宽w高h比,这个影响到视野的截面有多大。
fovy是眼睛上下睁开的幅度,角度值,值越小,视野范围越狭小(眯眼),值越大,视野范围越宽阔(睁开铜铃般的大眼);
zNear表示近裁剪面到眼睛的距离,zFar表示远裁剪面到眼睛的距离,注意zNear和zFar不能设置设置为负值(你怎么看到眼睛后面的东西)。
设置观察位置
gluLookAt(0.0, 5.0, -10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
gluLookAt用来定义观察者(相机)的状态,包括观察者在世界坐标系中所处的位置、看向世界坐标系中的方向(可以理解为眼睛所看向的方向)、观察者头部的朝向(可以在一个平面上360°旋转)
void gluLookAt (
GLdouble eyex, GLdouble eyey, GLdouble eyez,
GLdouble centerx, GLdouble centery, GLdouble centerz,
GLdouble upx, GLdouble upy, GLdouble upz);
第一二三个参数定义相机在世界坐标系中的位置坐标
第四五六个参数定义相机正对着的世界坐标系中的点的位置坐标,成像后这一点会位于画板的中心位置
第七八九参数,定义相机本身的朝向。这三个坐标是在世界坐标系中的坐标点,可以理解为人站立在相机处头的朝向。这三个坐标是世界坐标系中的坐标点,不是相机坐标系的,只是用来定义方向,注意这个不是视线(镜头)的朝向,而是摆放时相机本身的朝向,跟视线朝向方向是垂直的。
以观察者的角度去看这几个参数就很容易理解了。
第一组参数是定义人站在距离物体有多远处,
第二组参数是定义人眼看向世界坐标系中的哪个方向,有时候屏幕上黑黑的什么也看不到,可能就是这组参数设置的方向不对,有可能物体就在你身后不远处
第三组参数是人的朝向,也表示一个方向,这个朝向跟视线是垂直的。
始终需要明确的一点是openGL中世界坐标系是右手坐标系,在二维屏幕上,屏幕水平方向是x 轴方向,向右为正,屏幕竖起方向是Y轴方向,向上为正,垂直于屏幕的方向是Z轴方向,从屏幕里往外为正。
定义光源
GLfloat sun_light_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f}; //遗留强度
GLfloat sun_light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f}; //漫反射强度
GLfloat sun_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f}; //镜面反射强度
GLfloat sun_light_position[] = {0.0f, 0.0f, 0.0f, 1.0f}; //光源位置
glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position);//GL_LIGHT0:0号光源的定义,总共可以定义8个光源
glLightfv(GL_LIGHT0, GL_AMBIENT, sun_light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, sun_light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。这三个属性表示了光源所发出的光的反射特性(以及颜色)。
每个属性由四个值表示,分别代表了颜色的R, G, B, A值。
GL_AMBIENT表示该光源所发出的光,经过非常多次的反射后,最终遗留在整个光照环境中的强度(颜色)。
GL_DIFFUSE表示该光源所发出的光,照射到粗糙表面时经过漫反射,所得到的光的强度(颜色)。
GL_SPECULAR表示该光源所发出的光,照射到光滑表面时经过镜面反射,所得到的光的强度(颜色)。
(2)GL_POSITION属性。表示光源所在的位置。由四个值(X, Y, Z, W)表示。
如果第四个值W为零,则表示该光源位于无限远处,前三个值表示了它所在的方向。
这种光源称为方向性光源,通常,太阳可以近似的被认为是方向性光源。
如果第四个值W不为零,则X/W, Y/W, Z/W表示了光源的位置。这种光源称为位置性光源。
对于位置性光源,设置其位置与设置多边形顶点的方式相似,各种矩阵变换函数例如:glTranslate*、glRotate*等在这里也同样有效。方向性光源在计算时比位置性光源快了不少,因此,在视觉效果允许的情况下,应该尽可能的使用方向性光源。
(3)GL_SPOT_DIRECTION、GL_SPOT_EXPONENT、GL_SPOT_CUTOFF属性。
表示将光源作为聚光灯使用(这些属性只对位置性光源有效)。
很多光源都是向四面八方发射光线,但有时候一些光源则是只向某个方向发射,比如手电筒,只向一个较小的角度发射光线。
GL_SPOT_DIRECTION属性有三个值,表示一个向量,即光源发射的方向。
GL_SPOT_EXPONENT属性只有一个值,表示聚光的程度,为零时表示光照范围内向各方向发射的光线强度相同,为正数时表示光照向中央集中,正对发射方向的位置受到更多光照,其它位置受到较少光照。
数值越大,聚光效果就越明显。
GL_SPOT_CUTOFF属性也只有一个值,表示一个角度,它是光源发射光线所覆盖角度的一半,其取值范围在0到90之间,也可以取180这个特殊值。取值为180时表示光源发射光线覆盖360度,即不使用聚光灯,向全周围发射。
(4)GL_CONSTANT_ATTENUATION、GL_LINEAR_ATTENUATION、GL_QUADRATIC_ATTENUATION属性。
这三个属性表示了光源所发出的光线的直线传播特性(这些属性只对位置性光源有效)。
现实生活中,光线的强度随着距离的增加而减弱,OpenGL把这个减弱的趋势抽象成函数:
衰减因子 = 1 / (k1 + k2 * d + k3 * k3 * d)
其中d表示距离,光线的初始强度乘以衰减因子,就得到对应距离的光线强度。
k1, k2, k3分别就是GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, GL_QUADRATIC_ATTENUATION。通过设置这三个常数,就可以控制光线在传播过程中的减弱趋势。
属性还真是不少。当然了,如果是使用方向性光源,(3)(4)这两类属性就不会用到了,问题就变得简单明了。
定义材质
GLfloat sun_mat_ambient[] = {0.0f, 0.0f, 0.5f, 1.0f}; //类似于光照
GLfloat sun_mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f}; //类似于光照,通常GL_AMBIENT和GL_DIFFUSE都取相同的值,可以达到比较真实的效果
GLfloat sun_mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f}; //镜面反射
GLfloat sun_mat_emission[] = {0.5f, 0.0f, 0.0f, 1.0f}; //散发淡淡的红光
GLfloat sun_mat_shininess = 30.0f; //0-128
glMaterialfv(GL_FRONT, GL_AMBIENT, sun_mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, sun_mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, sun_mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, sun_mat_emission);
glMaterialf(GL_FRONT, GL_SHININESS, sun_mat_shininess);
glutSolidSphere(2.0, 40, 32);
控制材质:
材质与光源相似,也需要设置众多的属性。不同的是,光源是通过glLight函数来设置的,而材质则是通过glMaterial函数来设置的。glMaterial*函数有三个参数。第一个参数表示指定哪一面的属性。可以是GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK。
分别表示设置“正面”“背面”的材质,或者两面同时设置。第二、第三个参数与glLight*函数的第二、三个参数作用类似。
下面分别说明glMaterial*函数可以指定的材质属性。
(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。
这三个属性与光源的三个对应属性类似,每一属性都由四个值组成。
GL_AMBIENT表示各种光线照射到该材质上,经过很多次反射后最终遗留在环境中的光线强度(颜色)。
GL_DIFFUSE表示光线照射到该材质上,经过漫反射后形成的光线强度(颜色)。
GL_SPECULAR表示光线照射到该材质上,经过镜面反射后形成的光线强度(颜色)。
通常,GL_AMBIENT和GL_DIFFUSE都取相同的值,可以达到比较真实的效果。
使用GL_AMBIENT_AND_DIFFUSE可以同时设置GL_AMBIENT和GL_DIFFUSE属性。
(2)GL_SHININESS属性。
该属性只有一个值,称为“镜面指数”,取值范围是0到128。
该值越小,表示材质越粗糙,点光源发射的光线照射到上面,也可以产生较大的亮点。
该值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。
(3)GL_EMISSION属性。
该属性由四个值组成,表示一种颜色。
OpenGL认为该材质本身就微微的向外发射光线,以至于眼睛感觉到它有这样的颜色,但这光线又比较微弱,以至于不会影响到其它物体的颜色。
(4)GL_COLOR_INDEXES属性。
该属性仅在颜色索引模式下使用,由于颜色索引模式下的光照比RGBA模式要复杂,并且使用范围较小,这里不做讨论。
完整代码
#include <gl/glut.h>
#define WIDTH 800
#define HEIGHT 600
static GLfloat angle = 0.0f;
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕及深度缓存
// 创建透视效果视图
glMatrixMode(GL_PROJECTION); //声明当前进行的是投影变换,GL_MODELVIEW:模型变换,GL_PROJECTION:投影变换
glLoadIdentity(); //重置当前的模型观察矩阵
gluPerspective(90.0f, 1.0f, 1.0f, 20.0f); //视口变换
/*
设置相机参数
void gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
aspect表示裁剪面的宽w高h比,这个影响到视野的截面有多大。
fovy是眼睛上下睁开的幅度,角度值,值越小,视野范围越狭小(眯眼),值越大,视野范围越宽阔(睁开铜铃般的大眼);
zNear表示近裁剪面到眼睛的距离,zFar表示远裁剪面到眼睛的距离,注意zNear和zFar不能设置设置为负值(你怎么看到眼睛后面的东西)。
*/
glMatrixMode(GL_MODELVIEW); //声明当前进行的是模型变换
glLoadIdentity();
gluLookAt(0.0, 5.0, -10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
/*
设置观察位置
gluLookAt用来定义观察者(相机)的状态,包括观察者在世界坐标系中所处的位置、看向世界坐标系中的方向(可以理解为眼睛所看向的方向)、观察者头部的朝向(可以在一个平面上360°旋转)
void gluLookAt (
GLdouble eyex, GLdouble eyey, GLdouble eyez,
GLdouble centerx, GLdouble centery, GLdouble centerz,
GLdouble upx, GLdouble upy, GLdouble upz);
第一二三个参数定义相机在世界坐标系中的位置坐标
第四五六个参数定义相机正对着的世界坐标系中的点的位置坐标,成像后这一点会位于画板的中心位置
第七八九参数,定义相机本身的朝向。这三个坐标是在世界坐标系中的坐标点,可以理解为人站立在相机处头的朝向。这三个坐标是世界坐标系中的坐标点,不是相机坐标系的,只是用来定义方向,注意这个不是视线(镜头)的朝向,而是摆放时相机本身的朝向,跟视线朝向方向是垂直的。
以观察者的角度去看这几个参数就很容易理解了。
第一组参数是定义人站在距离物体有多远处,
第二组参数是定义人眼看向世界坐标系中的哪个方向,有时候屏幕上黑黑的什么也看不到,可能就是这组参数设置的方向不对,有可能物体就在你身后不远处
第三组参数是人的朝向,也表示一个方向,这个朝向跟视线是垂直的。
始终需要明确的一点是openGL中世界坐标系是右手坐标系,在二维屏幕上,屏幕水平方向是x 轴方向,向右为正,屏幕竖起方向是Y轴方向,向上为正,垂直于屏幕的方向是Z轴方向,从屏幕里往外为正。
*/
{
GLfloat other_light_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f}; //遗留强度
GLfloat other_light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f}; //漫反射强度
GLfloat other_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f}; //镜面反射强度
GLfloat other_light_position[] = {0.0f, 0.0f, 10.0f, 1.0f}; //光源位置
glLightfv(GL_LIGHT1, GL_POSITION, other_light_position);//GL_LIGHT1:1号光源的定义,总共可以定义8个光源
glLightfv(GL_LIGHT1, GL_AMBIENT, other_light_ambient);
glLightfv(GL_LIGHT1, GL_DIFFUSE, other_light_diffuse);
glLightfv(GL_LIGHT1, GL_SPECULAR, other_light_specular);
glEnable(GL_LIGHT1);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
}
// 定义太阳光源,它是一种白色的光源
{
GLfloat sun_light_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f}; //遗留强度
GLfloat sun_light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f}; //漫反射强度
GLfloat sun_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f}; //镜面反射强度
GLfloat sun_light_position[] = {0.0f, 0.0f, 0.0f, 1.0f}; //光源位置
/*
(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。这三个属性表示了光源所发出的光的反射特性(以及颜色)。
每个属性由四个值表示,分别代表了颜色的R, G, B, A值。
GL_AMBIENT表示该光源所发出的光,经过非常多次的反射后,最终遗留在整个光照环境中的强度(颜色)。
GL_DIFFUSE表示该光源所发出的光,照射到粗糙表面时经过漫反射,所得到的光的强度(颜色)。
GL_SPECULAR表示该光源所发出的光,照射到光滑表面时经过镜面反射,所得到的光的强度(颜色)。
(2)GL_POSITION属性。表示光源所在的位置。由四个值(X, Y, Z, W)表示。
如果第四个值W为零,则表示该光源位于无限远处,前三个值表示了它所在的方向。
这种光源称为方向性光源,通常,太阳可以近似的被认为是方向性光源。
如果第四个值W不为零,则X/W, Y/W, Z/W表示了光源的位置。这种光源称为位置性光源。
对于位置性光源,设置其位置与设置多边形顶点的方式相似,各种矩阵变换函数例如:glTranslate*、glRotate*等在这里也同样有效。方向性光源在计算时比位置性光源快了不少,因此,在视觉效果允许的情况下,应该尽可能的使用方向性光源。
(3)GL_SPOT_DIRECTION、GL_SPOT_EXPONENT、GL_SPOT_CUTOFF属性。
表示将光源作为聚光灯使用(这些属性只对位置性光源有效)。
很多光源都是向四面八方发射光线,但有时候一些光源则是只向某个方向发射,比如手电筒,只向一个较小的角度发射光线。
GL_SPOT_DIRECTION属性有三个值,表示一个向量,即光源发射的方向。
GL_SPOT_EXPONENT属性只有一个值,表示聚光的程度,为零时表示光照范围内向各方向发射的光线强度相同,为正数时表示光照向中央集中,正对发射方向的位置受到更多光照,其它位置受到较少光照。
数值越大,聚光效果就越明显。
GL_SPOT_CUTOFF属性也只有一个值,表示一个角度,它是光源发射光线所覆盖角度的一半,其取值范围在0到90之间,也可以取180这个特殊值。取值为180时表示光源发射光线覆盖360度,即不使用聚光灯,向全周围发射。
(4)GL_CONSTANT_ATTENUATION、GL_LINEAR_ATTENUATION、GL_QUADRATIC_ATTENUATION属性。
这三个属性表示了光源所发出的光线的直线传播特性(这些属性只对位置性光源有效)。
现实生活中,光线的强度随着距离的增加而减弱,OpenGL把这个减弱的趋势抽象成函数:衰减因子 = 1 / (k1 + k2 * d + k3 * k3 * d)
其中d表示距离,光线的初始强度乘以衰减因子,就得到对应距离的光线强度。
k1, k2, k3分别就是GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, GL_QUADRATIC_ATTENUATION。通过设置这三个常数,就可以控制光线在传播过程中的减弱趋势。
属性还真是不少。当然了,如果是使用方向性光源,(3)(4)这两类属性就不会用到了,问题就变得简单明了。
*/
glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position);//GL_LIGHT0:0号光源的定义,总共可以定义8个光源
glLightfv(GL_LIGHT0, GL_AMBIENT, sun_light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, sun_light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
}
// 定义太阳的材质并绘制太阳
{
GLfloat sun_mat_ambient[] = {0.0f, 0.0f, 0.5f, 1.0f}; //类似于光照
GLfloat sun_mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f}; //类似于光照,通常GL_AMBIENT和GL_DIFFUSE都取相同的值,可以达到比较真实的效果
GLfloat sun_mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f}; //镜面反射
GLfloat sun_mat_emission[] = {0.5f, 0.0f, 0.0f, 1.0f}; //散发淡淡的红光
GLfloat sun_mat_shininess = 30.0f; //0-128
/*
控制材质:
材质与光源相似,也需要设置众多的属性。不同的是,光源是通过glLight*函数来设置的,而材质则是通过glMaterial*函数来设置的。
glMaterial*函数有三个参数。第一个参数表示指定哪一面的属性。可以是GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK。
分别表示设置“正面”“背面”的材质,或者两面同时设置。第二、第三个参数与glLight*函数的第二、三个参数作用类似。
下面分别说明glMaterial*函数可以指定的材质属性。
(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。
这三个属性与光源的三个对应属性类似,每一属性都由四个值组成。
GL_AMBIENT表示各种光线照射到该材质上,经过很多次反射后最终遗留在环境中的光线强度(颜色)。
GL_DIFFUSE表示光线照射到该材质上,经过漫反射后形成的光线强度(颜色)。
GL_SPECULAR表示光线照射到该材质上,经过镜面反射后形成的光线强度(颜色)。
通常,GL_AMBIENT和GL_DIFFUSE都取相同的值,可以达到比较真实的效果。
使用GL_AMBIENT_AND_DIFFUSE可以同时设置GL_AMBIENT和GL_DIFFUSE属性。
(2)GL_SHININESS属性。
该属性只有一个值,称为“镜面指数”,取值范围是0到128。
该值越小,表示材质越粗糙,点光源发射的光线照射到上面,也可以产生较大的亮点。
该值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。
(3)GL_EMISSION属性。
该属性由四个值组成,表示一种颜色。
OpenGL认为该材质本身就微微的向外发射光线,以至于眼睛感觉到它有这样的颜色,但这光线又比较微弱,以至于不会影响到其它物体的颜色。
(4)GL_COLOR_INDEXES属性。
该属性仅在颜色索引模式下使用,由于颜色索引模式下的光照比RGBA模式要复杂,并且使用范围较小,这里不做讨论。
*/
glMaterialfv(GL_FRONT, GL_AMBIENT, sun_mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, sun_mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, sun_mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, sun_mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, sun_mat_shininess);
glutSolidSphere(2.0, 40, 32);
}
// 定义地球的材质并绘制地球
{
GLfloat earth_mat_ambient[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat earth_mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat earth_mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
GLfloat earth_mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat earth_mat_shininess = 30.0f;
glMaterialfv(GL_FRONT, GL_AMBIENT, earth_mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, earth_mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, earth_mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, earth_mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, earth_mat_shininess);
glRotatef(angle, 0.0f, -1.0f, 0.0f);
glTranslatef(5.0f, 0.0f, 0.0f);
glutSolidSphere(2.0, 40, 32);
}
glutSwapBuffers();
}
void myIdle(void)
{
angle += 1.0f;
if( angle >= 360.0f )
angle = 0.0f;
myDisplay();
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(200, 200);
glutInitWindowSize(WIDTH, HEIGHT);
glutCreateWindow("OpenGL光照演示");
glutDisplayFunc(&myDisplay);
glutIdleFunc(&myIdle);
glutMainLoop();
return 0;
}
效果
各位读者也可以修改上面的属性等参数,尝试获取不同的效果。
(上面的内容如有不正确的地方请及时与我联系修正,谢谢!)