OpenGL第九课.docx
- 文档编号:7718483
- 上传时间:2023-01-26
- 格式:DOCX
- 页数:38
- 大小:43.92KB
OpenGL第九课.docx
《OpenGL第九课.docx》由会员分享,可在线阅读,更多相关《OpenGL第九课.docx(38页珍藏版)》请在冰豆网上搜索。
OpenGL第九课
第九课
3D空间中移动图像:
你想知道如何在3D空间中移动物体,你想知道如何在屏幕上绘制一个图像,而让图像的背景色变为透明,你希望有一个简单的动画。
这一课将教会你所有的一切。
前面的课程涵盖了基础的OpenGL,每一课都是在前一课的基础上创建的。
这一课是前面几课知识的综合,当你学习这课时,请确保你已经掌握了前面几课的知识。
欢迎进入第九课。
到现在为止,您应该很好的理解OpenGL了。
『CKER:
如果没有的话,一定是我翻译的罪过......』。
您已经学会了设置一个OpenGL窗口的每个细节。
学会在旋转的物体上贴图并打上光线以及混色(透明)处理。
这一课应该算是第一课中级教程。
您将学到如下的知识:
在3D场景中移动位图,并去除位图上的黑色象素(使用混色)。
接着为黑白纹理上色,最后您将学会创建丰富的色彩,并把上过不同色彩的纹理相互混合,得到简单的动画效果。
我们在第一课的代码基础上进行修改。
先在程序源码的开始处增加几个变量。
出于清晰起见,我重写了整段代码。
#include
#include
下列这几行新加的。
twinkle和tp是布尔变量,表示它们只能设为TRUE或FALSE。
twinkle用来跟踪闪烁效果是否启用。
tp用来检查'T'键有没有被按下或松开.(按下时tp=TRUE,松开时tp=FALSE).
BOOLtwinkle;//闪烁的星星
BOOLtp;//'T'按下了么?
num跟踪屏幕上所绘制的星星数。
这个数字被定义为一个常量。
这意味着无法在以后的代码中对其进行修改。
这么做的原因是因为您无法重新定义一个数组。
因此,如果我们定义一个50颗星星的数组,然后又将num增加到51的话,就会出错『CKER:
数组越界』。
不过您还是可以(也只可以)在这一行上随意修改这个数字。
但是以后请您别再改动num的值了,除非您想看见灾难发生。
constnum=50;//绘制的星星数
现在我们来创建一个结构。
结构这词听起来有点可怕,但实际上并非如此。
一个结构使用一组简单类型的数据(以及变量等)来表达较大的具有相似性的数据组合。
我们知道我们在保持对星星的跟踪。
您可以看到下面的第七行就是stars;并且每个星星有三个整型的色彩值。
第三行intr,g,b设置了三个整数.一个红色(r),一个绿色(g),以及一个蓝色(b).此外,每个星星离屏幕中心的距离不同,而且可以是以屏幕中心为原点的任意360度中的一个角度。
如果你看下面第四行的话,会发现我们使用了一个叫做dist的浮点数来保持对距离的跟踪.第五行则用一个叫做angle的浮点数保持对星星角度值的跟踪。
因此我们使用了一组数据来描述屏幕上星星的色彩,距离,和角度。
不幸的是我们不止对一个星星进行跟踪。
但是无需创建50个红色值、50个绿色值、50个蓝色值、50个距离值以及50个角度值,而只需创建一个数组star。
star数组的每个元素都是stars类型的,里面存放了描述星星的所有数据。
star数组在下面的第八行创建。
第八行的样子是这样的:
starsstar[num]。
数组类型是stars结构.所数组能存放所有stars结构的信息。
数组名字是star.数组大小是[num]。
数组中存放着stars结构的元素.跟踪结构元素会比跟踪各自分开的变量容易的多.不过这样也很笨,因为我们竟然不能改变常量num来增减星星数量。
typedefstruct//为星星创建一个结构
{
intr,g,b;//星星的颜色
GLfloatdist;//星星距离中心的距离
GLfloatangle;//当前星星所处的角度
}
stars;//结构命名为stars
starsstar[num];//使用'stars'结构生成一个包含'num'个元素的'star'数组
接下来我们设置几个跟踪变量:
星星离观察者的距离变量(zoom),我们所见到的星星所处的角度(tilt),以及使闪烁的星星绕Z轴自转的变量spin。
loop变量用来绘制50颗星星。
texture[1]用来存放一个黑白纹理。
如果您需要更多的纹理的话,您应该增加texture数组的大小至您决定采用的纹理个数。
GLfloatzoom=-15.0f;//星星离观察者的距离
GLfloattilt=90.0f;//星星的倾角
GLfloatspin;//闪烁星星的自转
GLuintloop;//全局Loop变量
GLuinttexture[1];//存放一个纹理
紧接着上面的代码就是我们用来载入纹理的代码。
我不打算再详细的解释这段代码。
这跟我们在第六、七、八课中所用的代码是一模一样的。
这一次载入的位图叫做star.bmp。
这里我们使用glGenTextures(1,&texture[0]),来生成一个纹理。
纹理采用线性滤波方式。
AUX_RGBImageRec*LoadBMP(char*Filename)//载入位图文件
{
FILE*File=NULL;//文件句柄
if(!
Filename)//确认已给出文件名
{
returnNULL;//若无返回NULL
}
File=fopen(Filename,"r");//检查文件是否存在
if(File)//文件存在么?
{
fclose(File);//关闭文件句柄
returnauxDIBImageLoad(Filename);//载入位图并返回指针
}
returnNULL;//如果载入失败返回NULL
}
下面的代码(调用上面的代码)载入位图,并转换成纹理。
变量用来跟踪纹理是否已载入并创建好了。
intLoadGLTextures()//载入位图并转换成纹理
{
intStatus=FALSE;//状态指示器
AUX_RGBImageRec*TextureImage[1];//为纹理分配存储空间
memset(TextureImage,0,sizeof(void*)*1);//将指针设为NULL
//载入位图,查错,如果未找到位图文件则退出
if(TextureImage[0]=LoadBMP("Data/Star.bmp"))
{
Status=TRUE;//将Status设为TRUE
glGenTextures(1,&texture[0]);//创建一个纹理
//创建一个线性滤波纹理
glBindTexture(GL_TEXTURE_2D,texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,0,3,TextureImage[0]->sizeX,TextureImage[0]->sizeY,0,GL_RGB,GL_UNSIGNED_BYTE,TextureImage[0]->data);
}
if(TextureImage[0])//如果纹理存在
{
if(TextureImage[0]->data)//如果纹理图像存在
{
free(TextureImage[0]->data);//释放纹理图像所占的内存
}
free(TextureImage[0]);//释放图像结构
}
returnStatus;//返回Status的值
}
现在设置OpenGL的渲染方式。
这里不打算使用深度测试,如果您使用第一课的代码的话,请确认是否已经去掉了glDepthFunc(GL_LEQUAL);和glEnable(GL_DEPTH_TEST);两行。
否则,您所见到的效果将会一团糟。
这里我们使用了纹理映射,因此请您确认您已经加上了这些第一课中所没有的代码。
您会注意到我们通过混色来启用了纹理映射。
intInitGL(GLvoid)//此处开始对OpenGL进行所有设置
{
if(!
LoadGLTextures())//调用纹理载入子例程
{
returnFALSE;//如果未能载入,返回FALSE
}
glEnable(GL_TEXTURE_2D);//启用纹理映射
glShadeModel(GL_SMOOTH);//启用阴影平滑
glClearColor(0.0f,0.0f,0.0f,0.5f);//黑色背景
glClearDepth(1.0f);//设置深度缓存
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//真正精细的透视修正
glBlendFunc(GL_SRC_ALPHA,GL_ONE);//设置混色函数取得半透明效果
glEnable(GL_BLEND);//启用混色
以下是新增的代码。
设置了每颗星星的起始角度、距离、和颜色。
您会注意到修改结构的属性有多容易。
全部50颗星星都会被循环设置。
要改变star[1]的角度我们所要做的只是star[1].angle={某个数值};就这么简单!
for(loop=0;loop { star[loop].angle=0.0f;//所有星星都从零角度开始 第loop颗星星离中心的距离是将loop的值除以星星的总颗数,然后乘上5.0f。 基本上这样使得后一颗星星比前一颗星星离中心更远一点。 这样当loop为50时(最后一颗星星),loop除以num正好是1.0f。 之所以要乘以5.0f是因为1.0f*5.0f就是5.0f。 『CKER: 废话,废话! 这老外怎么跟孔乙己似的! : )』5.0f已经很接近屏幕边缘。 我不想星星飞出屏幕,5.0f是最好的选择了。 当然如果如果您将场景设置的更深入屏幕里面的话,也许可以使用大于5.0f的数值,但星星看起来就更小一些(都是透视的缘故)。 您还会注意到每颗星星的颜色都是从0~255之间的一个随机数。 也许您会奇怪为何这里的颜色得取值范围不是OpenGL通常的0.0f~1.0f之间。 这里我们使用的颜色设置函数是glColor4ub,而不是以前的glColor4f。 ub意味着参数是UnsignedByte型的。 一个byte的取值范围是0~255。 这里使用byte值取随机整数似乎要比取一个浮点的随机数更容易一些。 star[loop].dist=(float(loop)/num)*5.0f;//计算星星离中心的距离 star[loop].r=rand()%256;//为star[loop]设置随机红色分量 star[loop].g=rand()%256;//为star[loop]设置随机红色分量 star[loop].b=rand()%256;//为star[loop]设置随机红色分量 } returnTRUE;//初始化一切OK } Resize的代码也是一样的,现在我们转入绘图代码。 如果您使用第一课的代码,删除旧的DrawGLScene代码,只需将下面的代码复制过去就行了。 实际上,第一课的代码只有两行,所以没太多东西要删掉的。 intDrawGLScene(GLvoid)//此过程中包括所有的绘制代码 { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕及深度缓存 glBindTexture(GL_TEXTURE_2D,texture[0]);//选择纹理 for(loop=0;loop { glLoadIdentity();//绘制每颗星星之前,重置模型观察矩阵 glTranslatef(0.0f,0.0f,zoom);//深入屏幕里面 glRotatef(tilt,1.0f,0.0f,0.0f);//倾斜视角 现在我们来移动星星。 星星开始时位于屏幕的中心。 我们要做的第一件事是把场景沿Y轴旋转。 如果我们旋转90度的话,X轴不再是自左至右的了,他将由里向外穿出屏幕。 为了让大家更清楚些,举个例子。 假想您站在房子中间。 再设想您左侧的墙上写着-x,前面的墙上写着-z,右面墙上就是+x咯,您身后的墙上则是+z。 加入整个房子向右转90度,但您没有动,那么前面的墙上将是-x而不再是-z了。 所有其他的墙也都跟着移动。 -z出现在右侧,+z出现在左侧,+x出现在您背后。 神经错乱了吧? 通过旋转场景,我们改变了x和z平面的方向。 第二行代码沿x轴移动一个正值。 通常x轴上的正值代表移向了屏幕的右侧(也就是通常的x轴的正向),但这里由于我们绕y轴旋转了坐标系,x轴的正向可以是任意方向。 如果我们转180度的话,屏幕的左右侧就镜像反向了。 因此,当我们沿x轴正向移动时,可能向左,向右,向前或向后。 glRotatef(star[loop].angle,0.0f,1.0f,0.0f);//旋转至当前所画星星的角度 glTranslatef(star[loop].dist,0.0f,0.0f);//沿X轴正向移动 接着的代码带点小技巧。 星星实际上是一个平面的纹理。 现在您在屏幕中心画了个平面的四边形然后贴上纹理,这看起来很不错。 一切都如您所想的那样。 但是当您当您沿着y轴转上个90度的话,纹理在屏幕上就只剩右侧和左侧的两条边朝着您。 看起来就是一条细线。 这不是我们所想要的。 我们希望星星永远正面朝着我们,而不管屏幕如何旋转或倾斜。 我们通过在绘制星星之前,抵消对星星所作的任何旋转来实现这个愿望。 您可以采用逆序来抵消旋转。 当我们倾斜屏幕时,我们实际上以当前角度旋转了星星。 通过逆序,我们又以当前角度"反旋转"星星。 也就是以当前角度的负值来旋转星星。 就是说,如果我们将星星旋转了10度的话,又将其旋转-10度来使星星在那个轴上重新面对屏幕。 下面的第一行抵消了沿y轴的旋转。 然后,我们还需要抵消掉沿x轴的屏幕倾斜。 要做到这一点,我们只需要将屏幕再旋转-tilt倾角。 在抵消掉x和y轴的旋转后,星星又完全面对着我们了。 glRotatef(-star[loop].angle,0.0f,1.0f,0.0f);//取消当前星星的角度 glRotatef(-tilt,1.0f,0.0f,0.0f);//取消屏幕倾斜 如果twinkle为TRUE,我们在屏幕上先画一次不旋转的星星: 将星星总数(num)减去当前的星星数(loop)再减去1,来提取每颗星星的不同颜色(这么做是因为循环范围从0到num-1)。 举例来说,结果为10的时候,我们就使用10号星星的颜色。 这样相邻星星的颜色总是不同的。 这不是个好法子,但很有效。 最后一个值是alpha通道分量。 这个值越小,这颗星星就越暗。 由于启用了twinkle,每颗星星最后会被绘制两遍。 程序运行起来会慢一些,这要看您的机器性能如何了。 但两遍绘制的星星颜色相互融合,会产生很棒的效果。 同时由于第一遍的星星没有旋转,启用twinkle后的星星看起来有一种动画效果。 (如果您这里看不懂得话,就自己去看程序的运行效果吧。 ) 值得注意的是给纹理上色是件很容易的事。 尽管纹理本身是黑白的,纹理将变成我们在绘制它之前选定的任意颜色。 此外,同样值得注意的是我们在这里使用的颜色值是byte型的,而不是通常的浮点数。 甚至alpha通道分量也是如此。 if(twinkle)//启用闪烁效果 { //使用byte型数值指定一个颜色 glColor4ub(star[(num-loop)-1].r,star[(num-loop)-1].g,star[(num-loop)-1].b,255); glBegin(GL_QUADS);//开始绘制纹理映射过的四边形 glTexCoord2f(0.0f,0.0f);glVertex3f(-1.0f,-1.0f,0.0f); glTexCoord2f(1.0f,0.0f);glVertex3f(1.0f,-1.0f,0.0f); glTexCoord2f(1.0f,1.0f);glVertex3f(1.0f,1.0f,0.0f); glTexCoord2f(0.0f,1.0f);glVertex3f(-1.0f,1.0f,0.0f); glEnd();//四边形绘制结束 } 现在绘制第二遍的星星。 唯一和前面的代码不同的是这一遍的星星肯定会被绘制,并且这次的星星绕着z轴旋转。 glRotatef(spin,0.0f,0.0f,1.0f);//绕z轴旋转星星 //使用byte型数值指定一个颜色 glColor4ub(star[loop].r,star[loop].g,star[loop].b,255); glBegin(GL_QUADS);//开始绘制纹理映射过的四边形 glTexCoord2f(0.0f,0.0f);glVertex3f(-1.0f,-1.0f,0.0f); glTexCoord2f(1.0f,0.0f);glVertex3f(1.0f,-1.0f,0.0f); glTexCoord2f(1.0f,1.0f);glVertex3f(1.0f,1.0f,0.0f); glTexCoord2f(0.0f,1.0f);glVertex3f(-1.0f,1.0f,0.0f); glEnd();//四边形绘制结束 以下的代码代表星星的运动。 我们增加spin的值来旋转所有的星星(公转)。 然后,将每颗星星的自转角度增加loop/num。 这使离中心更远的星星转的更快。 最后减少每颗星星离屏幕中心的距离。 这样看起来,星星们好像被不断地吸入屏幕的中心。 spin+=0.01f;//星星的公转 star[loop].angle+=float(loop)/num;//改变星星的自转角度 star[loop].dist-=0.01f;//改变星星离中心的距离 接着几行检查星星是否已经碰到了屏幕中心。 当星星碰到屏幕中心时,我们为它赋一个新颜色,然后往外移5个单位,这颗星星将踏上它回归屏幕中心的旅程。 if(star[loop].dist<0.0f)//星星到达中心了么 { star[loop].dist+=5.0f;//往外移5个单位 star[loop].r=rand()%256;//赋一个新红色分量 star[loop].g=rand()%256;//赋一个新绿色分量 star[loop].b=rand()%256;//赋一个新蓝色分量 } } returnTRUE;//一切正常 } 现在我们添加监视键盘的代码。 下移到WinMain()。 找到SwapBuffers(hDC)一行。 我们就在这一行后面增加键盘监视代码。 代码将检查T键是否已按下。 如果T键按下过,并且又放开了,if块内的代码将被执行。 如果twinkle为FALSE,他将变为TRUE。 反之亦然。 只要T键按下,tp就变为TRUE。 这样处理可以防止如果您一直按着T键的话,块内的代码被反复执行。 SwapBuffers(hDC);//切换缓冲 if(keys['T']&&! tp)//是否T键已按下并且tp值为FALSE { tp=TRUE;//若是,将tp设为TRUE twinkle=! twinkle;//翻转twinkle的值 } 下面的代码检查是否松开了T键。 若是,使tp=FALSE。 除非tp的值为FALSE,否则按着T键时什么也不会发生。 所以这行代码很重要。 if(! keys['T'])//T键已松开了么? { tp=FALSE;//若是,tp为FALSE } 余下的代码检查上、下方向键,向上翻页键或向下翻页键是否按下。 if(keys[VK_UP])//上方向键按下了么? { tilt-=0.5f;//屏幕向上倾斜 } if(keys[VK_DOWN])//下方向键按下了么? { tilt+=0.5f;//屏幕向下倾斜 } if(keys[VK_PRIOR])//向上翻页键按下了么 { zoom-=0.2f;//缩小 } if(keys[VK_NEXT])//向下翻页键按下了么? { zoom+=0.2f;//放大 } 像以前一样,确认窗口的标题是否正确。 if(keys[VK_F1])//F1键按下了么? { keys[VK_F1]=FALSE;//若是,使对应的Key数组中的值为FALSE KillGLWindow();//销毁当前的窗口 fullscreen=! fullscreen;//切换全屏/窗口模式 //重建OpenGL窗口 if(! CreateGLWindow("NeHe's透明纹理实例",640,480,16,fullscreen)) { return0;//如果窗口未能创建,程序退出 } } } } 这一课我尽我所能来解释如何加载一个灰阶位图纹理,(使用混色)去掉它的背景色后,再给它上色,最后让它在3D场景中移动。 我已经向您展示了如何创建漂亮的颜色与动画效果。 实现原理是在原始位图上再重叠一份位图拷贝。 到现在为止,只要您很好的理解了我所教您的一切,您应该已经能够毫无问题的制作您自己的3DDemo了。 所有的基础知识都已包括在内! Lesson09 WelcometoTutorial9.BynowyoushouldhaveaverygoodunderstandingofOpenGL.You'velearnedeverythingfromsettingupanOpenGLWindow,totexturemappingaspinningobjectwhileusinglightingandblending.Thiswillbethefirstsemi-advancedtutorial.You'lllearnthefollowing: Movingbitmapsaroundthescreenin3D,removingtheblackpixelsaroundthebitmap(usingblending),a
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- OpenGL 第九课 第九
![提示](https://static.bdocx.com/images/bang_tan.gif)