第11课 OpenGL 飘动的旗帜
飘动的旗帜:
这一课从第六课的代码开始,创建一个飘动的旗帜。我相信在这课结束的时候,你可以掌握纹理映射和混合操作。
大家好!对那些想知道我在这里作了些什么的朋友,您可以先按文章的末尾所列出的链接,下载我那毫无意义的演示(Demo)看看先!我是bosco,我将尽我所能教您来实现一个以正弦波方式运动的图象。这一课基于NeHe的教程第六课,当然您至少也应该学会了一至六课的知识。您需要下载源码压缩包,并将压缩包内带的data目录连其下的位图一起释放至您的代码目录下。或者使用您自己的位图,当然它的尺寸必须适合OpenGL纹理的要求。
在我们开始之前,先打开Visual C++(译者:我可是用的C++ Builder…)并在其他的#inlude之后,添加如下的代码。这将引入我们在程序中将要用到的复杂(译者:复杂吗?)数学函数sine和cosine。
- #include <math.h> // 引入数学函数库中的Sin
我们将使用points数组来存放网格各顶点独立的x,y,z坐标。这里网格由45×45点形成,换句话说也就是由44格×44格的小方格子依次组成了。wiggle_count用来指定纹理波浪的运动速度。每3帧一次看起来很不错,变量hold将存放一个用来对旗形波浪进行光滑的浮点数。这几行添加在程序头部,位于最后一行#include之后、GLuint texture[1]之前的位置。
- float points[ 45 ][ 45 ][3]; // Points网格顶点数组
- int wiggle_count = 0; // 指定旗形波浪的运动速度
- GLfloat hold; // 临时变量
然后下移至LoadGLTextures()子过程。本课中使用的纹理文件名是Tim.bmp。找到LoadBMP("Data/NeHe.bmp")这一句,并用LoadBMP ("Data/Tim.bmp")替换它。
- if (TextureImage[0]=LoadBMP("Data/Tim.bmp")) // 载入位图
接着在InitGL()函数的尾部return TRUE之前,添加如下的代码。
- glPolygonMode( GL_BACK, GL_FILL ); // 后表面完全填充
- glPolygonMode( GL_FRONT, GL_LINE ); // 前表面使用线条绘制
上面的代码指定使用完全填充模式来填充多边形区域的背面(译者:或者叫做后表面吧)。相反,多边形的正面(译者:前表面)则使用轮廓线填充了。这些方式完全取决于您的个人喜好。并且与多边形的方位或者顶点的方向有关。详情请参考红宝书(Red Book)。这里我顺便推销一本推动我学习OpenGL的好书 — Addison-Wesley出版的《Programmer's Guide to OpenGL》。个人以为这是学习OpenGL的无价之宝。
接着上面的代码并在return TRUE这一句之前,添加如下的几行。
- // 沿X平面循环
- for(int x=0; x<45; x++)
- {
- // 沿Y平面循环
- for(int y=0; y<45; y++)
- {
- // 向表面添加波浪效果
- points[x][y][0]=float((x/5.0f)-4.5f);
- points[x][y][1]=float((y/5.0f)-4.5f);
- points[x][y][2]=float(sin((((x/5.0f)*40.0f)/360.0f)*3.141592654*2.0f));
- }
- }
这里感谢Graham Gibbons关于使用整数循环变量消除波浪间的脉冲锯齿的建议。
上面的两个循环初始化网格上的点。使用整数循环可以消除由于浮点运算取整造成的脉冲锯齿的出现。我们将x和y变量都除以5,再减去4.5。这样使得我们的波浪可以“居中”(译者:这样计算所得结果将落在区间[-4.5,4.5]之间)。
点[x][y][2]最后的值就是一个sine函数计算的结果。Sin()函数需要一个弧度参变量。将float_x乘以40.0f,得到角度值。然后除以360.0f再乘以PI,乘以2,就转换为弧度了。
接着我将彻底重写DrawGLScene函数。
- int DrawGLScene(GLvoid) // 绘制我们的GL场景
- {
- int x, y; // 循环变量
- float float_x, float_y, float_xb, float_yb; // 用来将旗形的波浪分割成很小的四边形
我们使用不同的变量来控制循环。下面的代码中大多数变量除了用来控制循环和存储临时变量之外并没有什么别的用处。
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓冲
- glLoadIdentity(); // 重置当前的模型观察矩阵
- glTranslatef(0.0f,0.0f,-12.0f); // 移入屏幕12个单位
- glRotatef(xrot,1.0f,0.0f,0.0f); // 绕 X 轴旋转
- glRotatef(yrot,0.0f,1.0f,0.0f); // 绕 Y 轴旋转
- glRotatef(zrot,0.0f,0.0f,1.0f); // 绕 Z 轴旋转
- glBindTexture(GL_TEXTURE_2D, texture[0]); // 选择纹理
正如您所见,上面的代码和第六课的很类似,唯一的区别就是我将场景挪的离镜头更远了一些。
- glBegin(GL_QUADS); // 四边形绘制开始
- for( x = 0; x < 44; x++ ) // 沿 X 平面 0-44 循环(45点)
- {
- for( y = 0; y < 44; y++ ) // 沿 Y 平面 0-44 循环(45点)
- {
接着开始使用循环进行多边形绘制。这里使用整型可以避免我以前所用的int()强制类型转换。
- float_x = float(x)/44.0f; // 生成X浮点值
- float_y = float(y)/44.0f; // 生成Y浮点值
- float_xb = float(x+1)/44.0f; // X浮点值+0.0227f
- float_yb = float(y+1)/44.0f; // Y浮点值+0.0227f
上面我们使用4个变量来存放纹理坐标。每个多边形(网格之间的四边形)分别映射了纹理的1/44×1/44部分。循环首先确定左下顶点的值,然后我们据此得到其他三点的值。
- glTexCoord2f( float_x, float_y); // 第一个纹理坐标 (左下角)
- glVertex3f( points[x][y][0], points[x][y][1], points[x][y][2] );
- glTexCoord2f( float_x, float_yb ); // 第二个纹理坐标 (左上角)
- glVertex3f( points[x][y+1][0], points[x][y+1][1], points[x][y+1][2] );
- glTexCoord2f( float_xb, float_yb ); // 第三个纹理坐标 (右上角)
- glVertex3f( points[x+1][y+1][0], points[x+1][y+1][1], points[x+1][y+1][2] );
- glTexCoord2f( float_xb, float_y ); // 第四个纹理坐标 (右下角)
- glVertex3f( points[x+1][y][0], points[x+1][y][1], points[x+1][y][2] );
- }
- }
- glEnd(); // 四边形绘制结束
上面几行使用glTexCoord2f()和glVertex3f()载入数据。提醒一点:四边形是逆时针绘制的。这就是说,您开始所见到的表面是背面。后表面完全填充了,前表面由线条组成。
如果您按顺时针顺序绘制的话,您初始时见到的可能是前表面。也就是说您将看到网格型的纹理效果而不是完全填充的。
- if( wiggle_count == 2 ) // 用来降低波浪速度(每隔2帧一次)
- {
每绘制两次场景,循环一次sine值,以产生运动效果。
- for( y = 0; y < 45; y++ ) // 沿Y平面循环
- {
- hold=points[0][y][2]; // 存储当前左侧波浪值
- for( x = 0; x < 44; x++) // 沿X平面循环
- {
- // 当前波浪值等于其右侧的波浪值
- points[x][y][2] = points[x+1][y][2];
- }
- points[44][y][2]=hold; // 刚才的值成为最左侧的波浪值
- }
- wiggle_count = 0; // 计数器清零
- }
- wiggle_count++; // 计数器加一
上面所作的事情是先存储每一行的第一个值,然后将波浪左移一下,是图象产生波浪。存储的数值挪到末端以产生一个永无尽头的波浪纹理效果。然后重置计数器wiggle_count以保持动画的进行。
上面的代码由NeHe(2000年2月)修改过,以消除波浪间出现的细小锯齿。
- xrot+=0.3f; // X 轴旋转
- yrot+=0.2f; // Y 轴旋转
- zrot+=0.4f; // Z 轴旋转
- return TRUE; // 返回
- }
标准的NeHe旋转增量。现在编译并运行程序,您将看到一个漂亮的位图波浪。除了嘘声一片之外,我不敢确信大家的反应。但我希望大家能从这一课中学到点什么。如果您有任何问题或者需要澄清的地方,请随便联络我。感谢大家。
第11课 OpenGL 飘动的旗帜的更多相关文章
- NeHe OpenGL教程 第十一课:飘动的旗帜
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- OPENGL NEHE Lesson11 11课的计算公式推导
计算多边形公式推导: 条件x 离散的值从0到45; y离散的值从0到45; z是符合正弦波. 问题: 1 要求x’=f(x)映射到 x’ ∈[-4.5, 4.5], x ∈{0, 1, 2, …, 4 ...
- Js笔记-第11课
// 第11课 作用域精解 运行期上下文,当函数执行时,会创建一个成为执行期上下文的内部对象.一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行期上下文都是独 ...
- 第11课 - enum, sizeof, typedef 分析
第11课 - enum, sizeof, typedef 分析 1. enum介绍 (1)enum是C语言中的一种自定义类型,和struct.union地位相同,格式如下: // enum每个值的最后 ...
- 第08课 OpenGL 混合
混合: 在这一课里,我们在纹理的基础上加上了混合,它看起具有透明的效果,当然解释它不是那么容易,当希望你喜欢它. 简单的透明OpenGL中的绝大多数特效都与某些类型的(色彩)混合有关.混色的定义为,将 ...
- 妙味课堂:JavaScript初级--第11课:字符串、查找高亮显示
1.数字字母 Unicode 编码 <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content- ...
- 第12课 OpenGL 显示列表
显示列表: 想知道如何加速你的OpenGL程序么?这一课将告诉你如何使用OpenGL的显示列表,它通过预编译OpenGL命令来加速你的程序,并可以为你省去很多重复的代码. 这次我将教你如何使用显示列表 ...
- 第10课 OpenGL 3D世界
加载3D世界,并在其中漫游: 在这一课中,你将学会如何加载3D世界,并在3D世界中漫游.这一课使用第一课的代码,当然在课程说明中我只介绍改变了代码. 这一课是由Lionel Brits (βtelge ...
- 第09课 OpenGL 移动图像
3D空间中移动图像: 你想知道如何在3D空间中移动物体,你想知道如何在屏幕上绘制一个图像,而让图像的背景色变为透明,你希望有一个简单的动画.这一课将教会你所有的一切.前面的课程涵盖了基础的OpenGL ...
随机推荐
- CORS跨域请求规则以及在Spring中的实现
CORS: 通常情况下浏览器禁止AJAX从外部获取资源,因此就衍生了CORS这一标准体系,来实现跨域请求. CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origi ...
- Ubuntu18.04安装jenkins
官网参考指引:https://pkg.jenkins.io/debian-stable/ wget -q -O - https://pkg.jenkins.io/debian-stable/jenki ...
- TP框架 商城前台用户注册方法
//注册 public function register(){ if(IS_POST){ //接收数据 $data = I('post.'); $model = D('User'); $data[' ...
- jmeter性能实战
概述 性能测试: 通过并发工具请求服务器,提前发现性能问题,优化并解决 为什么做性能测试? 常规需求 用户反馈性能问题 项目对性能不放心 性能测试的最终目标:? 性能指标分析 多快好省 项目性能场景提 ...
- redis小结 1-2
1.Redis 发布订阅 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. Redis 客户端可以订阅任意数量的频道. 下图展示了频道 c ...
- kubeadm 如何将节点加入集群
kubeadm join 使用 token 过期之后(24小时过期),如何加入集群 一.重启生成新token # 创建新token kubeadm token create # 查看是否存在有效的 t ...
- ARM平台如何玩转GDB远程调试?
前 言 关于GDB工具 GDB工具是GNU项目调试器,基于命令行使用.和其他的调试器一样,可使用GDB工具单步运行程序.单步执行.跳入/跳出函数.设置断点.查看变量等等,它是UNIX/LINUX操作 ...
- 模拟一个简单的tomcat
目录 简单处理 每个请求一个线程 模拟tomcat 参考 简单处理 // 客户端和服务器的通信,说到底就是两个数据的传输, // 客户端发送inputStream给服务器,服务器回复 // outpu ...
- Interrupted Exception异常可能没你想的那么简单!
摘要: 当我们在调用Java对象的wait()方法或者线程的sleep()方法时,需要捕获并处理InterruptedException异常.如果我们对InterruptedException异常处理 ...
- CefSharp请求资源拦截及自定义处理
CefSharp请求资源拦截及自定义处理 前言 在CefSharp中,我们不仅可以使用Chromium浏览器内核,还可以通过Cef暴露出来的各种Handler来实现我们自己的资源请求处理. 什么是资源 ...