学习书籍: OpenGL 超级宝典(中文第五版) 密码:fu4w
书籍源码:OpenGL 超级宝典第五版源代码 密码:oyb4
环境搭建:OpenGL 学习 01 - Mac 搭建 OpenGL 环境
基本概念 多边形偏移上一节在 OpenGL 学习 05 - 花托 中,我们通过深度测试实现真实视觉并提高性能,但会遇到一些麻烦,比如我们想将2个几何图形绘制在同一个位置,我们想要画一架大飞机,然后在飞机上绘制一个五角星图案,这叫做“贴花”。这 2 个图形的深度值 z 相同或者几乎相同,这种情况称为 深度值冲突 。
处理深度值冲突的方法:
手动调整 z 值进行一点点偏移,但可能会出现图形悬浮(不推荐)
- 利用 多边形偏移 调节片段的深度值,但实际不改变 3D 空间物理位置(推荐)
应用到片段上的总偏移方程式如下,其中 DZ 是深度值相对多边形屏幕区域的变化量,r 是使深度缓冲区值产生变化的最小值,这2个值都是 OpenGL 内部的值,我们不用关心,我们是通过控制 factor 和 units 达到效果:
// 设置多边形偏移 void glPolygonOffset(GLfloat factor, GLfloat units);裁剪
除了深度测试,还有一种提高渲染性能的方法,那就是 只刷新屏幕上发生变化的部分,即裁剪。
OpenGL 允许我们在将要进行渲染的窗口中指定一个裁剪框,只刷新裁剪框里面的变化。默认裁剪框和窗口大小一致,并不会进行裁剪测试。
//设置裁剪框位置和大小 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);混合
通常情况下,OpenGL 渲染时会把颜色值放在颜色缓存区中,任何绘制操作都是完全覆盖原来的颜色值,比如我们在一个红色图形前面画一个蓝色图形,在重叠的部分,蓝色覆盖红色。
但如果我们想在重叠区显示重叠颜色(比如红蓝混合色)怎么办?这时就需要使用到 OpenGL 的混合功能。
目标颜色:已经存储在颜色缓冲区中的颜色
源颜色:将要加入进行混合的颜色
混合方程式:目标颜色和源颜色的组合方式,用来生成混合后的颜色
其中 Cf 是最终产生颜色,Cs 为源颜色,Cd 为目标颜色,S 和 D 分别为源颜色和目标颜色的混合因子
// 我们通过控制 S 和 D 混合因子控制混合方程式输出,S 和 D 都是枚举值 void glBlendFunc(CLenum S, GLenum D);
看到上面的混合因子表,一开始是懵逼的,我们来简单计算一下,到底表示的意义是什么,下面是其中一种情况的计算过程,其他类推:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
但是这会带来一个问题,当我们进行了颜色混合后,最终目标颜色分量 Alpha 通道同时也被改变了——原来 Alpha 是 0.6,混合后变成了 0.76。这时我们就需要另外一个函数 glBlendFuncSeparate:
//允许分别为 RGB 通道和 Alpha 通道设置混合因子(OpenGL 2.0 开始支持) void glBlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
除了上面默认的混合方程式,也有其他混合方程式:
//改变混合方程式等式结构 void glBlendEquation(GLenum mode); //修改常量颜色,即上面表中的 Rc、Gc、Bc、Ac void glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);抗锯齿
OpenGL 混合的另外一种用途就是抗锯齿。图形边缘会出现一些吸引眼睛的注意力而让人感觉图形不自然的像素点,称为锯齿。我们需要尽可能的逼真,尤其在游戏、模拟和艺术创造中。为了消除图元之间的锯齿,OpenGL 利用混合功能,把像素的目标颜色和周围像素的颜色进行混合。
源码解析 多边形偏移抗锯齿功能开启条件:
开启混合功能
设置混合方程式为 GL_ADD
设置混合方程式因子为 S = GL_SRC_ALPHA, D = GL_ONE_MINUS_SRC_ALPHA
- 开启抗锯齿功能(点/线/多边形)
核心代码:
//设置多边形偏移的总偏移 glPolygonOffset(-1.0f, -1.0f); //开启多边形偏移 glEnable(GL_POLYGON_OFFSET_POINT); // 点 glEnable(GL_POLYGON_OFFSET_LINE); // 线 glEnable(GL_POLYGON_OFFSET_UNITS); // 图形 //关闭多边形偏移 glDisable(GL_POLYGON_OFFSET_POINT); // 点 glDisable(GL_POLYGON_OFFSET_LINE); // 线 glDisable(GL_POLYGON_OFFSET_UNITS); // 图形
Demo 源码: 之前文章的 03-Primitives Demo 中的三角形带中有运用到,这里就不重复拷贝了。
裁剪核心代码:
//开启裁剪 glEnable(GL_SCISSOR_TEST); //设置颜色缓冲区背景为红色 glClearColor(1.0f, 0.0f, 0.0f, 0.0f); //裁剪出(x: 100, y: 100, w: 600, h: 400)区域 glScissor(100, 100, 600, 400); //清空颜色缓存区,同步到后台缓冲区,注意这里会设置的是裁剪区域 glClear(GL_COLOR_BUFFER_BIT); //关闭裁剪 glDisable(GL_SCISSOR_TEST);
Demo 源码: 05-Scissor
#include混合// OpenGL toolkit #include //渲染画面 void RenderScene(void) { //设置颜色缓冲区背景色为蓝色 glClearColor(0.0f, 0.0f, 1.0f, 0.0f); //清空颜色缓存区,设置到后台缓存区,执行完这步才绘制了背景颜色 glClear(GL_COLOR_BUFFER_BIT); //开启裁剪 glEnable(GL_SCISSOR_TEST); //设置颜色缓冲区背景色为红色 glClearColor(1.0f, 0.0f, 0.0f, 0.0f); //裁剪出(x: 100, y: 100, w: 600, h: 400)区域 glScissor(100, 100, 600, 400); //清空颜色缓存区,设置到后台缓冲区,注意这里会设置的是裁剪区域 glClear(GL_COLOR_BUFFER_BIT); //设置颜色缓冲区背景色为绿色 glClearColor(0.0f, 1.0f, 0.0f, 0.0f); //裁剪出(x: 200, y: 200, w: 400, h: 200)区域 glScissor(200, 200, 400, 200); //清空颜色缓存区,设置到后台缓冲区,注意这里会设置的是裁剪区域 glClear(GL_COLOR_BUFFER_BIT); //关闭裁剪 glDisable(GL_SCISSOR_TEST); //将在后台缓冲区进行渲染,然后在结束时交换到前台 glutSwapBuffers(); } //窗口大小改变时接受新的宽度和高度 void ChangeSize(int w, int h) { //设置视图窗口位置 glViewport(0, 0, w, h); } //程序入口 int main(int argc, char* argv[]) { //设置当前工作目录,针对MAC OS X gltSetWorkingDirectory(argv[0]); //初始化GLUT库 glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); //初始化窗口大小 glutInitWindowSize(800, 600); //创建窗口 glutCreateWindow("OpenGL Scissor"); //注册回调函数 glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); //确保驱动程序的初始化中没有出现任何问题。 GLenum err = glewInit(); if(GLEW_OK != err) { fprintf(stderr, "glew error:%sn", glewGetErrorString(err)); return 1; } //进入调用循环 glutMainLoop(); return 0; }
核心源码:
//开启颜色混合 glEnable(GL_BLEND); //配置混合方程式,默认为 GL_FUNC_ADD 方程 glBlendEquation(GL_FUNC_ADD); //配置混合方程式混合因子 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //分别设置 RGB 通道和 Alpha 通道混合因子 glBlendFuncSeparate(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //改变常量颜色 glBlendColor(1.0f, 0.0f, 0.0f, 1.0f); //关闭颜色混合 glDisable(GL_BLEND);
Demo 源码: 06-Blending
#include抗锯齿// OpenGL toolkit #include #include GLBatch squareBatch; GLBatch greenBatch; GLBatch redBatch; GLBatch blueBatch; GLBatch blackBatch; GLShaderManager shaderManager; GLfloat blockSize = 0.2f; GLfloat vVerts[] = { -blockSize, -blockSize, 0.0f, blockSize, -blockSize, 0.0f, blockSize, blockSize, 0.0f, -blockSize, blockSize, 0.0f }; //程序一次性初始化 void SetupRC() { //设置窗口背景为黑色 glClearColor(1.0f, 1.0f, 1.0f, 1.0f ); //初始化着色器管理者 shaderManager.InitializeStockShaders(); //创建移动矩形批次 squareBatch.Begin(GL_TRIANGLE_FAN, 4); squareBatch.CopyVertexData3f(vVerts); squareBatch.End(); //绿色矩形批次 GLfloat vBlock[] = { 0.25f, 0.25f, 0.0f, 0.75f, 0.25f, 0.0f, 0.75f, 0.75f, 0.0f, 0.25f, 0.75f, 0.0f }; greenBatch.Begin(GL_TRIANGLE_FAN, 4); greenBatch.CopyVertexData3f(vBlock); greenBatch.End(); //红色矩形批次 GLfloat vBlock2[] = { -0.75f, 0.25f, 0.0f, -0.25f, 0.25f, 0.0f, -0.25f, 0.75f, 0.0f, -0.75f, 0.75f, 0.0f }; redBatch.Begin(GL_TRIANGLE_FAN, 4); redBatch.CopyVertexData3f(vBlock2); redBatch.End(); //蓝色矩形批次 GLfloat vBlock3[] = { -0.75f, -0.75f, 0.0f, -0.25f, -0.75f, 0.0f, -0.25f, -0.25f, 0.0f, -0.75f, -0.25f, 0.0f }; blueBatch.Begin(GL_TRIANGLE_FAN, 4); blueBatch.CopyVertexData3f(vBlock3); blueBatch.End(); //黑色矩形批次 GLfloat vBlock4[] = { 0.25f, -0.75f, 0.0f, 0.75f, -0.75f, 0.0f, 0.75f, -0.25f, 0.0f, 0.25f, -0.25f, 0.0f }; blackBatch.Begin(GL_TRIANGLE_FAN, 4); blackBatch.CopyVertexData3f(vBlock4); blackBatch.End(); } //特殊按钮监听 void SpecialKeys(int key, int x, int y) { GLfloat stepSize = 0.025f; //左上角的 X 坐标和右下角的 Y 坐标 GLfloat blockX = vVerts[0]; GLfloat blockY = vVerts[7]; //根据移动方向移动位置 switch (key) { case GLUT_KEY_UP: blockY += stepSize; break; case GLUT_KEY_DOWN: blockY -= stepSize; break; case GLUT_KEY_LEFT: blockX -= stepSize; break; case GLUT_KEY_RIGHT: blockX += stepSize; break; default: break; } //移动边界处理 if(blockX < -1.0f) blockX = -1.0f; if(blockX > (1.0f - blockSize * 2)) blockX = 1.0f - blockSize * 2;; if(blockY < -1.0f + blockSize * 2) blockY = -1.0f + blockSize * 2; if(blockY > 1.0f) blockY = 1.0f; //矩形四个顶点位置 vVerts[0] = blockX; vVerts[1] = blockY - blockSize*2; vVerts[3] = blockX + blockSize*2; vVerts[4] = blockY - blockSize*2; vVerts[6] = blockX + blockSize*2; vVerts[7] = blockY; vVerts[9] = blockX; vVerts[10] = blockY; //批次顶点数据编号 squareBatch.CopyVertexData3f(vVerts); //触发渲染 glutPostRedisplay(); } //渲染画面 void RenderScene(void) { //清理各个缓存区 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); //定义4种颜色 GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f }; GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 1.0f }; GLfloat vBlue[] = { 0.0f, 0.0f, 1.0f, 0.6f }; GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f }; //画绿色矩形 shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vGreen); greenBatch.Draw(); //画红色矩形 shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed); redBatch.Draw(); //画蓝色矩形 shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlue); blueBatch.Draw(); //画黑色矩形 shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlack); blackBatch.Draw(); //开启颜色混合 glEnable(GL_BLEND); //配置混合方程式 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //画移动矩形,本身半透明蓝色 shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlue); squareBatch.Draw(); //关闭颜色混合 glDisable(GL_BLEND); //将在后台缓冲区进行渲染,然后在结束时交换到前台 glutSwapBuffers(); } //窗口大小改变时接受新的宽度和高度 void ChangeSize(int w, int h) { //设置视图窗口位置 glViewport(0, 0, w, h); } //程序入口 int main(int argc, char* argv[]) { //设置当前工作目录,针对MAC OS X gltSetWorkingDirectory(argv[0]); //初始化GLUT库 glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); //初始化窗口大小 glutInitWindowSize(800, 600); //创建窗口 glutCreateWindow("Move Block with Arrow Keys to see blending"); //注册回调函数 glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); glutSpecialFunc(SpecialKeys); //确保驱动程序的初始化中没有出现任何问题。 GLenum err = glewInit(); if(GLEW_OK != err) { fprintf(stderr, "glew error:%sn", glewGetErrorString(err)); return 1; } //初始化设置 SetupRC(); //进入调用循环 glutMainLoop(); return 0; }
核心代码:
//开启抗锯齿处理,必须先开启颜色混合模式 glBlendEquation(GL_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); //抗锯齿开启 glEnable(GL_POINT_SMOOTH); glEnable(GL_LINE_SMOOTH); glEnable(GL_POLYGON_SMOOTH); //设置抗锯齿处理达到效果最好(另外一个是效果最快) glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); //关闭抗锯齿处理 glDisable(GL_BLEND); glDisable(GL_LINE_SMOOTH); glDisable(GL_POINT_SMOOTH); glDisable(GL_POLYGON_SMOOTH);
Demo 源码: 07-Smoother
#include// OpenGL toolkit #include #include GLShaderManager shaderManager; GLFrustum viewFrustum; GLBatch smallStarBatch; GLBatch mediumStarBatch; GLBatch largeStarBatch; GLBatch mountainRangeBatch; GLBatch moonBatch; //常量宏 #define SMALL_STARS 100 #define MEDIUM_STARS 40 #define LARGE_STARS 15 #define SCREEN_X 800 #define SCREEN_Y 600 //点击菜单选项触发的回调方法 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_LINE_SMOOTH); glDisable(GL_POINT_SMOOTH); glDisable(GL_POLYGON_SMOOTH); break; default: break; } //触发渲染 glutPostRedisplay(); } //渲染画面 void RenderScene(void) { //清除缓存区 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //设置着色器为单位黑色 GLfloat vWhite [] = { 1.0f, 1.0f, 1.0f, 1.0f }; shaderManager.UseStockShader(GLT_SHADER_FLAT, viewFrustum.GetProjectionMatrix(), vWhite); //画小点 glPointSize(1.0f); smallStarBatch.Draw(); //画大点 glPointSize(4.0f); mediumStarBatch.Draw(); //画超大点 glPointSize(8.0f); largeStarBatch.Draw(); //画月亮 moonBatch.Draw(); //画山的轮廓 glLineWidth(3.5); mountainRangeBatch.Draw(); //将在后台缓冲区进行渲染,然后在结束时交换到前台 glutSwapBuffers(); } //初始化小点批次 void SetupSmallStarBatch() { M3DVector3f vVerts[SMALL_STARS]; for(int i = 0; i < SMALL_STARS; i++) { vVerts[i][0] = (GLfloat)(rand() % SCREEN_X); vVerts[i][1] = (GLfloat)(rand() % (SCREEN_Y - 100)) + 100.0f; vVerts[i][2] = 0.0f; } smallStarBatch.Begin(GL_POINTS, SMALL_STARS); smallStarBatch.CopyVertexData3f(vVerts); smallStarBatch.End(); } //初始化大点批次 void SetupMeiumStarBatch() { M3DVector3f vVerts[MEDIUM_STARS]; for(int i = 0; i < MEDIUM_STARS; i++) { vVerts[i][0] = (GLfloat)(rand() % SCREEN_X); vVerts[i][1] = (GLfloat)(rand() % (SCREEN_Y - 100)) + 100.0f; vVerts[i][2] = 0.0f; } mediumStarBatch.Begin(GL_POINTS, MEDIUM_STARS); mediumStarBatch.CopyVertexData3f(vVerts); mediumStarBatch.End(); } //初始化超大点批次 void SetupLargeStarBatch() { M3DVector3f vVerts[LARGE_STARS]; for(int i = 0; i < LARGE_STARS; i++) { vVerts[i][0] = (GLfloat)(rand() % SCREEN_X); vVerts[i][1] = (GLfloat)(rand() % (SCREEN_Y - 100)) + 100.0f; vVerts[i][2] = 0.0f; } largeStarBatch.Begin(GL_POINTS, LARGE_STARS); largeStarBatch.CopyVertexData3f(vVerts); largeStarBatch.End(); } //初始化山轮廓批次 void SetupMountainRangeBatch() { M3DVector3f vMountains[12] = { 0.0f, 25.0f, 0.0f, 50.0f, 100.0f, 0.0f, 100.0f, 25.0f, 0.0f, 225.0f, 125.0f, 0.0f, 300.0f, 50.0f, 0.0f, 375.0f, 100.0f, 0.0f, 460.0f, 25.0f, 0.0f, 525.0f, 100.0f, 0.0f, 600.0f, 20.0f, 0.0f, 675.0f, 70.0f, 0.0f, 750.0f, 25.0f, 0.0f, 800.0f, 90.0f, 0.0f }; mountainRangeBatch.Begin(GL_LINE_STRIP, 12); mountainRangeBatch.CopyVertexData3f(vMountains); mountainRangeBatch.End(); } //初始化月亮批次 void SetupMoonBatch() { GLfloat x = 700.0f; GLfloat y = 500.0f; GLfloat r = 50.0f; M3DVector3f vVerts[SMALL_STARS]; int nVerts = 0; vVerts[nVerts][0] = x; vVerts[nVerts][1] = y; vVerts[nVerts][2] = 0.0f; for(GLfloat angle = 0; angle < M3D_2PI; angle += 0.2f) { nVerts++; vVerts[nVerts][0] = x + float(cos(angle)) * r; vVerts[nVerts][1] = y + float(sin(angle)) * r; vVerts[nVerts][2] = 0.0f; } nVerts++; vVerts[nVerts][0] = x + r;; vVerts[nVerts][1] = y; vVerts[nVerts][2] = 0.0f; moonBatch.Begin(GL_TRIANGLE_FAN, 34); moonBatch.CopyVertexData3f(vVerts); moonBatch.End(); } //程序化一次性初始化 void SetupRC() { //设置背景色为黑色 glClearColor(0.0f, 0.0f, 0.0f, 1.0f ); //初始化着色器 shaderManager.InitializeStockShaders(); //初始化各个图元批次 SetupSmallStarBatch(); SetupMeiumStarBatch(); SetupLargeStarBatch(); SetupMountainRangeBatch(); SetupMoonBatch(); } //窗口大小改变时接受新的宽度和高度 void ChangeSize(int w, int h) { glViewport(0, 0, w, h); //正投影 viewFrustum.SetOrthographic(0.0f, SCREEN_X, 0.0f, SCREEN_Y, -1.0f, 1.0f); } //程序入口 int main(int argc, char* argv[]) { //设置当前工作目录,针对MAC OS X gltSetWorkingDirectory(argv[0]); //GLUT初始化 glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); //创建窗口大小、标题 glutInitWindowSize(800, 600); glutCreateWindow("Smoothing Out The Jaggies"); //创建菜单并绑定回调函数,添加选项,确定右键触发 glutCreateMenu(ProcessMenu); glutAddMenuEntry("Antialiased Rendering",1); glutAddMenuEntry("Normal Rendering",2); glutAttachMenu(GLUT_RIGHT_BUTTON); //注册回调函数 glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); //判断驱动是否正常 GLenum err = glewInit(); if (GLEW_OK != err) { fprintf(stderr, "GLEW Error: %sn", glewGetErrorString(err)); return 1; } //初始化 SetupRC(); //运行循环 glutMainLoop(); return 0; }
上面的 Demo 源码全部都放在我的 github/OpenGLDemo 上,大家可以去下载和调试。
有什么问题可以在下方评论区提出,写得不好可以提出你的意见,我会合理采纳的,O(∩_∩)O哈哈~,求关注求赞



