本节是OpenGL学习的第八个课时,下面将详细介绍OpenGL的颜色模式,颜色混合以及抗锯齿。
(1)颜色模式:
OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式。
RGBA模式与索引模式的区别:
计算机必须为每个像素保存一些数据,在RGBA模式中数据就代表了颜色;而颜色索引模式中数据代表了一个索引,要获取真正的颜色值还需要查索引表。数据的数量是由帧缓存中的位面决定的。一个位面为一个像素的一个位的数据。假如是8位面的颜色,每个像素就有8个颜色位,因此就有2的8次方位,也就是256种不同的颜色值,或者说每个像素可以有256种颜色。位面通常分为RGB三个组成部分。
1)RGBA:
RGBA模式中,每一个像素会保存以下数据:R值(红色分量)、G值(绿色分量)、B值(蓝色分量)和A值(alpha分量)。其中红、绿、蓝三种颜色相组合,就可以得到我们所需要的各种颜色。而alpha不直接影响颜色,alpha通道一般用作不透明度参数。如果一个像素的alpha通道数值为0%,那它就是完全透明的,而数值为100%则意味着一个完全不透明的像素。在0%和100%之间的值则使得像素可以透过背景显示出来,就像透过玻璃(半透明性),这种效果是简单的二元透明性(透明或不透明)做不到的,它使数码合成变得容易。alpha通道值可以用百分比、整数或者像RGB参数那样用0到1的实数表示。
PNG是一种使用RGBA的图像格式。
glColor*系列函数可以用于设置颜色:(常用颜色参考表:http://blog.sina.com.cn/s/blog_4e20daf60102dydy.html)
void glColor3f(GLfloat red, GLfloat green, GLfloat blue); void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
注意:glColor系列函数,在参数类型不同时,表示“最大”颜色的值也不同:
1.采用f和d做后缀的函数,以1.0表示最大的使用。
2.采用b做后缀的函数,以127表示最大的使用。
3.采用ub做后缀的函数,以255表示最大的使用。
4.采用s做后缀的函数,以32767表示最大的使用。
5.采用us做后缀的函数,以65535表示最大的使用。
2)索引颜色:
在索引颜色模式中,OpenGL需要一个颜色表。颜色表的大小是很有限的,一般在256~4096之间,且总是2的整数次幂。在使用索引颜色方式进行绘图时,总是先设置颜色表,然后选择颜色。颜色表中的每一项保存了一种颜色。
使用glIndex*系列函数可以在颜色表中选择颜色:
void glIndexi(GLint c);
1.2.1设置颜色表
OpenGL并直接没有提供设置颜色表的方法,因此设置颜色表需要使用操作系统的支持。
我们在这里仅仅做一个简单测试,需要使用另一个OpenGL工具包:aux(已经过时)(下载:http://files.cnblogs.com/files/MenAngel/glAux.rar)
这个工具包需要就行如下配置:
1.glaux.dll放在C:WindowsSysWOW64; 2.GLAUX.H放(E:VSVCinclude)下; 3.GLAUX.lib放在(E:VSVClib)下; 具体还可以参考学习进程(2)
最后还要在VS的项目中进行如下配置:
1.2.2设置索引颜色绘制图形的实例:
#include <GL/glut.h> #include <glaux.h> #pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"") void paint(void); void InitPalette(void); void CALLBACK autoshape(GLsizei w, GLsizei h); void CALLBACK display(); void DrawColorFans(void); void paint(void) { glClearColor(0.4, 0.4, 0.4, 0.0); glClear(GL_COLOR_BUFFER_BIT); glShadeModel(GL_FLAT); } //重载的函数,用于设置自定义索引颜色表 void InitPalette(void) { GLint i; static GLfloat color[][3] = { { 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0 }, { 1.0, 0.0, 0.0 }, { 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0 }, { 0.0, 1.0, 1.0 }, { 1.0, 0.5, 0.5 }, { 0.0, 0.5, 0.5 } }; for (i = 0; i<8; i++) auxSetOneColor(i + 1, color[i][0], color[i][1], color[i][2]); } void CALLBACK autoshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho(-12.0, 12.0, -12.0*(GLfloat)h / (GLfloat)w, 12.0*(GLfloat)h / (GLfloat)w, -30.0, 30.0); else glOrtho(-12.0*(GLfloat)h / (GLfloat)w, 12.0*(GLfloat)h / (GLfloat)w, -12.0, 12.0, -30.0, 30.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void CALLBACK display(void) { InitPalette(); DrawColorFans(); glFlush(); } void DrawColorFans(void) { GLint n; //这里设置点的方式使用近似7*7*2=10*10 GLfloat point[8][2] = { { 7.0, -7.0 }, { 0.0, -10.0 }, { -7.0, -7.0 }, { -10.0, 0.0 }, { -7.0, 7.0 }, { 0.0, 10.0 }, { 7.0, 7.0 }, { 10.0, 0.0 } }; //绘制图形:三角形带 glBegin(GL_TRIANGLE_FAN); glVertex2f(0.0, 0.0); glVertex2f(10.0, 0.0); for (n = 0; n<8; n++) { //从颜色表中取色 glIndexi(n + 1); glVertex2fv(point[n]); } glEnd(); } void main() { //初始化绘图模式2D无交互,和Index颜色模式 auxInitDisplayMode(AUX_SINGLE | AUX_INDEX); auxInitPosition(200, 200, 500, 500); auxInitWindow("颜色索引"); paint(); //当绘制的窗体的大小发生改变时 auxReshapeFunc(autoshape); //持续回调函数 auxMainLoop(display); }
1.2.3清除屏幕用的颜色:
1.RGB模式:glClearColor(GL_COLOR_BUFFER_BIT) 2.索引颜色模式:glClearIndex(GLfloat i).(用法与glindexi类似)
1.2.4指定颜色模型:
在默认情况下,OpenGL会计算两点顶点之间的其它点,并为它们填上“合适”的颜色,使相邻的点的颜色值都比较接近。如果使用的是RGB模式,看起来就具有渐变的效果。如果是使用颜色索引模式,则其相邻点的索引值是接近的,如果将颜色表中接近的项设置成接近的颜色,则看起来也是渐变的效果。但如果颜色表中接近的项颜色却差距很大,则看起来可能是很奇怪的效果。使用glShadeModel函数可以关闭这种计算,如果顶点的颜色不同,则将顶点之间的其它点全部设置为与某一个点相同。(直线以后指定的点的颜色为准,而多边形将以任意顶点的颜色为准,由实现决定。)为了避免这个不确定性,尽量在多边形中使用同一种颜色。
glShadeModel的使用方法:
glShadeModel(GL_SMOOTH); // 平滑方式,这也是默认方式 glShadeModel(GL_FLAT); // 单色方式
(2)颜色混合:
混合就是把两种颜色混在一起。具体一点,就是把某一像素位置原来的颜色和将要画上去的颜色,通过某种方式混在一起,从而实现特殊的效果。
为什么引入颜色混合
材料属性和光照参数可以极大地增加图形的逼真度,但除此之外,我们在对现实世界进行建模时,有许多效果是通过混合颜色的方式实现的。透明的物体,像是玻璃水杯,在它后面发射过来的光会与透明物体的颜色混合在一起。这种透明在OpenGL中的实现方式,是通过首先绘制背景物体,然后把前景物体(比如水杯)与颜色缓冲区中已经存在的颜色进行混合而实现的。在这一过程中,颜色的alpha值成分发挥了重要作用。
1)实现原理:
在一般情况下,OpenGL在渲染时把颜色值存放在颜色缓冲区中,把每个片段(像素)的深度值存放在深度缓冲区中。当深度检测被关闭时,新的颜色值简单地覆盖颜色缓冲区中已经存在的颜色值;当深度检测被打开时,新的颜色值只有当它比原来的颜色更接近临近的裁剪平面时才会替换原来的颜色。当然,这是在OpenGL的混合功能被关闭的情况下。当混合功能被启用时,新的颜色会与颜色缓冲区中原有的颜色进行组合。通过对这些颜色进行不同的组合,可以产生许多种不同的效果。
注意:只有在RGBA模式下,才可以使用混合功能,颜色索引模式下是无法使用混合功能的。
目标颜色:已经存储在颜色缓冲区中的颜色称为目标颜色;
源颜色:当前渲染命令的结果进入颜色缓冲区中的颜色称为源颜色;
我们正是通过对目标颜色和源颜色进行不同的组合操作,来实现颜色混合的功能
当混合功能被启用时,源颜色和目标颜色的组合方式是由混合方程式来控制的。在默认情况下,使用的混合方程式如下所示:
Cf: 是最终计算产生的颜色
Cs: 是源颜色
Cd: 是目标颜色
S: 是源混合因子
D: 是目标混合因子
这两个混合因子可以通过下面的这个函数进行设置:
glBlendFunc(GLenum S, GLenum D);
枚举类型可能的值为:
GL_ZERO: 表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。
GL_ONE: 表示使用1.0作为因子,相当于完全的使用了这种颜色参与混合运算。
GL_SRC_ALPHA:表示使用源颜色的alpha值来作为因子。
GL_DST_ALPHA:表示使用目标颜色的alpha值来作为因子。
GL_ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
GL_ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。
示例:
假设的四个分量(指红色,绿色,蓝色,alpha值) 源颜色是(Rs, Gs, Bs, As),目标颜色是(Rd, Gd, Bd, Ad), 又设源因子为(Sr, Sg, Sb, Sa),目标因子为(Dr, Dg, Db, Da)。 则混合产生的新颜色可以表示为:
(Rs*Sr+Rd*Dr, Gs*Sg+Gd*Dg, Bs*Sb+Bd*Db, As*Sa+Ad*Da)
注意:如果颜色的某一分量超过了1.0,则它会被自动截取为1.0,不需要考虑越界的问题
2)开启或关闭:
要使用OpenGL的混合功能:glEnable(GL_BLEND);
要关闭OpenGL的混合功能:glDisable(GL_BLEND);
颜色混合模式绘制图形的示例:
#include <GL/glut.h> #include <stdlib.h> static int leftFirst = GL_TRUE; #pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"") /* Initialize alpha blending function. */ static void init(void) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //glBlendFunc(GL_ONE, GL_ONE); glShadeModel(GL_FLAT); glClearColor(0.0, 0.0, 0.0, 0.0); } static void drawLeftTriangle(void) { /* draw yellow triangle on LHS of screen */ glBegin(GL_TRIANGLES); glColor4f(1.0, 0.0, 0.0, 0.55); glVertex3f(0.1, 0.9, 0.0); glVertex3f(0.1, 0.1, 0.0); glVertex3f(0.7, 0.5, 0.0); glEnd(); } static void drawRightTriangle(void) { /* draw cyan triangle on RHS of screen */ glBegin(GL_TRIANGLES); glColor4f(0.0, 1.0, 0.0, 0.55); glVertex3f(0.9, 0.9, 0.0); glVertex3f(0.3, 0.5, 0.0); glVertex3f(0.9, 0.1, 0.0); glEnd(); } void display(void) { glClear(GL_COLOR_BUFFER_BIT); if (leftFirst) { drawLeftTriangle(); drawRightTriangle(); } else { drawRightTriangle(); drawLeftTriangle(); } glFlush(); } void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) gluOrtho2D(0.0, 1.0, 0.0, 1.0*(GLfloat)h / (GLfloat)w); else gluOrtho2D(0.0, 1.0*(GLfloat)w / (GLfloat)h, 0.0, 1.0); } void keyboard(unsigned char key, int x, int y) { switch (key) { case 't': case 'T': leftFirst = !leftFirst; glutPostRedisplay(); break; case 27: /* Escape key */ exit(0); break; default: break; } } /* Main Loop * Open window with initial window size, title bar, * RGBA display mode, and handle input events. */ int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(400, 400); glutInitWindowPosition(200,200); glutCreateWindow("颜色混合简单示例"); init(); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutDisplayFunc(display); glutMainLoop(); return 0; }
3)三维混合:(http://blog.sina.com.cn/s/blog_6c8369530100m0x8.html)
深度缓冲是这样一段数据,它记录了每一个像素距离观察者有多近。在启用深度缓冲测试的情况下,如果将要绘制的像素比原来的像素更近,则像素将被绘制。否则,像素就会被忽略掉,不进行绘制。显示结果总是近的物体遮住远的物体。
3D世界的绘制顺序:
1.首先绘制所有不透明的物体。如果两个物体都是不透明的,则谁先谁后没有关系。然后,将深度缓冲区设置为只读。 2.接下来,绘制所有半透明的物体。如果两个物体都是半透明的,则谁先谁后只需要根据自己的意愿(注意了,先绘制的将成为“目标颜色”,后绘制的将成为“源颜色”,所以绘制的顺序将会对结果造成一些影响)。 3.最后,将深度缓冲区设置为可读可写形式。 调用glDepthMask(GL_FALSE);可将深度缓冲区设置为只读形式。 调用glDepthMask(GL_TRUE);可将深度缓冲区设置为可读可写形式。
4)抗锯齿的处理:
混合功能的另一个用途是抗锯齿的处理。
为什么引入抗锯齿:
在绝大多数情况下,一个渲染片段映射到计算机屏幕上的一个像素。因为这些像素都是正方形的,所以通常我们可以清晰的看到两种不同颜色的分界,它们就是我们通常所说的锯齿。锯齿的出现会让人觉得图像是不自然的,极为影响观感。这种锯齿现象是计算机所产生的图像的弱点,这对于我们的许多要求尽可能逼真的渲染任务,都带来了消极的影响。
消除抗锯齿的原理:
为了消除图元之间的锯齿状边缘,OpenGL使用混合功能来混合片段的颜色,也就是把像素的目标颜色与周围相邻像素的颜色进行混合。从本质上说,在任何图元的边缘上,像素颜色都会稍微延伸到相邻的像素,以此来达到平滑像素颜色的效果。
2.4.1开启抗锯齿功能:
打开抗锯齿功能十分简单,首先我们必须启用混合功能,并对混合函数进行一些设置:
// 设置混合因子 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 启用混合 glEnable(GL_BLEND);
我们还需要确保把混合方程式设置为GL_ADD的模式,这也是默认的设置。
在启用混合功能并选择正确的混合方程式后,便可以调用glEnable函数对点、直线或多边形(任何实心图元)进行抗锯齿处理:
// 启用点平滑处理 glEnable(GL_POINT_SMOOTH); // 设置为画质最优的方式 glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); // 启用直线平滑处理 glEnable(GL_LINE_SMOOTH); // 设置为画质最优的方式 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // 启用多边形平滑处理 glEnable(GL_POLYGON_SMOOTH); // 设置为画质最优的方式 glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
2.4.2抗锯齿讲解示例:(下载地址:http://files.cnblogs.com/files/MenAngel/HeadFile.zip)
需要下载math3d.h Glee.h gltools.h,然后放在%vs%/vc/include目录下。
注意:此测试文件的后缀名必须为.cpp,不然math3d.h会报120个错误:
#include <GL/glut.h> #include <math.h> #include <math3d.h> #pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"") #define SMALL_STARS 100 M3DVector2f vSmallStars[SMALL_STARS]; #define MEDIUM_STARS 40 M3DVector2f vMediumStars[MEDIUM_STARS]; #define LARGE_STARS 15 M3DVector2f vLargeStars[LARGE_STARS]; #define SCREEN_X 400 #define SCREEN_Y 300 // 渲染场景的方法 void RenderScene() { int i; GLfloat x = 200.0f; GLfloat y = 150.0f; GLfloat r = 150.0f; GLfloat angle = 0.0f; // 清除颜色缓冲区和深度缓冲区 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //画分割线,分成五个视见区 glViewport(0, 0, 400, 300); //多组双顶点线段,点成对出现 glLineWidth(2.0); glColor3f(1.0f, 1.0f, 0.0f); glBegin(GL_LINES); glVertex2f(0.0, 200); glVertex2f(400.0,200); glVertex2f(0.0, 100); glVertex2f(400.0,100); glVertex2f(200.0, 300); glVertex2f(200.0, 100); glEnd(); // 设置绘图的颜色为白色 glColor3f(1.0f, 1.0f, 1.0f); //坐标范围从(0,0)到(800,600) glViewport(0, 0, 400, 300); glViewport(0, 200, 200, 100); // 绘制小号的顶点 glPointSize(1.0f); glBegin(GL_POINTS); for (i = 0; i < SMALL_STARS; ++i) { glVertex2fv(vSmallStars[i]); } glEnd(); glViewport(200, 200, 200, 100); // 绘制中号的顶点 glPointSize(4.0f); glBegin(GL_POINTS); for (i = 0; i< MEDIUM_STARS; i++) glVertex2fv(vMediumStars[i]); glEnd(); glViewport(0, 100, 200, 100); // 绘制大号的顶点 glPointSize(7.0f); glBegin(GL_POINTS); for (i = 0; i < LARGE_STARS; i++) glVertex2fv(vLargeStars[i]); glEnd(); glViewport(200, 100, 200, 100); // 绘制三角形带 glBegin(GL_TRIANGLE_FAN); glVertex2f(x, y);//图形中点 for (angle = 0; angle < 2.0f * 3.141592f; angle += 0.1f){ glVertex2f(x + (float)cos(angle) * r, y + (float)sin(angle) * r);//这些点都在以(x,y)为圆心,r为半径的圆上 glVertex2f(x + r, y); } glEnd(); glViewport(0, 0, 400, 100); // 绘制一条线带 glLineWidth(4.0f); glBegin(GL_LINE_STRIP); glVertex2f(0.0f, 25.0f+100.0f); glVertex2f(50.0f, 100.0f + 100.0f); glVertex2f(100.0f, 25.0f + 100.0f); glVertex2f(225.0f, 125.0f + 100.0f); glVertex2f(300.0f, 50.0f + 100.0f); glVertex2f(375.0f, 100.0f + 100.0f); glVertex2f(460.0f, 25.0f + 100.0f); glVertex2f(525.0f, 100.0f + 100.0f); glVertex2f(600.0f, 20.0f + 100.0f); glVertex2f(675.0f, 70.0f + 100.0f); glVertex2f(750.0f, 25.0f + 100.0f); glVertex2f(800.0f, 90.0f + 100.0f); glEnd(); // 交换缓冲区,显示画面 glutSwapBuffers(); } // 设置渲染状态的方法 void SetupRC() { int i; // 初始化小号顶点的位置数组 for (i = 0; i < SMALL_STARS; ++i) { vSmallStars[i][0] = (GLfloat)(rand() % SCREEN_X); vSmallStars[i][1] = (GLfloat)(rand() % (SCREEN_Y - 100)) + 50.0f; } // 初始化中号顶点的位置数组 for (i = 0; i < MEDIUM_STARS; i++) { vMediumStars[i][0] = (GLfloat)(rand() % SCREEN_X * 10) / 10.0f; vMediumStars[i][1] = (GLfloat)(rand() % (SCREEN_Y - 100)) + 50.0f; } // 初始化大号顶点的位置数组 for (i = 0; i < LARGE_STARS; i++) { vLargeStars[i][0] = (GLfloat)(rand() % SCREEN_X * 10) / 10.0f; vLargeStars[i][1] = (GLfloat)(rand() % (SCREEN_Y - 100)*10.0f) / 10.0f + 50.0f; } // 设置清除颜色为黑色 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } // 处理右键菜单的方法 void ProcessMenu(int value) { switch (value) { case 1: // 设置混合因子 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 启用混合 glEnable(GL_BLEND); // 启用点平滑处理 glEnable(GL_POINT_SMOOTH); // 设置为画面最优的方式 glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); // 启用直线平滑处理 glEnable(GL_LINE_SMOOTH); // 设置为画面最优的方式 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // 启用多边形平滑处理 glEnable(GL_POLYGON_SMOOTH); // 设置为画面最优的方式 glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); break; case 2: // 关闭混合 glDisable(GL_BLEND); // 关闭点平滑处理 glDisable(GL_POINT_SMOOTH); // 关闭直线平滑处理 glDisable(GL_LINE_SMOOTH); // 关闭多边形平滑处理 glDisable(GL_POLYGON_SMOOTH); break; default: break; } glutPostRedisplay(); } void ChangeSize(int w, int h) { if (h == 0) h = 1; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); //把窗体投影到宽度为400,高度为300的裁剪平面 gluOrtho2D(0.0, SCREEN_X, 0.0, SCREEN_Y); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } int main(int argc, char* argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(400, 300); glutCreateWindow("抗锯齿效果示例"); // 创建右键菜单 glutCreateMenu(ProcessMenu); glutAddMenuEntry("Open", 1); glutAddMenuEntry("Close", 2); glutAttachMenu(GLUT_RIGHT_BUTTON); glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); SetupRC(); glutMainLoop(); return 0; }
(3)用灯光和混合颜色模式模拟地板反射效果:(仅作了解)
在进行场景的绘制时,首先以上下颠倒的方式,最先绘制地板下方的球体;然后打开混合,在球体的上面绘制一层透明的地板;最后恢复颠倒后的坐标系,绘制地板上方的球体。这三部分完成后,一幅地板反射球体的幻觉画面就完成了:
#include <GL/glut.h> #include <math3d.h> #pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"") GLfloat fLightPos[4] = { -100.0f, 100.0f, 50.0f, 1.0f }; GLfloat fLightPosMirror[4] = { -100.0f, -100.0f, 50.0f, 1.0f }; GLfloat fNoLight[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; GLfloat fLowLight[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; GLfloat fBrightLight[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; static GLfloat yRot = 0.0f; void SetupRC() { glClearColor(fLowLight[0], fLowLight[1], fLowLight[2], fLowLight[3]); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fNoLight); glLightfv(GL_LIGHT0, GL_AMBIENT, fNoLight); glLightfv(GL_LIGHT0, GL_DIFFUSE, fLowLight); glLightfv(GL_LIGHT0, GL_SPECULAR, fBrightLight); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glMateriali(GL_FRONT, GL_SHININESS, 128); } void DrawGround() { GLfloat fExtent = 20.0f; GLfloat fStep = 0.5f; GLfloat y = 0.0f; GLfloat fColor; GLfloat iStrip, iRun; GLint iBounce = 0; glShadeModel(GL_FLAT); for (iStrip = -fExtent; iStrip <= fExtent; iStrip += fStep) { glBegin(GL_TRIANGLE_STRIP); for (iRun = fExtent; iRun >= -fExtent; iRun -= fStep) { if ((iBounce % 2) == 0) fColor = 1.0f; else fColor = 0.0f; glColor4f(fColor, fColor, fColor, 0.5f); glVertex3f(iStrip, y, iRun); glVertex3f(iStrip + fStep, y, iRun); iBounce++; } glEnd(); } glShadeModel(GL_SMOOTH); } void DrawSphere() { glColor3f(0.0f, 0.0f, 1.0f); glPushMatrix(); glTranslatef(0.0f, 0.5f, -3.5f); glPushMatrix(); //glRotatef(-yRot * 2.0f, 0.0f, 1.0f, 0.0f); glutSolidSphere(0.32f, 32, 9); glPopMatrix(); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glPopMatrix(); } void RenderScene() { // 清空颜色缓冲区和深度缓冲区 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // ----------------------- 首先绘制地板下方的球体 -------------------- // 保存矩阵状态 glPushMatrix(); // 将光源0的位置摆放到地板下方 glLightfv(GL_LIGHT0, GL_POSITION, fLightPosMirror); // 保存矩阵状态 glPushMatrix(); // 设置顺时针环绕的一面为多边形的正面,对多边形进行镜像 glFrontFace(GL_CW); // 在Y轴使用-1的缩放因子来反转Y轴 glScalef(1.0f, -1.0f, 1.0f); // 绘制地板下方的球体 DrawSphere(); // 设置逆时针环绕的一面为多边形的正面 glFrontFace(GL_CCW); // 恢复矩阵状态 glPopMatrix(); // ----------------- 通过对地板设置透明的颜色混合效果,来实现反射的幻觉 ----------------- // 关闭光照计算 glDisable(GL_LIGHTING); // 打开颜色混合 glEnable(GL_BLEND); // 设置混合因子 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 绘制地板 DrawGround(); // 关闭颜色混合 glDisable(GL_BLEND); // 打开光照计算 glEnable(GL_LIGHTING); // -------------------- 最后绘制地板上方的球体 --------------------------- // 将光源0的位置摆放到地板上面 glLightfv(GL_LIGHT0, GL_POSITION, fLightPos); // 绘制地板上方的球体 DrawSphere(); // 恢复矩阵状态 glPopMatrix(); // 执行缓冲区的交换 glutSwapBuffers(); } void TimerFunction(int value) { yRot += 1.0f; glutPostRedisplay(); glutTimerFunc(1, TimerFunction, 1); } void ChangeSize(int w, int h) { GLfloat fAspect; if (h == 0) h = 1; glViewport(0, 0, w, h); fAspect = (GLfloat)w / (GLfloat)h; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(35.0f, fAspect, 1.0f, 50.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0f, -0.4f, 0.0f); } int main(int argc, char* argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(800, 600); glutCreateWindow("使用混合模式模拟反射效果"); glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); //glutTimerFunc(10, TimerFunction, 1); SetupRC(); glutMainLoop(); return 0; }
(4)相关知识:
1)void glCullFace(GLenum mode):
mode:指明多边形的前面或后面是否被剔除。允许的符号常量有:GL_FRONT,GL_BACK和GL_FRONT_AND_BACK。初始值为GL_BACK。
本函数可以禁用多边形正面或背面上的光照、阴影和颜色计算及操作,消除不必要的渲染计算是因为无论对象如何进行旋转或变换,都不会看到多边形的背面。用GL_CULL_FACE参数调用glEnable和glDisable可以启用或禁用剔除。
注意:如果mode是GL_FRONT_AND_BACK,多边形不会被绘出,但是其他图元比如点、线会被绘出。
用法:表示禁用多边形正面或者背面上的光照、阴影和颜色计算及操作,消除不必要的渲染计算。 例如某对象无论如何位置变化,我们都只能看到构成其组成的多边形的某一面时,可使用该函数。
2)void glFrontFace(GLenum mode):
mode指明前面多边形的方向。可选的值有GL_CW和GL_CCW。默认值是GL_CCW。
用法:在一个全部由不透明封闭表面组成的场景中,背面多边形是永远看不见的。剔除这些不可见的多边形对于加速图形的渲染有很大的益处。如果一个虚构的对象的顶点是按照多边形内部顺时针的方向进行绘制的,那么可以称这个多边形基于窗口坐标的投影是顺时针的。反之,则为逆时针。glFrontFace就是用来指定多边形在窗口坐标中的方向是逆时针还是顺时针的。GL_CCW说明逆时针多边形为正面,而GL_CW说明顺时针多边形为正面。默认是逆时针多边形为正面。
开启和关闭剔除功能可以调用带GL_CULL_FACE参数的glEnable和glDisable函数。默认剔除功能是关闭的。
3)void glLightModelf(GLenum pname, GLfloat param)和void glLightModelx(GLenum pname, GLfixed param):
glLightModel —— 用于设置光照模型参数。
4.3.1pname:
指定一个单值光照模型参数。必须是GL_LIGHT_MODEL_TWO_SIDE或者GL_LIGHT_MODEL_AMBIENT。
GL_LIGHT_MODEL_AMBIENT:
params包含四个定点或浮点值来指定整个场景的环境光强度。初始值是(0.2,0.2,0.2,1.0)。
GL_LIGHT_MODEL_TWO_SIDE:
params是一个单定点或浮点数来指定多边形要不要进行一边或两边的光照计算。它对点和线的光照计算没有任何作用。如果params的值是0,一边的光照被指定,前面和后面的多边形将被赋予相同的计算颜色。如果params的值不是0,那么两边的光照都被指定。
4.3.2param:
指定要设置的参数指针。
4)glLightfv(GL_LIGHT0, GL_POSITION, light_position):
指定第0号光源的位置。一般支持OpenGL支持8个光照。
这两句是告诉OPENGL在后面的渲染中使用光照,默认情况下是关闭的:
glEnable(GL_LIGHTING); 启用灯光
glEnable(GL_LIGHT0);启用0号光源
glEnable(GL_COLOR_MATERIAL);启用颜色追踪
5)如何使用一个光源:
glLightfv(GL_LIGHT0, GL_POSITION, light_position);//指定光源位置 glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient); //光源中的环境光强度 glLightfv(GL_LIGHT0,GL_DIFFUSE,white_light); //光源中的散射光强度 glLightfv(GL_LIGHT0,GL_SPECULAR,white_light); //光源中的镜面反射光强度
6)void WINAPI glMateriali( GLenum face, GLenum pname, GLint param ):
The glMateriali function specifies material parameters for the lighting model.
glLightfv是光源glLightModelfv是材质。
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient); //材质属性中的环境光 glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse); //材质属性中的散射光 glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); //材质属性中的镜面反射光 glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); //材质属性的镜面反射指数 glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission); //材质属性中的发射光
7)void glScalef(GLfloat x, GLfloat y, GLfloat z):
模型变换函数,用作缩放。
模型变换的目的是设置模型的位置和方向,例如可以对模型进行旋转、移动和缩放,或者联合这几种操作。
glScalef (1.0, 2.0, 1.0);//表示y坐标值扩大两倍,这样原本方的物体就变成长的了。