前言
本篇为个人学习笔记,主要展示什么是几何着色器,及为什么需要它,如何使用几何着色器,以及用公告牌实现森林
什么是几何着色器?
几何着色器是顶点和片段着色器间一个可选的着色器,位于曲面细分和光栅化阶段之间,随着 2006 年底 DirectX 10 的发布被加入到硬件加速图形管线中.几何着色器作为 Shader Model 4.0 的一部分,不能在早期着色模型(<= SM 3.0)中使用
几何着色器以完整的图元(任意类型的图元)作为顶点的数组作为输入,如三角形、线段、点,输出对应的图元。与顶点着色器不同,几何着色器可以创建/销毁图元
几何着色器可以改变输入的图元拓扑结构
,但只能输出点、折线和三角形条
几何着色器输出的图元由顶点列表定义,经过几何着色器阶段的处理后,便得到位于齐次裁剪空间中的一系列图元
为什么需要几何着色器?
由于几何着色器可以创建/销毁图元,因此根据该特性可以实现许多效果,如将输入的图元扩展为一个或更多其他类型的图元
几何着色器的编写
几何着色器的编写与顶点着色器、片段着色器类似,有几个重点:
-
必须先指定几何着色器单次调用输出的顶点数量的最大值。但出于性能考虑,该值需尽可能小.一般来说标量数量
1-20最佳
,在27-40会下降至峰值性能的50%,标量个数等于顶点数量的最大值*顶点结构体中标量个数的乘积,如一个顶点结构体定义"float3 pos : POSITION"和"float2 tex : TEXCOORD",那么该结构体包含五个标量 -
几何着色器的输入顶点类型为顶点着色器输出的顶点类型,该输入参数必须是一个包含特定图元的顶点数组——点为一个顶点,线条为两个顶点,三角形为三个顶点,线及其邻接图元为4个顶点,三角形及其邻接图元为6个顶点,且输入参数必须以图元类型作为前缀,前缀类型如下:
- point:输入图元为点
- line:输入图元为线列表/线条带
- triangle:输入图元为三角形列表/三角形带
- lineadj:输入图元为线列表及其邻接图元/线条带及其邻接图元
- triangleadj:输入图元为三角形列表及其邻接图元/三角形带及其邻接图元
-
几何着色器不会区分输入的图元是列表(list)结构还是带状(strip)结构。如,若绘制的图元为三角形带,几何着色器仍把三角形带看作多个三角形并对其拆分单独进行处理——三角形三个顶点.但是绘制strip结构会产生额外开销,因为几何着色器会多次处理共用的顶点(毕竟这里没有索引)
-
输出是一种流(stream)类型,且输入必须标有inout修饰符。stream是模板类型,存有一系列顶点,它们定义了几何着色器输出的几何图形,这些顶点会构成图元
-
stream类型有如下三种
- PointStream<OutputVertexType> :一系列顶点定义的点列表
- LineStream<OutputVertexType>:一系列顶点定义的线条带
- TriangleStream<OutputVertexType>:一系列顶点定义的三角形带
-
对于线条和三角形来说,几何着色器输出的对应图元
默认为线条带和三角形带
,若想输出线条列表和三角形列表需借助函数RestartStrip()void StreamOutputObject<OutputVertexType>::RestartStrip();
-
向流列表中添加顶点:Append()
void StreamOutputObject<OutputVertexType>::Append(OutputVertexType v);
-
以下为它的模板:
[maxvertexcount(N)] //单次调用所输出的顶点数量的最大值
void ShaderName (PrimitiveType InputVertexType InputName[NumElements], inout StreamOutputObject<OutputVertexType>OutputName)
{
// Geometry shader body…
}
来看一些示例:
-
GS最多输出4个顶点,输入图元为一根线条,输出为一个三角形带
[maxvertexcount(4)] void GS(line VertexOut gin[2], inout TriangleStream<GeoOut> triStream) { //... }
-
GS最多输出32个顶点,输入图元为一个三角形,输出为一个三角形带
[maxvertexcount(32)] void GS(triangle VertexOut gin[3], inout TriangleStream<GeoOut> triStream) { //... }
-
GS最多输出4个顶点,输入图元为一个点,输出为一个三角形带
[maxvertexcount(4)] void GS(point VertexOut gin[1], inout TriangleStream<GeoOut> triStream) { //... }
-
Append()和RestartStrip()的使用方法,该实例将输入的三角形细分,并输出细分后的4个三角形
struct VertexOut { float3 PosL : POSITION; float3 NormalL : NORMAL; float2 Tex : TEXCOORD; } struct GeoOut { float4 PosH : SV_POSITION; float3 PosW : POSITION; float3 NormalW : NORMAL; float2 Tex : TEXCOORD; float FogLerp : FOG; } //细分 void Subdivide(VertexOut inVerts[3], out VertexOut outVerts[6]) { // 1 // * // / \ // / \ // m0*–—*m1 // / \ / \ // / \ / \ // *–-----*--–--—* // 0 m2 2 VertexOut m[3]; //计算三角形每条边的中点 m[0].PosL = 0.5f*(inVerts[0].PosL+inVerts[1].PosL); m[1].PosL = 0.5f*(inVerts[1].PosL+inVerts[2].PosL); m[2].PosL = 0.5f*(inVerts[2].PosL+inVerts[0].PosL); //规范化 m[0].PosL = normalize(m[0].PosL); m[1].PosL = normalize(m[1].PosL); m[2].PosL = normalize(m[2].PosL); //求法线 m[0].NormalL = m[0].PosL; m[1].NormalL = m[1].PosL; m[2].NormalL = m[2].PosL; //纹理坐标插值 m[0].Tex = 0.5f*(inVerts[0].Tex+inVerts[1].Tex); m[1].Tex = 0.5f*(inVerts[1].Tex+inVerts[2].Tex); m[2].Tex = 0.5f*(inVerts[2].Tex+inVerts[0].Tex); outVerts[0] = inVerts[0]; outVerts[1] = m[0]; outVerts[2] = m[2]; outVerts[3] = m[1]; outVerts[4] = inVerts[2]; outVerts[5] = inVerts[1]; } void OutputSubdivision(VertexOut v[6], inout TriangleStream<GeoOut> triStream) { GeoOut gout[6]; [unroll] for(int i = 0; i < 6; ++i) { //顶点变换至世界空间 gout[i].PosW = mul(float4(v[i].PosL, 1.0f), gWorld).xyz; gout[i].NormalW = mul(v[i].NormalL, (float3x3)gWorldInvTranspose); //顶点变换至齐次裁剪空间 gout[i].PosH = mul(float4(v[i].PosL, 1.0f), gWorldViewProj); gout[i].Tex = v[i].Tex; //以两个三角形带来绘制细分的三角形 //三角形带1 : 底部的三个三角形 //三角形带2 : 顶部的一个三角形 [unroll] for(int j = 0; j < 5; ++j) { triStream.Append(gout[j]); } triStream.RestartStrip(); triStream.Append(gout[1]); triStream.Append(gout[5]); triStream.Append(gout[3]); } [maxvertexcount(8)] void GS(triangle VertexOut gin[3], inout TriangleStream<GeoOut>) { VertexOut v[6]; Subdivide(gin, v); OutputSubdivision(v, triStream); }
几何着色器的编译,绑定至流水线
几何着色器的编译方式和绑定至流水线的方式与VS、PS相同。示例如下:
mShaders["treeSpritePS"] = d3dUtil::CompileShader(L"Shaders\\TreeSprite.hlsl", alphaTestDefines, "PS", "ps_5_0");
D3D12_GRAPHICS_PIPELINE_STATE_DESC treeSpritePsoDesc = opaquePsoDesc;
treeSpritePsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders["treeSpritePS"]->GetBufferPointer()),
mShaders["treeSpritePS"]->GetBufferSize()
};
用公告板实现森林
-
何为公告板(Billboarding)?
公告板是一种用3D物体图片的四边形来替代3D物体的渲染,他会根据观察方向来确定多边形面朝方向,随着观察角度的变化,公告板多边形的方向也会根据需求随之改变
如下图,所有公告牌都面对着摄像机
-
公告板有什么用处?
与 alpha 纹理 和 动画相结合,可以用公告板技术表示许多
不具有平滑实体表面
的现象,如烟,火,雾,爆炸效果,云朵等
求四边形公告板的顶点坐标
若给定 世界空间中四边形公告板的中心位置为\(C(C_x, C_y, C_z)\),摄像机位置\(E(E_x, E_y, E_z)\),公告牌的局部坐标系和世界空间的关系,公告牌在世界空间中的大小,即可求得公告牌的四个顶点坐标
\(w = \frac{(E_x - C_x, 0, E_z - C_z)}{\|E_x - C_x, 0, E_z - C_z \|} \\
v = (0,1,0) \\
u = v \times w\)
v[0] = float4(gin[0].CenterW + halfWidth*right - halfHeight*up, 1.0f);
v[1] = float4(gin[0].CenterW + halfWidth*right + halfHeight*up, 1.0f);
v[2] = float4(gin[0].CenterW - halfWidth*right - halfHeight*up, 1.0f);
v[3] = float4(gin[0].CenterW - halfWidth*right + halfHeight*up, 1.0f);
HLSL示例
由于需要确定公告板的中心点,因此我们需要点图元
:
将PSO的PrimitiveTopologyType设为D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT,ID3D12GraphicsCommandList::IASetPrimitiveTopology设为D3D_PRIMITIVE_TOPOLOGY_POINTLIST。而在几何着色器中,我们需要把这些中心点扩展为公告板的四边形,还需要计算公告板的世界矩阵
-
顶点结构体
在顶点结构体中存储公告板的中心点的位置,以及公告板的宽高,如此即可表示其大小
struct TreeSpriteVertex { XMFLOAT3 Pos; XMFLOAT2 Size; }; mTreeSpriteInputLayout = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "SIZE", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, };
-
HLSL实现:以下将展示VS、PS、GS三种着色器是如何搭配使用的
该例中GS用于把一个点扩展为一个四边形,并让其和y轴对齐来正对相机
#ifndef NUM_DIR_LIGHTS #define NUM_DIR_LIGHTS 3 #endif #ifndef NUM_POINT_LIGHTS #define NUM_POINT_LIGHTS 0 #endif #ifndef NUM_SPOT_LIGHTS #define NUM_SPOT_LIGHTS 0 #endif // 包含光照所需的结构体和函数 #include "LightingUtil.hlsl" //纹理数组 Texture2DArray gTreeMapArray : register(t0); //静态采样器 SamplerState gsamPointWrap : register(s0); SamplerState gsamPointClamp : register(s1); SamplerState gsamLinearWrap : register(s2); SamplerState gsamLinearClamp : register(s3); SamplerState gsamAnisotropicWrap : register(s4); SamplerState gsamAnisotropicClamp : register(s5); //与物体相关的常量数据 cbuffer cbPerObject : register(b0) { float4x4 gWorld; float4x4 gTexTransform; }; // 绘制所用的杂项常量数据,每帧更新 cbuffer cbPass : register(b1) { float4x4 gView; float4x4 gInvView; float4x4 gProj; float4x4 gInvProj; float4x4 gViewProj; float4x4 gInvViewProj; float3 gEyePosW; float cbPerObjectPad1; float2 gRenderTargetSize; float2 gInvRenderTargetSize; float gNearZ; float gFarZ; float gTotalTime; float gDeltaTime; float4 gAmbientLight; float4 gFogColor; float gFogStart; float gFogRange; float2 cbPerObjectPad2; // Indices [0, NUM_DIR_LIGHTS) are directional lights; // indices [NUM_DIR_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights; // indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS) // are spot lights for a maximum of MaxLights per object. Light gLights[MaxLights]; }; //材质 cbuffer cbMaterial : register(b2) { float4 gDiffuseAlbedo; float3 gFresnelR0; float gRoughness; float4x4 gMatTransform; }; //输入的顶点结构体 struct VertexIn { float3 PosW : POSITION; float2 SizeW : SIZE; }; //输出的顶点结构体 struct VertexOut { float3 CenterW : POSITION; float2 SizeW : SIZE; }; //GS的输出结构体 struct GeoOut { float4 PosH : SV_POSITION; float3 PosW : POSITION; float3 NormalW : NORMAL; float2 TexC : TEXCOORD; uint PrimID : SV_PrimitiveID; }; VertexOut VS(VertexIn vin) { VertexOut vout; vout.CenterW = vin.PosW; vout.SizeW = vin.SizeW; return vout; } // 因为需要将一个点扩展为一个四边形,所以每次调用GS最多输出四个顶点 [maxvertexcount(4)] void GS(point VertexOut gin[1], uint primID : SV_PrimitiveID, inout TriangleStream<GeoOut> triStream) { //计算公告板的局部坐标系和世界空间的相对关系,使得公告板和y轴对齐且面向相机 float3 up = float3(0.0f, 1.0f, 0.0f); float3 look = gEyePosW - gin[0].CenterW; look.y = 0.0f; // 使公告板立于xz平面 look = normalize(look); float3 right = cross(up, look); // 计算世界空间中四边形的顶点 float halfWidth = 0.5f*gin[0].SizeW.x; float halfHeight = 0.5f*gin[0].SizeW.y; float4 v[4]; v[0] = float4(gin[0].CenterW + halfWidth*right - halfHeight*up, 1.0f); v[1] = float4(gin[0].CenterW + halfWidth*right + halfHeight*up, 1.0f); v[2] = float4(gin[0].CenterW - halfWidth*right - halfHeight*up, 1.0f); v[3] = float4(gin[0].CenterW - halfWidth*right + halfHeight*up, 1.0f); //将四边形的顶点变换到世界空间,将其以三角形带的形式输出 float2 texC[4] = { float2(0.0f, 1.0f), float2(0.0f, 0.0f), float2(1.0f, 1.0f), float2(1.0f, 0.0f) }; GeoOut gout; [unroll] for(int i = 0; i < 4; ++i) { gout.PosH = mul(v[i], gViewProj); gout.PosW = v[i].xyz; gout.NormalW = look; gout.TexC = texC[i]; gout.PrimID = primID; triStream.Append(gout); } } float4 PS(GeoOut pin) : SV_Target { float3 uvw = float3(pin.TexC, pin.PrimID%3); float4 diffuseAlbedo = gTreeMapArray.Sample(gsamAnisotropicWrap, uvw) * gDiffuseAlbedo; #ifdef ALPHA_TEST // Discard pixel if texture alpha < 0.1. We do this test as soon // as possible in the shader so that we can potentially exit the // shader early, thereby skipping the rest of the shader code. clip(diffuseAlbedo.a - 0.1f); #endif // Interpolating normal can unnormalize it, so renormalize it. pin.NormalW = normalize(pin.NormalW); // 光线经物体表面一点反射到相机方向的向量 float3 toEyeW = gEyePosW - pin.PosW; float distToEye = length(toEyeW); toEyeW /= distToEye; // normalize // Light terms. float4 ambient = gAmbientLight*diffuseAlbedo; const float shininess = 1.0f - gRoughness; Material mat = { diffuseAlbedo, gFresnelR0, shininess }; float3 shadowFactor = 1.0f; float4 directLight = ComputeLighting(gLights, mat, pin.PosW, pin.NormalW, toEyeW, shadowFactor); float4 litColor = ambient + directLight; #ifdef FOG float fogAmount = saturate((distToEye - gFogStart) / gFogRange); litColor = lerp(litColor, gFogColor, fogAmount); #endif // Common convention to take alpha from diffuse albedo. litColor.a = diffuseAlbedo.a; return litColor; }
可以看到其中有两个未见过的声明,它们分别意为语义和纹理数组:
uint primID : SV_PrimitiveID Texture2DArray gTreeMapArray : register(t0);
-
SV_PrimitiveID语义
-
在VS中使用SV_PrimitiveID语义
在输入装配器阶段会自动为每个图元生成图元ID,假设绘制n个图元,第一个图元标记为0,第二个标记为1······最后一个为n-1
VertexOut VS(VertexIn vin, uint vertID : SV_VertexID)
-
在GS中使用SV_PrimitiveID语义
仅用于将图元ID写入输出的顶点
[maxvertexcount(4)] void GS(point VertexOut gin[1], uint primID : SV_PrimitiveID, inout TriangleStream<GeoOut> triStream)
-
在PS中SV_PrimitiveID语义
PS将图元ID用作纹理数组的索引
float4 PS(VertexOut pin, uint primID : SV_PrimitiveID : SV_Target)
-
-
纹理数组:存放纹理的数组,用接口ID3D12Resource表示
-
创建方式
Texture2DArray gTreeMapArray : register(t0);
那为什么不使用C样式的数组初始化方式呢?
Texture2D TexArray[4];
在着色器5.1模型中可以这样写,但在这之前的版本不可以。而且,该实现方式可能会产生额外开销
-
指定纹理数组存储元素个数:设置DepthOrArraySize属性来指定纹理数组存储元素个数。若是3D纹理,该项指定为深度值
-
对纹理数组采样:使用纹理数组需要三个坐标,前两个为2D纹理坐标,第三个为该纹理的索引
float3 uvw = float3(pin.TexC, pin.PrimID%3); float4 diffuseAlbedo = gTreeMapArray.Sample(gsamAnisotropicWrap, uvw) * gDiffuseAlbedo;
-
好处
-
在一次绘制调用中,可以画出不同纹理的图元。一般而言,必须用不同纹理对每个有着独立渲染项的网格进行处理,但使用纹理数组,便只需调用一次
SetTextureArray(); DrawPrimitivesWithTextureArray();
-
-
加载纹理数组
微软公司提供了加载纹理数组的工具texassemble
-
合并多个.dds图像为一个纹理数组treeArray.dds
texassemble -array -o treeArray.dds t0.dds t1.dds t2.dds t3.dds
-
需要注意的是使用texassemble构建纹理数组时,输入的多个图像只能各有一种mipmap层级,创建后可以通过texconv工具生成多种mipmap层级,并改变纹理格式
texconv -m 10 -f BC3_UNORM treeArray.dds
-
-
纹理子资源(subresource)
下图展示了一个拥有多个纹理的纹理数组,其中每个纹理都有自己的mipmap链。我们用
数组切片(array slice)
表示纹理数组中的某个纹理及其mipmap链,用切片(mip slice)
表示纹理数组中某一层级的所有mipmap,而子资源
是指纹理数组中某纹理的单个mipmap层级,而子资源也是以线性索引进行标记的
D3D也提供了相应的函数来计算子资源的线性索引,给定mip切片索引、数组切片索引、平面切片(对某些YUV平面格式的分量以索引方式进行单独提取,不需要则设为0)索引、mipmap层级、纹理数组大小即可
最后再结合上篇所讲的AlphaToCoverage技术解决森林边缘失真的问题实现该示例
-
-
大致流程
-
加载纹理
auto grassTex = std::make_unique<Texture>(); grassTex->Name = "grassTex"; grassTex->Filename = L"../../Textures/grass.dds"; ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(), mCommandList.Get(), grassTex->Filename.c_str(), grassTex->Resource, grassTex->UploadHeap)); auto waterTex = std::make_unique<Texture>(); waterTex->Name = "waterTex"; waterTex->Filename = L"../../Textures/water1.dds"; ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(), mCommandList.Get(), waterTex->Filename.c_str(), waterTex->Resource, waterTex->UploadHeap)); auto fenceTex = std::make_unique<Texture>(); fenceTex->Name = "fenceTex"; fenceTex->Filename = L"../../Textures/WireFence.dds"; ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(), mCommandList.Get(), fenceTex->Filename.c_str(), fenceTex->Resource, fenceTex->UploadHeap)); auto treeArrayTex = std::make_unique<Texture>(); treeArrayTex->Name = "treeArrayTex"; treeArrayTex->Filename = L"../../Textures/treeArray2.dds"; ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(), mCommandList.Get(), treeArrayTex->Filename.c_str(), treeArrayTex->Resource, treeArrayTex->UploadHeap)); mTextures[grassTex->Name] = std::move(grassTex); mTextures[waterTex->Name] = std::move(waterTex); mTextures[fenceTex->Name] = std::move(fenceTex); mTextures[treeArrayTex->Name] = std::move(treeArrayTex);
-
创建根签名
//四个根参数:一个描述符表,三个描述符 CD3DX12_DESCRIPTOR_RANGE texTable; texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0); // Root parameter can be a table, root descriptor or root constants. CD3DX12_ROOT_PARAMETER slotRootParameter[4]; // Perfomance TIP: Order from most frequent to least frequent. slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL); slotRootParameter[1].InitAsConstantBufferView(0); slotRootParameter[2].InitAsConstantBufferView(1); slotRootParameter[3].InitAsConstantBufferView(2); auto staticSamplers = GetStaticSamplers(); // A root signature is an array of root parameters. CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter, (UINT)staticSamplers.size(), staticSamplers.data(), D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT); // create a root signature with a single slot which points to a descriptor range consisting of a single constant buffer ComPtr<ID3DBlob> serializedRootSig = nullptr; ComPtr<ID3DBlob> errorBlob = nullptr; HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf()); if(errorBlob != nullptr) { ::OutputDebugStringA((char*)errorBlob->GetBufferPointer()); } ThrowIfFailed(hr); ThrowIfFailed(md3dDevice->CreateRootSignature( 0, serializedRootSig->GetBufferPointer(), serializedRootSig->GetBufferSize(), IID_PPV_ARGS(mRootSignature.GetAddressOf())));
-
创建描述符堆
//创建SRV描述符堆,存储四个不同的纹理资源的描述符 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(), &srvDesc, hDescriptor); // next descriptor hDescriptor.Offset(1, mCbvSrvDescriptorSize); srvDesc.Format = waterTex->GetDesc().Format; md3dDevice->CreateShaderResourceView(waterTex.Get(), &srvDesc, hDescriptor); // next descriptor hDescriptor.Offset(1, mCbvSrvDescriptorSize); srvDesc.Format = fenceTex->GetDesc().Format; md3dDevice->CreateShaderResourceView(fenceTex.Get(), &srvDesc, hDescriptor); // next descriptor hDescriptor.Offset(1, mCbvSrvDescriptorSize); auto desc = treeArrayTex->GetDesc(); srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; srvDesc.Format = treeArrayTex->GetDesc().Format; srvDesc.Texture2DArray.MostDetailedMip = 0; srvDesc.Texture2DArray.MipLevels = -1; srvDesc.Texture2DArray.FirstArraySlice = 0; srvDesc.Texture2DArray.ArraySize = treeArrayTex->GetDesc().DepthOrArraySize; md3dDevice->CreateShaderResourceView(treeArrayTex.Get(), &srvDesc, hDescriptor);
-
创建着色器和输入布局
//着色器使用的宏,是否实现雾和alpha test const D3D_SHADER_MACRO defines[] = { "FOG", "1", NULL, NULL }; const D3D_SHADER_MACRO alphaTestDefines[] = { "FOG", "1", "ALPHA_TEST", "1", NULL, NULL }; mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", nullptr, "VS", "vs_5_0"); mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", defines, "PS", "ps_5_0"); //开启雾效 mShaders["alphaTestedPS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", alphaTestDefines, "PS", "ps_5_0"); //开启雾效,alpha test mShaders["treeSpriteVS"] = d3dUtil::CompileShader(L"Shaders\\TreeSprite.hlsl", nullptr, "VS", "vs_5_0"); mShaders["treeSpriteGS"] = d3dUtil::CompileShader(L"Shaders\\TreeSprite.hlsl", nullptr, "GS", "gs_5_0"); mShaders["treeSpritePS"] = d3dUtil::CompileShader(L"Shaders\\TreeSprite.hlsl", alphaTestDefines, "PS", "ps_5_0"); mStdInputLayout = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, }; //用于实现公告板的顶点结构体布局 mTreeSpriteInputLayout = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "SIZE", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, };
-
生成陆地、海洋、立方体
//生成陆地 GeometryGenerator geoGen; GeometryGenerator::MeshData grid = geoGen.CreateGrid(160.0f, 160.0f, 50, 50); // 获取所需要的顶点,并利用高度函数计算每个顶点的高度值 // 顶点的颜色基于高度而定 std::vector<Vertex> vertices(grid.Vertices.size()); for(size_t i = 0; i < grid.Vertices.size(); ++i) { auto& p = grid.Vertices[i].Position; vertices[i].Pos = p; vertices[i].Pos.y = GetHillsHeight(p.x, p.z); vertices[i].Normal = GetHillsNormal(p.x, p.z); vertices[i].TexC = grid.Vertices[i].TexC; } const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex); std::vector<std::uint16_t> indices = grid.GetIndices16(); const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t); auto geo = std::make_unique<MeshGeometry>(); geo->Name = "landGeo"; ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU)); CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize); ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU)); CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize); geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader); geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader); geo->VertexByteStride = sizeof(Vertex); geo->VertexBufferByteSize = vbByteSize; geo->IndexFormat = DXGI_FORMAT_R16_UINT; geo->IndexBufferByteSize = ibByteSize; SubmeshGeometry submesh; submesh.IndexCount = (UINT)indices.size(); submesh.StartIndexLocation = 0; submesh.BaseVertexLocation = 0; geo->DrawArgs["grid"] = submesh; mGeometries["landGeo"] = std::move(geo); //生成海洋 std::vector<std::uint16_t> indices(3 * mWaves->TriangleCount()); // 3 indices per face assert(mWaves->VertexCount() < 0x0000ffff); // Iterate over each quad. int m = mWaves->RowCount(); int n = mWaves->ColumnCount(); int k = 0; for(int i = 0; i < m - 1; ++i) { for(int j = 0; j < n - 1; ++j) { indices[k] = i*n + j; indices[k + 1] = i*n + j + 1; indices[k + 2] = (i + 1)*n + j; indices[k + 3] = (i + 1)*n + j; indices[k + 4] = i*n + j + 1; indices[k + 5] = (i + 1)*n + j + 1; k += 6; // next quad } } UINT vbByteSize = mWaves->VertexCount()*sizeof(Vertex); UINT ibByteSize = (UINT)indices.size()*sizeof(std::uint16_t); auto geo = std::make_unique<MeshGeometry>(); geo->Name = "waterGeo"; // Set dynamically. geo->VertexBufferCPU = nullptr; geo->VertexBufferGPU = nullptr; ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU)); CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize); geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader); geo->VertexByteStride = sizeof(Vertex); geo->VertexBufferByteSize = vbByteSize; geo->IndexFormat = DXGI_FORMAT_R16_UINT; geo->IndexBufferByteSize = ibByteSize; SubmeshGeometry submesh; submesh.IndexCount = (UINT)indices.size(); submesh.StartIndexLocation = 0; submesh.BaseVertexLocation = 0; geo->DrawArgs["grid"] = submesh; mGeometries["waterGeo"] = std::move(geo); //生成立方体 GeometryGenerator geoGen; GeometryGenerator::MeshData box = geoGen.CreateBox(8.0f, 8.0f, 8.0f, 3); std::vector<Vertex> vertices(box.Vertices.size()); for (size_t i = 0; i < box.Vertices.size(); ++i) { auto& p = box.Vertices[i].Position; vertices[i].Pos = p; vertices[i].Normal = box.Vertices[i].Normal; vertices[i].TexC = box.Vertices[i].TexC; } const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex); std::vector<std::uint16_t> indices = box.GetIndices16(); const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t); auto geo = std::make_unique<MeshGeometry>(); geo->Name = "boxGeo"; ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU)); CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize); ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU)); CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize); geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader); geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader); geo->VertexByteStride = sizeof(Vertex); geo->VertexBufferByteSize = vbByteSize; geo->IndexFormat = DXGI_FORMAT_R16_UINT; geo->IndexBufferByteSize = ibByteSize; SubmeshGeometry submesh; submesh.IndexCount = (UINT)indices.size(); submesh.StartIndexLocation = 0; submesh.BaseVertexLocation = 0; geo->DrawArgs["box"] = submesh; mGeometries["boxGeo"] = std::move(geo);
-
生成公告板
//公告板的中心位置、宽高 struct TreeSpriteVertex { XMFLOAT3 Pos; XMFLOAT2 Size; }; static const int treeCount = 16; //树的个数 std::array<TreeSpriteVertex, 16> vertices; for(UINT i = 0; i < treeCount; ++i) { //随机生成公告板的位置 float x = MathHelper::RandF(-45.0f, 45.0f); float z = MathHelper::RandF(-45.0f, 45.0f); float y = GetHillsHeight(x, z); // 绘制的树要比陆地高 y += 8.0f; vertices[i].Pos = XMFLOAT3(x, y, z); vertices[i].Size = XMFLOAT2(20.0f, 20.0f); } std::array<std::uint16_t, 16> indices = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; const UINT vbByteSize = (UINT)vertices.size() * sizeof(TreeSpriteVertex); const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t); auto geo = std::make_unique<MeshGeometry>(); geo->Name = "treeSpritesGeo"; ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU)); CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize); ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU)); CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize); geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader); geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader); geo->VertexByteStride = sizeof(TreeSpriteVertex); geo->VertexBufferByteSize = vbByteSize; geo->IndexFormat = DXGI_FORMAT_R16_UINT; geo->IndexBufferByteSize = ibByteSize; SubmeshGeometry submesh; submesh.IndexCount = (UINT)indices.size(); submesh.StartIndexLocation = 0; submesh.BaseVertexLocation = 0; geo->DrawArgs["points"] = submesh; mGeometries["treeSpritesGeo"] = std::move(geo);
-
定义材质
auto grass = std::make_unique<Material>(); grass->Name = "grass"; grass->MatCBIndex = 0; grass->DiffuseSrvHeapIndex = 0; grass->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); grass->FresnelR0 = XMFLOAT3(0.01f, 0.01f, 0.01f); grass->Roughness = 0.125f; // This is not a good water material definition, but we do not have all the rendering // tools we need (transparency, environment reflection), so we fake it for now. auto water = std::make_unique<Material>(); water->Name = "water"; water->MatCBIndex = 1; water->DiffuseSrvHeapIndex = 1; water->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.5f); water->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f); water->Roughness = 0.0f; auto wirefence = std::make_unique<Material>(); wirefence->Name = "wirefence"; wirefence->MatCBIndex = 2; wirefence->DiffuseSrvHeapIndex = 2; wirefence->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); wirefence->FresnelR0 = XMFLOAT3(0.02f, 0.02f, 0.02f); wirefence->Roughness = 0.25f; auto treeSprites = std::make_unique<Material>(); treeSprites->Name = "treeSprites"; treeSprites->MatCBIndex = 3; treeSprites->DiffuseSrvHeapIndex = 3; treeSprites->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); treeSprites->FresnelR0 = XMFLOAT3(0.01f, 0.01f, 0.01f); treeSprites->Roughness = 0.125f; mMaterials["grass"] = std::move(grass); mMaterials["water"] = std::move(water); mMaterials["wirefence"] = std::move(wirefence); mMaterials["treeSprites"] = std::move(treeSprites);
-
生成渲染项
//每帧渲染需要的数据 auto wavesRitem = std::make_unique<RenderItem>(); wavesRitem->World = MathHelper::Identity4x4(); XMStoreFloat4x4(&wavesRitem->TexTransform, XMMatrixScaling(5.0f, 5.0f, 1.0f)); wavesRitem->ObjCBIndex = 0; wavesRitem->Mat = mMaterials["water"].get(); wavesRitem->Geo = mGeometries["waterGeo"].get(); wavesRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; wavesRitem->IndexCount = wavesRitem->Geo->DrawArgs["grid"].IndexCount; wavesRitem->StartIndexLocation = wavesRitem->Geo->DrawArgs["grid"].StartIndexLocation; wavesRitem->BaseVertexLocation = wavesRitem->Geo->DrawArgs["grid"].BaseVertexLocation; mWavesRitem = wavesRitem.get(); mRitemLayer[(int)RenderLayer::Transparent].push_back(wavesRitem.get()); auto gridRitem = std::make_unique<RenderItem>(); gridRitem->World = MathHelper::Identity4x4(); XMStoreFloat4x4(&gridRitem->TexTransform, XMMatrixScaling(5.0f, 5.0f, 1.0f)); gridRitem->ObjCBIndex = 1; gridRitem->Mat = mMaterials["grass"].get(); gridRitem->Geo = mGeometries["landGeo"].get(); gridRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; gridRitem->IndexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount; gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation; gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation; mRitemLayer[(int)RenderLayer::Opaque].push_back(gridRitem.get()); auto boxRitem = std::make_unique<RenderItem>(); XMStoreFloat4x4(&boxRitem->World, XMMatrixTranslation(3.0f, 2.0f, -9.0f)); boxRitem->ObjCBIndex = 2; boxRitem->Mat = mMaterials["wirefence"].get(); boxRitem->Geo = mGeometries["boxGeo"].get(); boxRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; boxRitem->IndexCount = boxRitem->Geo->DrawArgs["box"].IndexCount; boxRitem->StartIndexLocation = boxRitem->Geo->DrawArgs["box"].StartIndexLocation; boxRitem->BaseVertexLocation = boxRitem->Geo->DrawArgs["box"].BaseVertexLocation; mRitemLayer[(int)RenderLayer::AlphaTested].push_back(boxRitem.get()); auto treeSpritesRitem = std::make_unique<RenderItem>(); treeSpritesRitem->World = MathHelper::Identity4x4(); treeSpritesRitem->ObjCBIndex = 3; treeSpritesRitem->Mat = mMaterials["treeSprites"].get(); treeSpritesRitem->Geo = mGeometries["treeSpritesGeo"].get(); treeSpritesRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; treeSpritesRitem->IndexCount = treeSpritesRitem->Geo->DrawArgs["points"].IndexCount; treeSpritesRitem->StartIndexLocation = treeSpritesRitem->Geo->DrawArgs["points"].StartIndexLocation; treeSpritesRitem->BaseVertexLocation = treeSpritesRitem->Geo->DrawArgs["points"].BaseVertexLocation; mRitemLayer[(int)RenderLayer::AlphaTestedTreeSprites].push_back(treeSpritesRitem.get()); mAllRitems.push_back(std::move(wavesRitem)); mAllRitems.push_back(std::move(gridRitem)); mAllRitems.push_back(std::move(boxRitem)); mAllRitems.push_back(std::move(treeSpritesRitem));
-
生成帧资源
void TreeBillboardsApp::BuildFrameResources() { for(int i = 0; i < gNumFrameResources; ++i) { mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(), 1, (UINT)mAllRitems.size(), (UINT)mMaterials.size(), mWaves->VertexCount())); } }
reference
Directx12 3D 游戏开发实战
标签:std,1.0,DX,几何,indices,顶点,geo,着色器 From: https://www.cnblogs.com/chenglixue/p/17166168.html