公告板是游戏里一很常见的技术,用来绘制树木,草地,爆炸效果等。说白了就求一个矩形框,然后贴一张照片上去。(Ps下由于个人博客所以讲的比较随便,有时候讲billboard,有时候公告板,有时候一个矩形框,都指差不多一个东西)
而Geometry Shader则是Directx10里面推出的一个新的Shader能够处理完整的几何模型。想典型VertexShader只知道并处理顶点,而Geometry Shader知道顶点,直线和三角形并处理它们。
这里用个Demo阐述下如何用GeomtryShader在GPU中绘制Billboard公告板。
1先讲下求公告板的算法。说白了就是求它的世界坐标系。
假设这个公告板上方的向量是v,面向我们眼睛向量w,公告板右手方向u。公告板中心位置为C,我们眼睛位置为E,则有:
用图表达下场景:
则该公告板的世界坐标系为
知道了其算法后,在GeomtryShader中我们主要根据传进来的公告板的中心点位置,求它的世界矩坐标系转换阵。
[maxvertexcount(4)]
void GS(point VS_OUT gIn[1],uint primID:SV_PrimitiveID,inout TriangleStream<GS_OUT> triStream)
{
float halfWidth=0.5f*gIn[0].sizeW.x;
float halfHeight=0.5f*gIn[0].sizeW.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=gEyePosW-gIn[0].posW;
look.y=0.0f;
look=normalize(look);
float3 right=cross(up,look);
matrix world;
world[0]=float4(right,0.0f);
world[1]=float4(up,0.0f);
world[2]=float4(look,0.0f);
world[3]=float4(gIn[0].posW,1.0f);
GS_OUT gOut;
//[unroll]
for(int i=0;i<4;i++)
{
gOut.posW=mul(v[i],world);
gOut.posH=mul(v[i],world);
gOut.posH=mul(gOut.posH,View);
gOut.posH=mul(gOut.posH,Projection);
gOut.normalW=look;
gOut.texC=texC[i];
gOut.primID=primID;
triStream.Append(gOut);
}
}
2了解了公告板的算法以及如何在GeometryShader中实现这个算法之后,因为上面得到的只是一堆矩形框的位置,所以我们需要载入一组纹理图片,方便把这些纹理贴到这些矩形框上去。
总的来说我们是把几张纹理图片放到一个texture2Darray里。Directx11用ID3D11Texture2D 表示这个2d纹理数组(没看错,不管是一张纹理,还是多张,Directx11都是ID3D11Texture2D 来表示)。
创建步骤:
1读入图片,为每一张图片创建一个ID3D11Texture2D srcTex[i]。
2创建用来存储上面所有纹理的纹理数组ID3D11Texture2D texArray;
3将每个纹理srcTex[i]拷贝到texArray相应的位置。
4为texArray创建一个shader resource view.
具体函数如下:
HRESULT Tree::BuildShaderResourceView()
{
HRESULT hr=S_OK;
std::wstring filenames[4]={ L"tree0.dds",L"tree1.dds",L"tree2.dds",L"tree3.dds" };
// 1读入图片,为每一张图片创建一个ID3D11Texture2D srcTex[i]。
ID3D11Texture2D* srcTex[4];
for(UINT i=0;i<4;i++)
{
D3DX11_IMAGE_LOAD_INFO loadInfo;
loadInfo.Width=D3DX11_DEFAULT;
loadInfo.Height=D3DX11_DEFAULT;
loadInfo.Depth=D3DX11_DEFAULT;
loadInfo.CpuAccessFlags=D3D11_CPU_ACCESS_READ;
loadInfo.BindFlags=0;
loadInfo.Filter=D3DX11_FILTER_NONE;
loadInfo.MipFilter=D3DX11_FILTER_NONE;
loadInfo.FirstMipLevel=0;
loadInfo.Format=DXGI_FORMAT_R8G8B8A8_UNORM;
loadInfo.MipLevels=D3DX11_DEFAULT;
loadInfo.MiscFlags=0;
loadInfo.Usage=D3D11_USAGE_STAGING;
loadInfo.pSrcInfo=0;
IFR(D3DX11CreateTextureFromFile(m_pDevice,filenames[i].c_str(),&loadInfo,NULL,(ID3D11Resource**)&srcTex[i],NULL));
}
//2创建用来存储上面所有纹理的纹理数组ID3D11Texture2D texArray;
D3D11_TEXTURE2D_DESC texDesc;
srcTex[0]->GetDesc(&texDesc);
D3D11_TEXTURE2D_DESC texArrayDesc;
texArrayDesc.Width=texDesc.Width;
texArrayDesc.Height=texDesc.Height;
texArrayDesc.MipLevels=texDesc.MipLevels;
texArrayDesc.ArraySize=4;
texArrayDesc.BindFlags=D3D11_BIND_SHADER_RESOURCE;
texArrayDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM;
texArrayDesc.CPUAccessFlags=0;
texArrayDesc.SampleDesc.Count=1;
texArrayDesc.SampleDesc.Quality=0;
texArrayDesc.MiscFlags=0;
texArrayDesc.Usage=D3D11_USAGE_DEFAULT;
ID3D11Texture2D *texArray=NULL;
IFR(m_pDevice->CreateTexture2D(&texArrayDesc,NULL,&texArray));
//3将每个纹理srcTex[i]拷贝到texArray相应的位置。
for(int i=0;i<4;i++)
{
for(int j=0;j<texDesc.MipLevels;j++)
{
D3D11_MAPPED_SUBRESOURCE subTex;
m_pContext->Map(srcTex[i],j,D3D11_MAP_READ,0,&subTex);
m_pContext->UpdateSubresource(texArray,D3D11CalcSubresource(j,i,texDesc.MipLevels),NULL,subTex.pData,subTex.RowPitch,0);
m_pContext->Unmap(srcTex[i],j);
}
}
//4为texArray创建一个shader resource view.
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc,sizeof(srvDesc));
srvDesc.Format=texArrayDesc.Format;
srvDesc.Texture2DArray.MipLevels=texArrayDesc.MipLevels;
srvDesc.Texture2DArray.MostDetailedMip=0;
srvDesc.Texture2DArray.ArraySize=4;
srvDesc.Texture2DArray.FirstArraySlice=0;
srvDesc.ViewDimension=D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
IFR(m_pDevice->CreateShaderResourceView(texArray,&srvDesc,&m_pTreeMapRV));
SAFE_RELEASE(texArray);
for(int i=0;i<4;i++)
SAFE_RELEASE(srcTex[i]);
return hr;
}
最后我把Vertex,Geometry,还有特别负责绘制的Pixel Shader的具体代码都贴出来。
VS_OUT VS(VS_IN vIn)
{
VS_OUT vOut;
vOut.posW=vIn.posW;
vOut.sizeW=vIn.sizeW;
return vOut;
}
[maxvertexcount(4)]
void GS(point VS_OUT gIn[1],uint primID:SV_PrimitiveID,inout TriangleStream<GS_OUT> triStream)
{
float halfWidth=0.5f*gIn[0].sizeW.x;
float halfHeight=0.5f*gIn[0].sizeW.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=gEyePosW-gIn[0].posW;
look.y=0.0f;
look=normalize(look);
float3 right=cross(up,look);
matrix world;
world[0]=float4(right,0.0f);
world[1]=float4(up,0.0f);
world[2]=float4(look,0.0f);
world[3]=float4(gIn[0].posW,1.0f);
GS_OUT gOut;
//[unroll]
for(int i=0;i<4;i++)
{
gOut.posW=mul(v[i],world);
gOut.posH=mul(v[i],world);
gOut.posH=mul(gOut.posH,View);
gOut.posH=mul(gOut.posH,Projection);
gOut.normalW=look;
gOut.texC=texC[i];
gOut.primID=primID;
triStream.Append(gOut);
}
}
float4 PS(GS_OUT pIn):SV_Target
{
float3 uvw=float3(pIn.texC,pIn.primID%4);
float4 diffuse=gDiffuseMap.Sample(gLinearSam,uvw);
clip(diffuse.a-0.5f);
return diffuse;
//return float4(1.0f,0.0f,0.0f,0.5f);
}
最后实验截图,我们的树都是公告板生成的。