日志
用顶点着色器实现渐变动画 2.3.1渐变动画(Morphing) Morphing渐变是20世纪90年代出现的一种革命性的计算机图形技术,该技术使得动画序列平滑且易于处理,即使在低档配置的计算机系统上也能正常运行。 渐变是指随时间的变化把一个形状改变为另一个形状。对我们而言,这些形状就是Mesh网格模型。渐变网格模型的处理就是以时间轴为基准,逐渐地改变网格模型顶点的坐标,从一个网格模型的形状渐变到另外一个。请看图2.3:
我们在程序中使用两个网格模型——源网格模型和目标网格模型,设源网格模型中顶点1的坐标为A(Ax,Ay,Az),目标网格模型中对应顶点1的坐标为B(Bx,By,Bz),要计算渐变过程中时间点t所对应的顶点1的坐标C(Cx,Cy,Cz),我们使用如下方法: T为源网格模型到目标网格模型渐变所花费的全部时间,得到时间点t占整个过程T的比例为: S = t / T 那么顶点1在t时刻对应的坐标C为: C = A * (1-S)+ B * S 这样,在渲染过程中我们根据时间不断调整S的值,就得到了从源网格模型(形状一)到目标网格模型(形状二)的平滑过渡。 接下来将在程序里使用顶点着色器实现我们的渐变动画。
2.3.2渐变动画中的顶点声明 程序中,我们设定一个顶点对应两个数据流,这两个数据流分别包含了源网格模型的数据和目标网格模型的数据。渲染过程中,我们在着色器里根据两个数据流中的顶点数据以及时间值确定最终的顶点信息。 个数据流包含分量如下: 源网格模型数据流:顶点位置、顶点法线、纹理坐标; 目标网格模型数据流:顶点位置、顶点法线; 注意目标网格模型数据流没有包含纹理坐标,因为纹理对于两个网格模型都是一样的,所以仅使用源网格模型的纹理就可以了。 顶点声明指定如下: D3DVERTEXELEMENT9 decl[] = { //源网格模型数据流,包含分量位置、法线、纹理坐标 { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,D3DDECLUSAGE_ POSITION, 0 }, { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ NORMAL, 0 }, { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ TEXCOORD, 0 },
//目标网格模型数据流,包含分量位置、纹理坐标 { 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ POSITION, 1 }, { 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ NORMAL, 1 }, D3DDECL_END() };
2.3.3渐变动画中的顶点着色器 下面给出顶点着色器源码,代码存储于vs.txt中,该顶点着色器根据源网格模型数据流和目标网格模型数据流中的信息以及时间标尺值计算出顶点最终位置信息,并对顶点做了坐标变换和光照处理。代码中给出了详细的注释,帮助读者理解。 //全局变量 //世界矩阵、观察矩阵、投影矩阵的合矩阵,用于顶点的坐标变换 matrix WVPMatrix;
//光照方向 vector LightDirection; //存储2.3.1小节提到的公式S = t / T中的时间标尺S值 //注意到Scalar是一个vector类型,我们在Scalar.x中存储了S值,Scalar.y中存储的则是(1-S)值 vector Scalar;
//输入 struct VS_INPUT { //对应源网格模型数据流中的顶点分量:位置、法线、纹理坐标 vector position : POSITION; vector normal : NORMAL; float2 uvCoords : TEXCOORD; //对应目标网格模型数据流中的顶点分量:位置、法线 vector position1 : POSITION1; vector normal1 : NORMAL1; };
//输出 struct VS_OUTPUT { vector position : POSITION; vector diffuse : COLOR; float2 uvCoords : TEXCOORD; };
//入口函数 VS_OUTPUT Main(VS_INPUT input) { VS_OUTPUT output = (VS_OUTPUT)0;
//顶点最终位置output.position取决于源网格模型数据流中位置信息input.position和目标网格模型数据流中位置信息input.position1以及时间标尺Scalar的值 //对应2.3.1小节中的公式C = A * (1-S)+ B * S output.position = input.position*Scalar.x + input.position1*Scalar.y; //顶点坐标变换操作 output.position = mul(output.position, WVPMatrix);
//计算顶点最终法线值 vector normal = input.normal*Scalar.x + input.normal1*Scalar.y; //逆光方向与法线的点积,获得漫射色彩 output.diffuse = dot((-LightDirection), normal);
//存储纹理坐标 output.uvCoords = input.uvCoords;
return output; } 以上是本例用到的顶点着色器,在接下来的应用程序中,我们将给三个着色器全局变量赋值: ² WVPMatrix; 世界矩阵、观察矩阵、投影矩阵的合矩阵,用于顶点的坐标变换; ² LightDirection 光照方向; ² Scalar 存储2.3.1小节提到的公式S = t / T中的时间标尺S值; 注意到Scalar是一个vector类型,我们在Scalar.x中存储了S值,Scalar.y中存储的则是(1-S)值;
2.3.4应用程序 我们在应用程序中执行以下操作: · 加载两个两个Mesh模型:源网格模型,目标网格模型; · 创建、设置顶点声明; · 创建、设置顶点着色器; · 为着色器全局赋值; · 把两个Mesh模型数据分别绑定到两个数据流中; · 渲染Mesh模型; 下面是应用程序代码: … /*********************声明变量*****************/ //两个指向LPD3DXMESH的指针,分别用于存储源网格模型和目标网格模型; LPD3DXMESH g_SourceMesh; LPD3DXMESH g_TargetMesh;
//顶点声明指针 IDirect3DVertexDeclaration9 *g_Decl = NULL;
//顶点着色器 IDirect3DVertexShader9 *g_VS = NULL; //常量表 ID3DXConstantTable* ConstTable = NULL;
//常量句柄 D3DXHANDLE WVPMatrixHandle = 0; D3DXHANDLE ScalarHandle = 0; D3DXHANDLE LightDirHandle = 0; … /***************程序初始化*****************/ //加载源、目标网格模型 Load_Meshes();
//顶点声明 D3DVERTEXELEMENT9 MorphMeshDecl[] = { //1st stream is for source mesh - position, normal, texcoord { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 }, { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
//2nd stream is for target mesh - position, normal { 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 1 }, { 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1 }, D3DDECL_END() };
//创建顶点着色器 ID3DXBuffer* shader = NULL; ID3DXBuffer* errorBuffer = NULL; D3DXCompileShaderFromFile("vs.txt", 0, 0, "Main", // entry point function name "vs_1_1", D3DXSHADER_DEBUG, &shader, &errorBuffer, &ConstTable);
if(errorBuffer) { ::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0); ReleaseCOM(errorBuffer); }
//创建顶点着色器 g_pd3dDevice->CreateVertexShader((DWORD*)shader->GetBufferPointer(), &g_VS);
//创建顶点声明 g_pd3dDevice->CreateVertexDeclaration(MorphMeshDecl ,&g_Decl);
//得到各常量句柄 WVPMatrixHandle = ConstTable->GetConstantByName(0, "WVPMatrix"); ScalarHandle = ConstTable->GetConstantByName(0, "Scalar"); LightDirHandle = ConstTable->GetConstantByName(0, "LightDirection");
//为着色器全局变量LightDirection赋值 ConstTable->SetVector(g_pd3dDevice, LightDirHandle, &D3DXVECTOR4(0.0f, -1.0f, 0.0f, 0.0f)); //设置各着色器变量为默认值 ConstTable->SetDefaults(g_pd3dDevice); … /*******************渲染*******************/ g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(153,153,153), 1.0f, 0 ); g_pd3dDevice->BeginScene();
//为着色器全局变量WVPMatrix赋值 D3DXMATRIX matWorld, matView, matProj; g_pd3dDevice->GetTransform(D3DTS_WORLD, &matWorld); g_pd3dDevice->GetTransform(D3DTS_VIEW, &matView); g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &matProj); D3DXMATRIX matWVP; matWVP = matWorld * matView * matProj;
ConstTable->SetMatrix(g_pd3dDevice, WVPMatrixHandle, &matWVP);
//为着色器全局变量Scalar赋值,注意程序中获取时间标尺值Scalar的方法 float DolphinTimeFactor = (float)(timeGetTime() % 501) / 250.0f; float Scalar = (DolphinTimeFactor<=1.0f)?DolphinTimeFactor:(2.0f-DolphinTimeFactor); ConstTable->SetVector(g_pd3dDevice,ScalarHandle,&D3DXVECTOR4(1.0f-Scalar, Scalar, 0.0f, 0.0f));
//设置顶点着色器和顶点声明 g_pd3dDevice->SetVertexShader(g_VS); g_pd3dDevice->SetVertexDeclaration(g_Decl);
//绑定目标网格模型的定点缓存到第二个数据流中 IDirect3DVertexBuffer9 *pVB = NULL; g_TargetMesh->GetVertexBuffer(&pVB); g_pd3dDevice->SetStreamSource(1, pVB, 0, D3DXGetFVFVertexSize(g_TargetMesh->GetFVF())); ReleaseCOM(pVB);
//绑定源网格模型的顶点缓存到第一个数据流中 g_SourceMesh->GetVertexBuffer(&pVB); g_pd3dDevice->SetStreamSource(0, pVB, 0, D3DXGetFVFVertexSize(g_TargetMesh->GetFVF())); ReleaseCOM(pVB);
//绘制Mesh网格模型 DrawMesh(g_SourceMesh, g_pMeshTextures0, g_VS, g_Decl);
g_pd3dDevice->EndScene(); g_pd3dDevice->Present( NULL, NULL, NULL, NULL ); …
2.3.5对应用程序的一点说明 程序中我们使用SetStreamSource方法把源网格模型和目标网格模型中的顶点缓存分别绑定到两个设备数据流,但是Direct3D对数据流中的数据的真正引用只有在调用诸如DrawPrimitive、DrawIndexedPrimitive之类的绘制方法时才发生,因此在绘制Mesh网格模型时我们不能再使用传统的DrawSubmit方法,而是使用了DrawIndexedPrimitive,下面就如何调用DrawIndexedPrimitive绘制Mesh模型进行说明,该部分内容和HLSL着色器关系不大,在这里列出仅仅是为了大家理解程序的完整性,读者完全可以跳过本节不看。 使用DrawIndexedPrimitive绘制Mesh模型的步骤如下: 1. 加载网格模型后使用OptimizeInPlace方法对Mesh进行优化; 2. 一旦优化了网格模型,你就可以查询ID3DXMesh对象,得到一个D3DXATTRIBUTERANGE数据类型的数组,我们称之为属性列表,该数据类型被定义如下: typedef struct_D3DXATTRIBUTERANGE{ DWORD AttribId; //子集编号 DWORD FaceStart; //这两个变量用于圈定本子集中的多边形 DWORD FaceCount; DWORD VertexStart; //这两个变量用于圈定本子集中的顶点 DWORD VertexCount; } D3DXATTRIBUTERANGE; 我们属性列表中的每一项都代表一个被优化后Mesh的一个子集,D3DXATTRIBUTERANGE结构的各字段描述了该子集的信息。 1. 得到属性数据后,我们就调用DrawIndexedPrimitive方法可以精美地渲染子集了。
下面是绘制Mesh模型的程序代码: 在Load_Meshes()函数的最后,我们使用OptimizeInPlace方法对源网格模型和目标网格模型进行优化,其他加载材质和纹理的操作和之前一样,相信大家能够理解: … //优化源网格模型 g_SourceMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL); … //优化目标网格模型 g_TargetMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL); …
在Draw_Mesh()函数中,渲染模型,注意程序是如何配合属性表调用DrawIndexedPrimitive方法进行绘制的: …
//分别得到指向Mesh模型顶点缓存区和索引缓存区的指针 IDirect3DVertexBuffer9 *pVB = NULL; IDirect3DIndexBuffer9 *pIB = NULL; pMesh->GetVertexBuffer(&pVB); pMesh->GetIndexBuffer(&pIB);
//得到Mesh模型的属性列表 DWORD NumAttributes; D3DXATTRIBUTERANGE *pAttributes = NULL; pMesh->GetAttributeTable(NULL, &NumAttributes); pAttributes = new D3DXATTRIBUTERANGE[NumAttributes]; pMesh->GetAttributeTable(pAttributes, &NumAttributes);
//设置顶点着色器和顶点声明 g_pd3dDevice->SetVertexShader(pShader); g_pd3dDevice->SetVertexDeclaration(pDecl);
//设置数据流 g_pd3dDevice->SetStreamSource(0, pVB, 0, D3DXGetFVFVertexSize(pMesh->GetFVF())); g_pd3dDevice->SetIndices(pIB);
//遍历属性列表并配合其中的信息调用DrawIndexPrimitive绘制各个子集 for(DWORD i=0;i<NumAttributes;i++) { if(pAttributes[i].FaceCount) { //Get material number DWORD MatNum = pAttributes[i].AttribId;
//Set texture g_pd3dDevice->SetTexture(0, pTextures[MatNum]);
//Draw the mesh subset g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, pAttributes[i].VertexStart, pAttributes[i].VertexCount, pAttributes[i].FaceStart * 3, pAttributes[i].FaceCount); } }
//Free resources ReleaseCOM(pVB); ReleaseCOM(pIB); delete [] pAttributes;
…
编译运行程序,效果如图2.4所示,你将看到屏幕上白色的海豚上下翻腾,同时感受到顶点着色器为渲染效果所带来的巨大改善。
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/trcj1/archive/2007/02/13/1509471.aspx |