本文由哈利_蜘蛛侠原创,转载请注明出处!有问题请联系[email protected]
这一次我们继续来讲述Jim Adams 老哥的RPG编程书籍第二版第二章的第10节:Particles (粒子),第11节:Depth Sorting and Z-Buffering (深度排序和Z-缓存),以及第12节:Working with Viewports (使用视口)。这两节的内容都不多,所以就放在一期里面讲了。
原文翻译:
===============================================================================
大爆炸、烟雾痕迹,甚至是追随着一个hurdling(不知道咋个翻译法)魔法飞弹的尾巴的小小火星都是一种叫做粒子(particles)的特殊的效果的杰作。粒子遵循着和广告牌技术同样的技术,用起来也同样简单。要使用粒子,你建立一些被烟雾、火苗、火星或任何你想要使用的图形所纹理映射的多边形。在适当的时候,你启用alpha混合(可选)并这样绘制粒子,使得它们面朝观察点(使用广告牌技术)。最后的结果就是一张由被混合后的物体组成的抽象画,你可以用来产生一些酷炫的效果。
关于粒子的一件很酷的事情就是它们实际上可以是任何尺寸的,因为你可以创建一个放缩矩阵来与粒子多边形的世界变换矩阵结合起来。这意味着你只需要使用一个多边形来绘制你所有的粒子,除非粒子的纹理要变化,而在这种情况下多边形的数目应该与纹理的数目相匹配。
是时候来创建一个粒子图像了。你可以从一个圆形开始,它在中心位置是实心的(不透明的),并且在靠近边缘的时候变得越来越透明(如图2.22所示)。
现在,建立4个顶点来使用两个多边形(为了优化,使用三角形带)。顶点的坐标代表一个粒子的默认尺寸,这个尺寸你将会在稍后进行放大。(感觉这句话的逻辑不对啊!PS:我的翻译可没有问题。)每一个粒子可以具有不同的性质,包括其颜色(通过使用材质)。
然后你使用这个结构(哪个结构?),连同一个包含两个多边形(用于创建一个正方形)的顶点缓存,来将多边形渲染到3-D设备中。在被绘制之前,每一个粒子都通过其自己的世界矩阵进行摆放(当然,使用广告牌技术)。你将世界变换矩阵与每个粒子的缩放矩阵变换进行结合。然后你设定一个材质(使用IDirect3DDevice9::SetMaterial 函数)来改变粒子的颜色,组后你绘制出这个粒子。
下面是一个创建一个粒子顶点缓存并将之绘制到设备中的例子:
// g_pD3DDevice = pre-initialized deviceobject // define a custom vertex structure and descriptor typedef struct { FLOATx, y, z; // Local 3-D coordinates FLOATu, v; // Texture coordinates } sVertex; #define VertexFVF (D3DFVF_XYZ |D3DFVF_TEX1) // Particle vertex buffer and texture IDirect3DVertexBuffer9 *g_pParticleVB =NULL; IDirect3DTexture9 *g_pParticleTexture = NULL; BOOL SetupParticle() { BYTE*Ptr; sVertexVerts[4] = { {-1.0f, 1.0f, 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f }, {-1.0f, -1.0f, 0.0f, 0.0f, 1.0f }, { 1.0f, -1.0f, 0.0f, 1.0f, 1.0f } }; // Create particle vertex buffer and stuff in data if(FAILED(g_pD3DDevice->CreateVertexBuffer( \ sizeof(sVertex)*4,0, VertexFVF, \ D3DPOOL_MANAGED,&g_pParticleVB, NULL))) return FALSE; if(FAILED(g_pParticleVB->Lock(0,0,(void**)&Ptr, 0))) return FALSE; memcpy(Ptr,Verts, sizeof(Verts)); g_pParticleVB->Unlock(); // Get particle texture D3DXCreateTextureFromFile(g_pD3DDevice,“particle.bmp”, \ &g_pParticleTexture); returnTRUE; }
</pre><pre name="code" class="cpp">BOOL DrawParticle(float x, float y, floatz, float scale) { D3DXMATRIX matWorld, matView, matTransposed; D3DXMATRIX matTrans, matScale; D3DMATERIAL9 d3dm; // Set render states (alpha blending and attributes) g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE); g_pD3DDevice->SetRenderState(D3DRS_SRCBLEND, \ D3DBLEND_SRCALPHA); g_pD3DDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE); // Turn on ambient lighting g_pD3DDevice->SetRenderState(D3DRS_AMBIENT,0xffffffff); // Set stream source to particle vertex buffer g_pD3DDevice->SetStreamSource(0,g_pParticleVB, 0, \ sizeof(sVertex)); // Set vertex shader to particle type g_pD3DDevice->SetFVF(VertexFVF); // Set texture g_pD3DDevice->SetTexture(0,g_pParticleTexture); // Set the particle color ZeroMemory(&d3dm,sizeof(D3DMATERIAL9)); d3dm.Diffuse.r= d3dm.Ambient.r = 1.0f; d3dm.Diffuse.g= d3dm.Ambient.g = 1.0f; d3dm.Diffuse.b= d3dm.Ambient.b = 0.0f; d3dm.Diffuse.a= d3dm.Ambient.a = 1.0f; g_pD3DDevice->SetMaterial(&d3dm); // Build scaling matrix D3DXMatrixScaling(&matScale,scale, scale, scale); // Build translation matrix D3DXMatrixTranslation(&matTrans,x, y, z); // Build the billboard matrix g_pD3DDevice->GetTransform(D3DTS_VIEW,&matView); D3DXMatrixTranspose(&matTransposed,&matView); // Combine matrices to form world translation matrix D3DXMatrixMultiply(&matWorld,&matScale, &matTransposed); D3DXMatrixMultiply(&matWorld,&matWorld, &matTrans); // Set world transformation g_pD3DDevice->SetTransform(D3DTS_WORLD,&matWorld); // Draw particle g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0, 2); // Turn off alpha blending g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE); return TRUE; }
这两个函数展示了如何建立一个用于粒子的顶点缓存和纹理以及绘制实际的粒子。一个完整的粒子示例的代码相当冗长。我只是想要给你看看如何操控单个的粒子。
如果想要一个展示在更高层面上使用粒子的完整的示例应用程序,请查阅书本的CD-ROM中的Particle项目(在\Bookode\Chap02\Particle目录下)。(不用担心,我会把源代码和更新后的代码发出来的。)
===============================================================================
现在是时候给出代码了!不过很悲哀的是,我并不是很懂这个程序的逻辑,看上去挺复杂的样子。另外我实际上并没有对此代码做出本质性的改动。下面是程序运行时的截图:
下面是代码下载的地址:
Particle代码下载地址
===============================================================================
在你将多边形网格物体渲染到场景中时,离观察者远些的物体需要被近处的物体所阻挡这件事很快就变得明显了。这叫做深度排序(depth sorting),并且有两种方法来实现。
第一个方法叫做画家的算法(painter’s algorithm)。这个方法将物体分解为各自的多边形,并将这些多边形从后往前进行排序,然后按照这个顺序对它们进行绘制(如图2.23所示)。用这种方法进行绘制保证了一个多边形总是被绘制在它后面的多边形的前面。
第二种深度排序的方法,也是图形硬件设备使用最多的方法,叫做Z-缓存方法(Z-Buffer method)。这种方法是工作在像素基础上的,而每个像素具有一个z-值(离观察者的距离)(实际上,是离观察者的沿着z轴的距离)。
在每个像素被绘制的时候,渲染器首先检查是否一个具有更小的z-值的像素已经在那里了。如果没有,那么这个像素就会被绘制;如果有的话,那么这个像素就会被跳过。你可以在图2.23中看到这个概念。
大多数加速的3-D图形卡具有一个内建的Z-缓存,所以Z-缓存方法是我们选择的深度排序的方法。在你的应用程序中使用Z-缓存的最简单的方法就是在你创建你的设备对象的时候对其初始化,并设定显示方法(presentation methods)。
你这样做:首先使用适当的D3DFORMAT 设置来选择Z-缓存的精度(16位,24位或32位)。然后你会发现用于Z-缓存的的很多设置,但是我考虑的只有D3DFMT_D16(16位)和D3DFMT_D32(32位)这两种。
你出于两个理由来使用不同的精度——考虑存储空间(storage)和考虑质量(quality)。就存储空间而言,32位的Z-缓存占用的空间比16位的Z-缓存要多得多,所以在可能的情况下,努力使用16位的Z-缓存吧。
就质量而言,对于那些彼此挨得特别近的物体使用16位的Z-缓存的话,有时候会导致错误的像素被绘制,因为精度不够。改用32位的Z-缓存可以解决精度问题,但是付出的是占用双倍内存的代价。不过你不需要担心:在快速反应(fast-action)的游戏世界中,速度和优化是更重要的,所以始终使用16位的Z-缓存吧!
回到显示(presentation)的建立上来,在你的应用程序中你可以将下列两行加进来以启用Z-缓存:
d3dp.EnableAutoDepthStencil = TRUE; d3dp.AutoDepthStencilFormat = D3DFMT_D16;// or D3DFMT_D32
现在你可以继续你的初始化例程了。当你准备好利用Z-缓存进行渲染时(it doesn’t kick on automatically,我猜是“它不会自动起作用”的意思),你必须设定适当的渲染状态:
// g_pD3DDevice = pre-initialized deviceobject // To turn on Z-Buffer, use: g_pD3DDevice->SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE); // To turn off Z-Buffer, use: g_pD3DDevice->SetRenderState(D3DRS_ZENABLE,D3DZB_FALSE);
注意
===============================================================================
虽然让Z-缓存一直开着似乎是很合乎逻辑的,但是在你不需它的时候将它关闭可以提高一点速度。在你将按钮等图形绘制到屏幕上时,你需要完全控制在哪里绘制什么东西,所以在你觉得适当的时候就开启或关闭Z-缓存吧。
===============================================================================
如果你已经尝试过了新的Z-缓存功能的话,你也许已经发现了一些不正常的事情。例如,屏幕在几帧之后不更新了,或者太远的物体被裁剪了。这是因为你必须在每一帧之前清除Z-缓存,并且需要重调你的投影矩阵以便实现距离的设置。
要在每一帧前清除Z-缓存,将你的IDirect3DDevice9::Clear函数的调用改写如下:
g_pD3DDevice->Clear(0, NULL, \ D3DCLEAR_TARGET| D3DCLEAR_ZBUFFER, \ D3DCOLOR_RGBA(0.0f,0.0f, 0.0f, 0.0f), 1.0f, 0);
注意前面增加的D3DCLEAR_ZBUFFER 标记。这个标记告诉清除函数,它得将Z-缓存清除为给定值(第五个参数,这里是1.0f)。这个值可以从0.0(最小的Z-缓存深度)变动到1.0(最大的Z-缓存深度)。
使用值1.0告诉清除函数将所有的深度值设为它们的最大值。如果你想要将Z-缓存清除位最大值的一半,那么使用0.5。唯一的问题就是最大值代表什么意思?
Z-缓存代表从观察点的距离(实际上是纵向距离),所以当渲染进行时,比最大的观察距离更远的物体(还有那些太近的物体)不会被绘制。在你调用(通过D3DX)建立投影矩阵的函数的时候,你设定了最小和最大的观察距离,如下列代码所示:
// Set a min distance of 1.0 and a max of1000.0 D3DXMatrixPerspectiveFovLH(&MatrixProj,D3DX_PI/4, \ 1.0f,1.0f, 1000.0f);
最后的两个值是你想要调节的——分别是最小和最大的距离。对这些值进行试验,看看哪些值最适合你;只要你记住,最小距离和最大距离之间的距离越大,Z-缓存的质量就越差。一般来说,将最小距离设为1.0,而将最大距离设为1,000,2,000或5,000。
===============================================================================
本书的随书光盘关于这个主题给了一个程序,叫做ZBuffer。不过我把它进行了比较大的改动,变成两个贴着透明纹理的正方形绕着不同的轴旋转(因此要用到前面所讲的alpha test知识)。下面是程序运行时的截图:
这里用到了两幅部分透明的.png格式图片。由于图片扣得比较马虎,所以看上去效果不太好,大家多多担待哈!
下面是代码的下载地址,包含了原始代码和本人的代码:
ZBuffer代码下载地址
===============================================================================
有时候,你想将图形渲染到显示屏的某一部分上,就像渲染到主应用程序窗口的小窗口上。主视口(The main viewport)一般覆盖了整个显示屏,但是在你需要的时候,你可以改变视口的尺寸以让它覆盖屏幕的一块子区域(就像渲染到一个雷达、汽车后视镜或者其他任何“屏中屏”的情况)。
为了设置一个视口,你首先用你想使用的新视口的坐标和维数来填充一个D3DVIEWPORT9结构体:
typedef struct _D3DVIEWPORT9 { DWORD X; // left X coordinate of viewport DWORD Y; // top Y coordinate of viewport DWORD Width; // Width of viewport DWORD Height; // Height of viewport float MinZ; // 0.0 float MaxZ; // 1.0 } D3DVIEWPORT9;
在你用适当的数据设置结构体后,你通过调用函数ID3DDevice9::SetViewport函数来告诉Direct3D去使用它,如下列代码所示:
// pD3DDevice = pre-initialized 3-D device // Create a viewport D3DVIEWPORT9 d3dvp = { 0,0, 100, 100, 0.0f,1.0f }; // Set the new viewport pD3DDevice->SetViewport(&d3dvp);
从这里开始(在调用SetViewport后),所有的图形都被渲染进了你定义的视口窗口中了。在你用完这个新的视口之后,你重建旧的视口。为了获得旧的视口的设置以便稍后重建它们,你调用ID3DDevice9::GetViewport函数,如下所示:
// pD3DDevice = pre-initialized 3-D device D3DVIEWPORT9 OldViewport; // Get old viewport settings pD3DDevice->GetDevice(&OldViewport);// get old viewport // ... change viewport settings as needed // Restore old viewport pD3DDevice->SetDevice(&OldViewport);
===============================================================================
好啦,我们终于翻译完了这三节内容了!这三节的内容都不难,不过最先讲的粒子还是很值得花时间去研究的!
现在我们已经完成了第2章的一多半内容了!剩下的基本上就是将网格模型了,而这是比较高级的技术了。你也许会问:怎么这第2章这么长呢?这要讲到啥时候啊?后面的章节岂不是更难?别着急,你要知道,其实Jim Adams老哥在这第2章里讲了人家Frank D. Luna花了好几章内容来讲述的东西呢!如果不谈论HLSL语言的话,基本上这第2章的内容就涵盖了“龙书”第一版的所有内容了!是不是很酷?!
不过这里讲得有点急于求成了,所以有些地方看不懂是自然的,大家多多参考“龙书”第二版哈!
另外,虽然后面的内容我没有仔细看,但是可以很负责任地告诉你,后面的章节(至少是我会讲述到的那些章节)的内容没有这一章难!这不禁让我想起了一句名言:开发游戏是一件很困难的事,但是它会随着你的开发进度而变得越来越容易!这是Allen Sherrod,《Ultimate Game Programming with DirectX》一书的作者说的话。