日期:2014-05-17 浏览次数:20771 次
要使用DirectX来获得三维效果,一般首先要生成一个三维模型,然后计算它在可视空间中的投影。这样得到的二维图像十分真实,但是计算量也很大。在大规模场景渲染中,随着模型精度的提高,这样的处理方式十分消耗资源。人眼的分辨率是有限的,对于远处的模型,模糊一些不会影响到整体效果。Billboard技术就是用二维图片来模拟三维模型的投影,从而提高渲染效率。只要距离足够远,通过将二维图片旋转至合适角度,实际渲染效果与三维模型相差无几,但计算量减少很多。本文使用几何着色器,利用Billboard技术在之前的模型中添加树木贴图。
整个过程与上一篇的内容类似。不过这一次树木模型的顶点结构与其他模型不同,所以要重新写一套着色器(TreeVertexShader.hlsl、TreeGeometryShader.hlsl、TreePixelShader.hlsl)。使用Billboard绘制树木时,CPU只要生成树木的位置和大小即可,计算过程均由几何着色器完成,而顶点着色器只起到传递参数的作用,代码如下:
struct VertexShaderInput { float3 center : POSITION; float2 size : SIZE; }; struct VertexShaderOutput { float3 center : POSITION; float2 size : SIZE; }; VertexShaderOutput main( VertexShaderInput input ) { VertexShaderOutputoutput; output.center =input.center; output.size =input.size; return output; }
另外,为了方便观察绘制效果,新像素着色器只进行纹理采样,不实现光照等效果。
SamplerState samplerLinear : register(s0); Texture2D texDiffuse : register(t0); struct PixelInputType { float4 posH : SV_POSITION; float3 posW : POSITION; float3 normalW : NORMAL; float2 texC : TEXCOORD; }; float4 main(PixelInputType pIn) : SV_Target { float4 diffuse =texDiffuse.Sample(samplerLinear, pIn.texC); // alpha值小于0.25,放弃该像素 clip(diffuse.a -0.25f); // 输出纹理颜色 return diffuse; }
三个新着色器中,几何着色器是重点。由于几何着色器在顶点着色器和像素着色器之间,根据前面的代码可以很容易地得到几何着色器的结构定义:
struct GSInput { float3 center : POSITION; float2 size : SIZE; }; struct GSOutput { float4 posH : SV_POSITION; float3 posW : POSITION; float3 normal : NORMAL; float2 tex : TEXCOOD; };
而计算树木贴图的变换矩阵时需要观察点的位置等信息,所以在几何着色器中定义一个常量缓冲区来存储相关信息:
cbuffer cbTreeConstanBuffer : register(b0) { matrix model; matrix view; matrix projection; float4 eye; };
接下来就根据输入的点信息来生成树木模型。具体的数学原理在DirectX游戏编程中有详细的介绍,这里主要关注其实现。
[maxvertexcount(4)] void main( point GSInput input[1], inout TriangleStream<GSOutput > output ) { // // 根据size计算树木贴图的四个顶点坐标 // float halfWidth =0.5f*input[0].size.x; float halfHeight =0.5f*input[0].size.y; float4 v[4]; v[0] = float4(-halfWidth,-halfHeight, 0.0f, 1.0f); v[1] = float4(+halfWidth,-halfHeight, 0.0f, 1.0f); v[2] = float4(-halfWidth,+halfHeight, 0.0f, 1.0f); v[3] = float4(+halfWidth,+halfHeight, 0.0f, 1.0f); // // 四个顶点的纹理坐标 // float2 texC[4]; texC[0] = float2(0.0f, 1.0f); texC[1] = float2(1.0f, 1.0f); texC[2] = float2(0.0f, 0.0f); texC[3] = float2(1.0f, 0.0f); // // 计算使贴图面向观察点的变换矩阵 // float3 up = float3(0.0f, 1.0f, 0.0f); float3 look =input[0].center - eye.xyz; look.y =0.0f; look =normalize(look); float3 right = cross(up,look); float4x4 W; W[0] = float4(right, 0.0f); W[1] = float4(up, 0.0f); W[2] = float4(look, 0.0f); W[3] = float4(input[0].center,1.0f); float4x4 gViewProj =mul(view, projection); float4x4 WVP =mul(W,gViewProj); // // 转换顶点坐标到世界空间 // 输出三角形带 // GSOutput gOut; [unroll] for(int i = 0; i < 4;++i) { gOut.posH = mul(v[i], WVP); gOut.posW = mul(v[i], W).xyz; gOut.normal = look; gOut.tex = texC[i]; output.Append(gOut); } }
有上一篇文章的基础,着色器的代码很容易理解。读入一个顶点(即树木贴图的中心点坐标和贴图的尺寸),生成四个顶点,之后将四个顶点转换到投影空间,并设置好其对应的纹理坐标,接着就可以由像素着色器进行处理。从这个过程中可以看出,顶点能够包含的内容是很广泛的,并不仅仅是坐标信息而已,感觉顶点应该是可由GPU处理的信息集合。
着色器编写完成后,就能在程序中使用了。首先还是定义顶点和常量