汉唐中医倪师感冒八大经方.doc_第1页
汉唐中医倪师感冒八大经方.doc_第2页
汉唐中医倪师感冒八大经方.doc_第3页
汉唐中医倪师感冒八大经方.doc_第4页
汉唐中医倪师感冒八大经方.doc_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

NeHe OpenGL第四十二课:多重视口 2010-08-23 23:29:14标签:NeHe OpenGL 多重视口 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。/1130898/381907 NeHe OpenGL第四十二课:多重视口多重视口画中画效果,很酷吧。使用视口它变得很简单,但渲染四次可会大大降低你的显示速度哦:)欢迎来到充满趣味的另一课。这次我将向你展示怎样在单个窗口内显示多个视口。这些视口在窗口模式下能正确的调整大小。其中有两个窗口起用了光照。窗口之一用的是正交投影而其他三个则是透视投影。为了保持教程的趣味性,在本例子中我们同样需要学习迷宫代码,怎么渲染到一张纹理以及怎么得到当前窗口的分辨率。一旦你明白了本教程,制作分屏游戏以及多视图的3D程序就很简单了。接下来,让我们投入到代码中来吧!你可以利用最近的NeHeGL或者IPicture代码作为主要基本代码。我们需要看的第一个文件就是NeHeGL.cpp,其中有三节代码已经被修改了。我将只列出那些被修改了的代码。第一个且最重要的被修改了的代码就是ReshapeGL()函数。这是我们设置屏幕(主视口)分辨率的地方。现在所有的主视口设置都在画循环里完成了。因此这儿所有我们能做的就是设置我们的主窗口。 void ReshapeGL (int width, int height) / 当窗口移动或者大小改变时重新调整窗口glViewport (0, 0, (GLsizei)(width), (GLsizei)(height); / 重置当前视口下一步我们添加一些代码用于监视擦除窗口背景的Windows消息(WM_ERASEBKGND).如果它被调用,我们截取它并返回0,这样就阻止了窗口背景被擦除,并让我们自己来调整主窗口大小,这样就没有了我们以前常见的那种恼人的闪烁。如果你还不明白我的意思,删掉 case WM_ERASEBKGND: 和 return 0; 你自己比较就能知道有何不同。 LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)DWORD tickCount; / 保存当前的时间_int64 timer; / 记录时间/ 返回窗口结构GL_Window* window = (GL_Window*)(GetWindowLong (hWnd, GWL_USERDATA);switch (uMsg) / 处理消息case WM_ERASEBKGND: / 检测Windows是否去擦除背景return 0; / 跳过直接返回在WinMain函数中,我们需要修改窗口标题并设置分辨率至1024x768.如果由于某种原因你的显示器不能支持到1024x768,你可以设置低一点的分辨率,但是牺牲了一些细节。 window.init.width = 1024; / 宽window.init.height = 768; / 高现在该是对lesson42.cpp文件动手术的时候了(主要代码).我们以包含标准头文件和库文件作为开始吧. #include #include #include #include NeHeGL.h #pragma comment( lib, opengl32.lib ) #pragma comment( lib, glu32.lib ) GL_Window* g_window; Keys* g_keys; 然后我们声明一些我们打算在整个程序中都要用到的全局变量。mx和my纪录了当前所在迷宫中的房间。每个房间都被墙隔开(因此房间都是2个单元大小的部分)。with和height是用来建立纹理需要的。它也是迷宫的宽和高。让迷宫和贴图的大小一致的原因是使迷宫中的象素和纹理中的象素一一对应。我倾向于把宽和高都设成256,尽管这要花更长的时间来建立迷宫。如果你的显卡能支持处理大型贴图。可以试着以2次幂增加这个值(256, 512, 1023)。确保这个值不至于太大。如果这个主窗口的宽度有1024个象素,并且每个视口的大小都是主窗口的一半,相应的你应该设置你的贴图宽度也是窗口宽度的一半。如果你使贴图宽度为1024象素,但你的视口大小只有512,空间不足于容纳贴图中所有得象素,这样每两个象素就会重叠在一起。贴图的高度也作同样处理:高度是窗口高度的1/2. 当然你还必须四舍五入到2的幂。 int mx,my; / 循环变量const width = 128; / 迷宫大小const height = 128; dong用来跟踪迷宫是否被建完,后面有这个更详细的解释。sp用来确认空格键是否处于按下状态。通过按空格健,迷宫就会被重置,然后程序将重新开始画一个新的迷宫。如果我们不去检测空格键是否处于按下状态,迷宫会在空格键按下的瞬间被重置很多次。这个值确保迷宫只被重置一次。 BOOL done; / 迷宫是否被建完BOOL sp; r4保存了4个随机的红色分量值,g4保存了4个随机的绿色分量值,b4保存了4个随机的兰色分量值。这些值赋给各个视口不同的颜色。第一个视口颜色为r0,g0,b0。请注意每一个颜色都是一个字节的值,而不是常用的浮点值。我这里用字节是因为产生0-255的随机值比产生0.0f-1.0f的浮点值更容易。tex_data指向我们的贴图数据。 BYTE r4, g4, b4; / 随机的颜色BYTE *tex_data; / 保存纹理数据xrot,yrot和zrot是旋转3d物体用到的变量。最后,我们声明一个二次曲面物体,这样我们可以用gluCylinder和gluSphere来画圆柱和球体,这比手工绘制这些物体容易多了。 GLfloat xrot, yrot, zrot; / 旋转物体GLUquadricObj *quadric; / 二次几何体对象下面的小段代码设置纹理中位置dmx,dmy的颜色值为纯白色。tex_data是指向我们的纹理数据的指针。每一个象素都由3字节组成(1字节红色分量,1字节绿色分量,一字节兰色分量). 红色分量的偏移为0,我们要修改的象素的在纹理数据中的偏移为dmx(象素的x坐标)加上dmy(象素y坐标)与贴图宽度的乘积,最后的结果乘3(3字节每象素)。下面第一行代码设置red(0)颜色分量为255, 第二行设置green(1)颜色分量为255,最后一行设置blue(2)颜色分量为255,最后的结果为在dmx,dmy处的象素颜色为白色。 void UpdateTex(int dmx, int dmy) / 更新纹理tex_data0+(dmx+(width*dmy)*3)=255; / 设置颜色为白色tex_data1+(dmx+(width*dmy)*3)=255; tex_data2+(dmx+(width*dmy)*3)=255; 重置有相当多的工作量。它清空纹理,给每一个视口设置随机颜色,删除迷宫中的墙并为迷宫的生成设置新的随机起点。第一行代码清空tex_data指向的贴图数据。我们需要清空width(贴图宽)*height(贴图高)*3(红,绿,兰)。 (代码已经够清楚了,呜呼,干吗要翻译这段?) 清空内存空间就是设置所有的字节为0。如果3个颜色分量都清零,那么整个贴图就完全变黑了! void Reset (void) ZeroMemory(tex_data, width * height *3); / 清空纹理数据现在我们来给每一个视口设置随机的颜色。对于不了解这些的人来说,这里的随机并不是真正那种随机! 如果你写了一个简单的程序来打印出10个随机数字,每次你运行程序,你都会得到同样的10个数字。为了使事情(看起来)更加随机,我们可以设置随机数种子。同样的,如果你设置种子为1,你总是会得到同样的结果。然而,如果我们设置srand为开机后当前时钟计数(这可能是任意的数),我们的程序每次运行都会有不同的结果。我们有四个视口,因此我们需要从0-3的循环来处理。我们给每一个颜色(red,green,blue)从128-255中间的随机值。要加128的目的是需要更亮的颜色。最小值为0,最大值为255,而128则表示大约有50%的亮度。 srand(GetTickCount(); / 初始化随机向量for (int loop=0; loopkeyDown VK_ESCAPE) / 处理键盘信息TerminateApplication (g_window); if (g_keys-keyDown VK_F1) ToggleFullscreen (g_window); if (g_keys-keyDown & !sp) sp=TRUE; Reset(); if (!g_keys-keyDown ) sp=FALSE; xrot,yrot和zrot通过和一些小浮点数相乘而随着时间的消逝而增加。这样我们可以让物体绕x轴,y轴和z轴旋转。每个变量都增加不同的值使旋转好看一点 xrot+=(float)(milliseconds)*0.02f; yrot+=(float)(milliseconds)*0.03f; zrot+=(float)(milliseconds)*0.015f; 下面的代码用来检测我们是否画完了迷宫。我们开始设置done值为true, 然后循环检查每一个房间去看是否需要增加一面墙,如果有一间房还有被访问到,我们设置done为false.如果tex_data(x + (width*y)*3的值为0, 我们就明白这个房间还没被访问到,而且没有在里面没有画一个象素。如果这儿有一个象素,那么它的值为255。我们只需要检查它的颜色红色分量值。因为我们知道这个值只能为0(空)或者255(更新过) done=TRUE; / 循环所有的纹理素,如果为0则表示没有绘制完所有的迷宫,返回for (int x=0; xwidth; x+=2) for (int y=0; yhWnd,Lesson 42: Multiple Viewports. 2003 NeHe Productions. Maze Complete!);Sleep(5000);SetWindowText(g_window-hWnd,Lesson 42: Multiple Viewports. 2003 NeHe Productions. Building Maze!);Reset();下面的代码也许让人看着糊涂,但其实并不难懂。我们检查当前房间的右边房间是否被访问过或者是否当前位置的右边是迷宫的右边界(当前房间右边的房间就不存在),同样检查左边的房间是否访问过或者是否达到左边界。其它方向也作如此检查。如果房间颜色的红色分量的值为255,就表示这个房间已经被访问过了(因为它已经被函数UpdateTex更新过)。如果mx(当前x坐标)小于2, 就表示我们已经到了迷宫最左边不能再左了。如果往四个方向都不能移动了或以已经到了边界,就给mx和my一个随机值,然后检查这个值对应点是否被访问,如果没有,我们就重新寻找一个新的随机变量,直到该变量对应的单元早已经被访问。因为需要从旧的路径中分叉出新的路径,所以我们必须保持搜素知道发觉有一老的路径可以从那里开始新的路径。为了使代码尽量简短,我没有打算去检查mx-2是否小于0。如果你想有100%的错误检测,你可以修改这段代码阻止访问不属于当前贴图的内存。 / 检测是否走过这里if (tex_data(mx+2)+(width*my)*3)=255) | mx(width-4) & (tex_data(mx-2)+(width*my)*3)=255) | mx(height-4) & (tex_data(mx+(width*(my-2)*3)=255) | my2)do mx=int(rand()%(width/2)*2; my=int(rand()%(height/2)*2; while (tex_data(mx+(width*my)*3)=0); 下面这行代码赋给dir变量0-3之间的随机值,这个值告诉我们该往右,往上,往左还是往下画迷宫。在得到随机的方向之后,我们检查dir的值是否为0(往右移),如果是并且我们不在迷宫的右边界,然后检查当前房间的右边房间,如果没被访问,我们就调用UpdateTex(mx+1,my)在两个房间之间建立一堵墙,然后mx增加2移到新的房间. dir=int(rand()%4); / 随机一个走向if (dir=0) & (mx=(width-4) / 向右走,更新数据if (tex_data(mx+2)+(width*my)*3)=0) UpdateTex(mx+1,my); mx+=2; 如果dir的值为1(往下),并且我们不在迷宫底部,我们检查当前房间的下面房间是否被访问过。如果没被访问过,我们就在两个房间(当前房间和当前房间下面的房间)建立一堵墙。然后my增加2移到新的房间. if (dir=1) & (my=2) / 向左走,更新数据if (tex_data(mx-2)+(width*my)*3)=0) UpdateTex(mx-1,my); mx-=2; 如果dir的值为3并且不在迷宫的最顶部,我们检?榈鼻胺考涞纳厦媸欠癖环梦剩绻挥校蛟诹礁龇考?(当前房间和当前房间上面个房间)之间建立一堵墙,然后my增加2移到新的房间。 if (dir=3) & (my=2) / 向上走,更新数据if (tex_data(mx+(width*(my-2)*3)=0) UpdateTex(mx,my-1); my-=2; 移到新的房间后,我们必须标志当前房间为正在访问状态。我们通过调用以当前位置mx, my为参数的UpdateTex()函数来达到这个目的。 UpdateTex(mx,my); / 更新纹理这段代码我们开始讲一些新的东西.我们必须知道当前窗口的大小以便正确的调整视口的大小。为了的到当前窗口的宽和高,我们需要获取窗口上下左右坐标值。得到这些值后我们通过窗口右边的坐标减去左边的坐标得到宽度值。底部坐标减去顶部坐标得到窗口的高度值。我们用RECT结构来得到窗口的那些值。RECT保存了一个矩形的坐标。也即矩形的左,右,顶部,底部的坐标。为获取窗口的屏幕坐标,我们用GetClientRect()函数。我们传进去的第一个参数是当前窗口的句柄。第二个参数是一个结构用于保存返回的窗口位置信息. void Draw (void) / 绘制RECT rect; / 保存长方形坐标GetClientRect(g_window-hWnd, &rect); / 获得窗口大小int window_width=rect.right-rect.left; int window_height=rect.bottom-rect.top; 我们在每一帧都需要更新纹理并且要在映射纹理之前更新。更新纹理最快的方法是用命令glTexSubImage2D(). 它能把内存中的纹理的全部或部分和屏幕中的物体建立映射。下面的代码我们表明用的?2维纹理,纹理细节级别为0,没有x方向(0)或y方向(0)的偏移,我们需要利用整张纹理的每一部分,图像为GL_RGB类型,对应的数据类型为GL_UNSIGNED_BYTE. tex_data是我们需要映射的具体数据。这是一个非非常快的不用重建纹理而更新纹理的方法。同样需要注意的是这个命令不会为你建立一个纹理。你必须在更新纹理前把纹理建立好。 / 设置更新的纹理glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, tex_data);这行代码非常重要,它将清空整个屏幕。一次性清空整个屏幕,然后在画每一个视口前清空它们的深度存非常重要。 glClear (GL_COLOR_BUFFER_BIT); 现在是主画循环。我们要画四个视口,所以建立了一个0到3的循环。首先要做的事是设置用glColor3ub(r,g,b)设置当前视口的颜色。这对某些人来说不太熟悉,它跟glColor3f(r,g,b)几乎一样但是用无符号字节代替浮点数为参数。记住早些时候我说过参省一个0-255的随机颜色值会更容易。好在已经有了该命令设置正确颜色所需要的值。glColor3f(0.5f,0.5f,0.5f)是指颜色中红,绿,蓝具有50%的亮度值。glColor3ub(127,127,127)同样也表示同样的意思。如果loop的值为0,我们将选择r0,b0,b0,如果loop指为1, 我们选用r1,g1,b1. 这样,每个场景都有自个的随机颜色。 for (int loop=0; loop4; loop+) / 循环绘制4个视口glColor3ub(rloop,gloop,bloop); 在画之前首先要做的是设置当前视口,如果loop值为0,我们画第一个视口。我们想把第一个视口放在屏幕的左半部分(0),并且在屏幕的上半部分(window_height/2).视口的宽度为当前主窗口的一半(window_width/2), 高度也为主窗口高度的一半(window_height/2). 如果主窗口为1024x768, 结果就是一个起点坐标为0,384,宽512,高384的视口。 这个视口看起来象下面这张图 设置完视口后,我们选择当前矩阵为投影矩阵,重置它并设置为2D平行投影视图。我们需要以平行投影视图来填充整个视口,因此我们给左边的值为0,右边的值为window_width/2(跟视口一样),同样给底部的值赋为window_height/2,顶部的值为0. 这样给了视口同样的高度。 这个平行投影视图的左上角的坐标为0,0,右下角坐标为window_width/2,window_height/2.if (loop=0) / 绘制左上角的视口/ 设置视口区域glViewport (0, window_height/2, window_width/2, window_height/2);glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluOrtho2D(0, window_width/2, window_height/2, 0);如果loop的值为1, 我们是在画第二个视口了。它在屏幕的右上部分。宽度和高度都跟前一个视图一样。唯一不同的是glViewport()函数的第一个参数为window_width/2.这告诉程序视口起点是从窗口左起一半的地方。第二个视口看起来象下面这样:同样的,我们设置当前矩阵为投影矩阵并重置它。但这次我们设置透视投影参数为FOV为45度,并且近截面值为0.1f,远截面值为500.0f if (loop=1) / 绘制右上角视口glViewport (window_width/2, window_height/2, window_width/2, window_height/2);glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 ); I如果loop值为2,我们画第三个视口。它将在主窗口的右下部分。宽度和高度与第二个视口一样。跟第二个视口不同的是glViewport()函数的第二个参数为0.这告诉程序我们想让视口位于主窗口的右下部分。 第三个视口看起来如下: 透视视图的设置同第二个视图。 if (loop=2) / 绘制右下角视口glViewport (window_width/2, 0, window_width/2, window_height/2);glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 ); 如果loop等于3,我们就画最后一个视口(第四个视口)。它将位于窗口的左下部分。宽度和高度跟前几次设置一样。唯一跟第三个视口不同的是glViewport()的第一个参数为0.这告诉程序视口将在主窗口的左下部分。第四个视口看起来如下:透视投影视图设置同第二个视口。 if (loop=3) / 绘制右下角视口glViewport (0, 0, window_width/2, window_height/2);glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 ); 下面的代码选择模型视图矩阵为当前矩阵真,并重置它。然后清空深度缓存。我们在每个视口画之前清空深度缓存。注意到我们没有清除屏幕颜色,只是深度缓存!如果你没有清除深度缓存,你将看到物体的部分消失了,等等,很明显不美观! glMatrixMode (GL_MODELVIEW); glLoadIdentity (); glClear (GL_DEPTH_BUFFER_BIT);我们要画的第一副图为一个平坦的2维纹理方块。这个方块是在平行投影模式下画的,并且将会覆盖整个视口。因为我们用了平行投影投影模式,这儿没有第三维了,因此没必要在z轴进行变换。记住我们第一个视口的左上角坐标维0,0,右下部分坐标为window_width/2,window_height/2.这意味我们的四边形的右上坐标为window_width/2,0,左上坐标为0,0,左下坐标为0,window_height/2.右下坐标为window_width/2,window_height/2. 请注意在平行投影投影模式下,我们能在象素级别上处理而不是单元级别(决定于我们的视口设置) if (loop=0) / 绘制左上角的视图glBegin(GL_QUADS); glTexCoord2f(1.0f, 0.0f); glVertex2i(window_width/2, 0 );glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0 );glTexCoord2f(0.0f, 1.0f); glVertex2i(0, window_height/2);glTexCoord2f(1.0f, 1.0f); glVertex2i(window_width/2, window_height/2);glEnd(); 第二个要画的图像是一个带光照的平滑球体。第二个视图是带透视的,因此我们首先必须做的是往屏幕里平移14个单位,然后在x,y,z轴旋转物体。我们激活光照,画球体,然后关闭光照。这个球体半径为4个单元长度,围绕z轴的细分度为32,沿z轴的细分度也为32. 如果你还在犯迷糊,可以试着改变stacks或者slices的值为更小。通过减小stacks/slices的值,你就减少了球体的平滑度。纹理坐标是自动产生的! if (loop=1) / 绘制右上角的视图glTranslatef(0.0f,0.0f,-14.0f); glRotatef(xrot,1.0f,0.0f,0.0f); glRotatef(yrot,0.0f,1.0f,0.0f); glRotatef(zrot,0.0f,0.0f,1.0f); glEnable(GL_LIGHTING); gluSphere(quadric,4.0f,32,32); glDisabl

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论