- 注意!本文是在下几年前入门期间所写(young and naive),其中许多表述可能不正确,为防止误导,请各位读者仔细鉴别。
用几何着色器实现Billboard
几何着色器简介
几何着色器的一般形式如下
[maxvertexcount(N)]
void ShaderName (
PrimitiveType InputVertexType InputName [NumElements],
inout StreamOutputObject<OutputVertexType> OutputName)
{
// Geometry shader body…
}
第一排方括号内的是最大输出的顶点数量,第一个参数是顶点着色器输出,也就是几何着色器的输入,输入类型有以下五种:
第二个参数要用inout标注,输出的是一个StreamOutputObject,这是一个模板类,取值有以下几种
可以用StreamOutputObject<OutputVertexType>::Append(OutputVertexType v);
方法来输出顶点,输出的一定是strip,如果希望输出list,可以用StreamOutputObject<OutputVertexType>::RestartStrip();
来让strip重新开始。
Texture Array
有的时候可以吧几张图片放在同一张纹理上,形成一个纹理数组,hlsl中输入参数长这样
Texture2DArray gTreeMapArray;
当然也可以用以下方法代替:
Texture2D TexArray[4];
…
float4 PS(GeoOut pin) : SV_Target
{
float4 c = TexArray[pin.PrimID%4].Sample(samLinear,pin.Tex);
新的DX12支持后面这种,但是旧的版本不一定对这种方法支持的很好,所以有的时候我们需要用Texture Array来达到同样效果。
在Texture Array上采样可以通过下面这种方法
float3 uvw = float3(pin.Tex, pin.PrimID%4);
float4 diffuseAlbedo = gTreeMapArray.Sample(gsamAnisotropicWrap, uvw) * gDiffuseAlbedo;
uvw的前两维就是uv,第三维w是数组下标,从0开始。
然后primitiveID可以在几何着色器的输入种通过SV_PrimitiveID获得,如下:
[maxvertexcount(4)]
void GS(point VertexOut gin[1],
uint primID : SV_PrimitiveID,
inout TriangleStream<GeoOut> triStream)
{
···
gout.PrimID = primID;
···
}
如果有几何着色器的话,primitive id只能在几何着色器的输入签名中出现,如果没有几何着色器的话,则可以在像素着色器的输入参数中获取:
float4 PS(VertexOut pin, uint primID : SV_PrimitiveID) : SV_Target
{
// Pixel shader body...
}
载入Texture Array的时候,绑定srv的操作不一样,应该注意,以下会给出代码。
接下来还有几个概念可以了解一下
横着的是texture数组,一竖条称为一个array slice,竖着的是mipmap列,一横条称作一个mip slice,其中一张图叫做一个subresource,subresource的默认标号方法如下
要获取一个subresource的标号的话,可以用如下方法
inline UINT D3D12CalcSubresource( UINT MipSlice, UINTArraySlice, UINT PlaneSlice, UINT MipLevels, UINT ArraySize )
{
return MipSlice + ArraySlice * MipLevels + PlaneSlice * MipLevels * ArraySize;
}
Alpha-To-Coverage
之前在blend一章也提到过这是个对叶子、头发等渲染非常有用的一个设置,首先这个问题来源于,计算msaa的时候,以4x msaa为例,着色器只会在采样中心运行一次,然后在4个小方块里检查coverage,然后把颜色取平均,但是问题来了,这个检查coverage的时候是只看多边形有没有cover这个小方块,是不考虑alpha通道的,所以这样的话效果会不好,因此,我们希望把alpha低的地方在取平均的时候加权更小,比如有一个方块虽然被多边形cover了但他的alpha只有0.5,那么颜色取平均的时候这个方块的颜色还要再乘个0.5,这样的话,出来的效果就是alpha越低的地方颜色越淡,过渡也就会均匀很多,按理说我们希望所有有透明度的地方都打开alpha-to-coverage检测,这个设置在D3D12_BLEND_DESC::AlphaToCoverageEnable = true
,叶子、头发之类的都可以用这个加强效果,开销主要来源于msaa,ff的头发就是用了这种。
Tree Billboards Demo
接下来实现一个非常简单的billboard demo,即用一系列点经过几何着色器变成一个面片(两个三角面),然后在这个三角面上渲染树的图案,同时,树的y轴永远朝上,xz轴永远朝向摄像机。
整个demo基于之前的blend demo,下面只给出修改了的关键部分代码
首先创建srv的时候,树的贴图是个Texture Array,创建的时候ViewDimension是D3D12_SRV_DIMENSION_TEXTURE2DARRAY,此外还有些别的设置不同,可以注意一下。
void TreeBillboardsApp::BuildDescriptorHeaps()
{
//
// Create the SRV heap.
//
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {
};
srvHeapDesc.NumDescriptors = 4;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&mSrvDescriptorHeap)));
//
// Fill out the heap with actual descriptors.
//
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
auto grassTex = mTextures["grassTex"]->Resource;
auto waterTex = mTextures["waterTex"]->Resource;
auto fenceTex = mTextures["fenceTex"]->Resource;
auto treeArrayTex = mTextures["treeArrayTex"]->Resource;
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {
};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = grassTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = -1;
md3dDevice->CreateShaderResourceView(grassTex.Get