PNPatches学习
2013/5/1 Simon&Lee
理论学习
通常,为了在游戏中更加丰富模型细节,AMD提出了一种Tessellation的网格拆分技术。经过若干年的发展,微软在DirectX11中引入了这一技术。在基于DirectX11的渲染管线中,增加了Hull-Shader Stage、Tessellator Stage和Domain-Shader Stage三个阶段。
Hull-Shader Stage阶段
Hull-ShaderStage是一个可编程管线阶段,通过对输入的控制点进行计算,生成输出控制点和一些常量数据供tessellation阶段和demain shader阶段使用。
Tessellator Stage阶段
TessellatorStage是一个固定管线阶段,对于开发者一般不需要太多关注该阶段。该阶段主要是根据hull shader阶段的输出数据进行网格拆分,并将细分数据输出到domain shader阶段,以便进行后处理。
Domain-Shader Stage阶段
Domain-ShaderStage也是一个可编程管线阶段,主要是根据tessellator阶段的输出数据和hull shader阶段的ConstantData进行计算,将最终的顶点数据传给PixelShader阶段。
实现细节
1. C++部分代码
(1)Load资源
首先,读取法线纹理和位移纹理作为网格细分资源。
ID3D11ShaderResourceView*pDisplacementTextureSRV = NULL;
ID3D11ShaderResourceView*pNormalTextureSRV = NULL;
// Try toload displacement and normal maps from cache
{
D3DX11_IMAGE_INFO imageInfo;
ID3D11Texture2D*pDisplacementTexture = NULL;
ID3D11Texture2D*pNormalTexture = NULL;
WCHAR path[1024];
if(DXUTFindDXSDKMediaFileCch(path, MAX_PATH, L"PNPatches/cache/displacement_map.dds") == S_OK)
{
if (D3DX11GetImageInfoFromFile(path, NULL,&imageInfo, NULL) == S_OK)
{
D3DX11_IMAGE_LOAD_INFOimageLoadInfo;
memset(&imageLoadInfo, 0, sizeof(imageLoadInfo));
imageLoadInfo.Width = imageInfo.Width;
imageLoadInfo.Height = imageInfo.Height;
imageLoadInfo.MipLevels = 1;
imageLoadInfo.Format = imageInfo.Format;
imageLoadInfo.BindFlags = D3D11_BIND_SHADER_RESOURCE;
V_RETURN(D3DX11CreateTextureFromFile(pDevice, path,&imageLoadInfo, NULL, (ID3D11Resource**)&pDisplacementTexture, NULL));
D3D11_SHADER_RESOURCE_VIEW_DESC descSRV;
memset(&descSRV, 0, sizeof(descSRV));
descSRV.Format = imageInfo.Format;
descSRV.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
descSRV.Texture2D.MipLevels = 1;
pDevice->CreateShaderResourceView(pDisplacementTexture,&descSRV, &pDisplacementTextureSRV);
pDisplacementTexture->Release();
}
}
if(DXUTFindDXSDKMediaFileCch(path, MAX_PATH, L"PNPatches/cache/normal_map.dds") == S_OK)
{
if (D3DX11GetImageInfoFromFile(path, NULL,&imageInfo, NULL) == S_OK)
{
D3DX11_IMAGE_LOAD_INFOimageLoadInfo;
memset(&imageLoadInfo, 0, sizeof(imageLoadInfo));
imageLoadInfo.Width = imageInfo.Width;
imageLoadInfo.Height = imageInfo.Height;
imageLoadInfo.MipLevels = 1;
imageLoadInfo.Format = imageInfo.Format;
imageLoadInfo.BindFlags = D3D11_BIND_SHADER_RESOURCE;
V_RETURN(D3DX11CreateTextureFromFile(pDevice, path,&imageLoadInfo, NULL, (ID3D11Resource**)&pNormalTexture, NULL));
D3D11_SHADER_RESOURCE_VIEW_DESC descSRV;
memset(&descSRV, 0, sizeof(descSRV));
descSRV.Format = imageInfo.Format;
descSRV.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
descSRV.Texture2D.MipLevels = 1;
pDevice->CreateShaderResourceView(pNormalTexture,&descSRV, &pNormalTextureSRV);
pNormalTexture->Release();
}
}
}
(2)Render函数
然后,提交Shader寄存器数据,并进行渲染。
void MyMesh::RenderAnimated(ID3D11DeviceContext *pd3dDeviceContext, D3DXMATRIX& mView, D3DXMATRIX& mProj, D3DXVECTOR3& cameraPos, float time)
{
UpdateView(mView, mProj, cameraPos);
ID3DX11EffectPass*pOverridePass = NULL;
...
// Constants
D3DXMATRIX mViewProj =mView * mProj;
g_pEffect->GetVariableByName("g_ModelViewProjectionMatrix")->AsMatrix()->SetMatrix(mViewProj);
g_pEffect->GetVariableByName("g_CameraPosition")->AsVector()->SetFloatVector(cameraPos);
g_pEffect->GetVariableByName("g_DisplacementScale")->AsScalar()->SetFloat(m_DisplacementScale);
// Buffers& resources
g_pEffect->GetVariableByName("g_PositionsBuffer")->AsShaderResource()->SetResource(g_pMeshVertexBufferSRV);
g_pEffect->GetVariableByName("g_CoordinatesBuffer")->AsShaderResource()->SetResource(g_pMeshCoordinatesBufferSRV);
g_pEffect->GetVariableByName("g_IndicesBuffer")->AsShaderResource()->SetResource(g_pMeshTrgsIndexBufferSRV);
g_pEffect->GetVariableByName("g_NormalsBuffer")->AsShaderResource()->SetResource(g_pMeshNormalsBufferSRV);
g_pEffect->GetVariableByName("g_TangentsBuffer")->AsShaderResource()->SetResource(g_pMeshTangentsBufferSRV);
g_pEffect->GetVariableByName("g_CornerCoordinatesBuffer")->AsShaderResource()->SetResource(g_pMeshCornerCoordinatesBufferSRV);
g_pEffect->GetVariableByName("g_EdgeCoordinatesBuffer")->AsShaderResource()->SetResource(g_pMeshTriEdgeCoordinatesBufferSRV);
g_pEffect->GetVariableByName("g_DisplacementTexture")->AsShaderResource()->SetResource(g_pDisplacementMapSRV);
g_pEffect->GetVariableByName("g_WSNormalMap")->AsShaderResource()->SetResource(g_pNormalMapSRV);
// Animationstuff
int bonesNum =m_pCharacterAnimation->GetBonesNum();
D3DXMATRIX* pFrame =m_pCharacterAnimation->GetFrame(time);
g_pEffect->GetVariableByName("mAnimationMatrixArray")->AsMatrix()->SetMatrixArray((float*)pFrame, 0,bonesNum);
g_pEffect->GetVariableByName("g_BoneIndicesBuffer")->AsShaderResource()->SetResource(m_pBoneIndicesBufferSRV);
g_pEffect->GetVariableByName("g_BoneWeightsBuffer")->AsShaderResource()->SetResource(m_pBoneWeightsBufferSRV);
if(m_TessellationFactor != 1.0f)
{
g_pEffect->GetVariableByName("g_TessellationFactor")->AsScalar()->SetFloat(m_TessellationFactor* g_TessellationFactorResScale);
pd3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);
g_pEffect->GetTechniqueByName("RenderTessellatedTrianglesAnimated")->GetPassByIndex(0)->Apply(0,pd3dDeviceContext);
}
else
{
g_pEffect->GetVariableByName("g_TessellationFactor")->AsScalar()->SetFloat(1.0f);
pd3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
g_pEffect->GetTechniqueByName("RenderTrianglesAnimated")->GetPassByIndex(0)->Apply(0,pd3dDeviceContext);
}
if(pOverridePass) pOverridePass->Apply(0, pd3dDeviceContext);
pd3dDeviceContext->Draw(g_MeshTrgIndicesNum,0);
...
}
2. Shader部分代码
(1)Technique和pass设置
technique11 RenderTessellatedTrianglesAnimated
{
pass p0
{
SetRasterizerState(CullBackMS);
SetDepthStencilState(DepthNormal, 0);
SetBlendState(NoBlending, float4(0.0f, 0.0f,0.0f, 0.0f), 0xFFFFFFFF);
SetVertexShader(CompileShader(vs_4_0, RenderTessellatedDiffuseVS(true)));
SetHullShader(CompileShader(hs_5_0, DiffuseHS()));
SetDomainShader(CompileShader(ds_5_0, DiffuseDS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_4_0, RenderTessellatedDiffusePS()));
}
}
(2)VertexShader
HSIn_Diffuse RenderTessellatedDiffuseVS(uint vertexID : SV_VertexID, uniform bool renderAnimated = false)
{
HSIn_Diffuse output;
int2 indices = g_IndicesBuffer.Load(vertexID);
output.position = g_PositionsBuffer.Load(indices.x).xyz;
output.texCoord = g_CoordinatesBuffer.Load(indices.xy);
float4 normalData = g_NormalsBuffer.Load(indices.x);
output.normal = normalData.xyz;
float4 tangentData = g_TangentsBuffer.Load(indices.x);
output.tangent = tangentData.xyz;
if (renderAnimated)
{
int4 boneIndices = g_BoneIndicesBuffer.Load(indices.x);
float4 boneWeights = g_BoneWeightsBuffer.Load(indices.x);
float4x4 combinedAnimMatrix = 0;
[unroll]
for (int iBone=0; iBone<4; ++iBone)
{
combinedAnimMatrix += mAnimationMatrixArray[boneIndices[iBone]] * boneWeights[iBone];
}
float3 animatedPosition = mul(float4(output.position, 1), combinedAnimMatrix).xyz;
output.position = mul(animatedPosition, MAnimatedScale);
float3x3 rotationMatrix = (float3x3)combinedAnimMatrix;
float3 animatedNormal = mul(float4(output.normal, 1), rotationMatrix).xyz;
output.normal = normalize(mul(animatedNormal, MAnimatedScale));
float3 animatedTangent = mul(float4(output.tangent, 1), rotationMatrix).xyz;
output.tangent = normalize(mul(animatedTangent, MAnimatedScale));
}
#ifdef FIX_THE_SEAMS
output.cornerCoord = g_CornerCoordinatesBuffer.Load(indices.x);
output.edgeCoord = g_EdgeCoordinatesBuffer.Load(vertexID);
#endif
return output;
}
(3)HullShader
struct HS_CONSTANT_DATA_OUTPUT
{
float Edges[3] : SV_TessFactor;
float Inside : SV_InsideTessFactor;
#ifdef PN_TRIANGLES
// Geometrycubic generated control points
float3 f3B210 : POSITION3;
float3 f3B120 : POSITION4;
float3 f3B021 : POSITION5;
float3 f3B012 : POSITION6;
float3 f3B102 : POSITION7;
float3 f3B201 : POSITION8;
float3 f3B111 : CENTER;
#endif
float sign : SIGN;
};
[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("DiffuseConstantHS")]
[maxtessfactor(64.0)]
HSIn_Diffuse DiffuseHS( InputPatch<HSIn_Diffuse, 3> inputPatch, uint i : SV_OutputControlPointID)
{
return inputPatch[i];
}
HS_CONSTANT_DATA_OUTPUT DiffuseConstantHS( InputPatch<HSIn_Diffuse, 3> inputPatch)
{
HS_CONSTANT_DATA_OUTPUT output;
//tessellation factors are proportional to model space edge length
for (uint ie = 0; ie < 3; ++ie)
{
#ifdef MESH_CONSTANT_LOD
output.Edges[ie] = g_TessellationFactor / (float)512 * (float)64;
#else
float3 edge = inputPatch[(ie + 1) % 3].position - inputPatch[ie].position;
float3 vec = (inputPatch[(ie + 1) % 3].position + inputPatch[ie].position) / 2 - g_FrustumOrigin;
float len = sqrt(dot(edge, edge) / dot(vec, vec));
output.Edges[(ie + 1) % 3] =max(1,len* g_TessellationFactor);
#endif
}
// culling
int culled[4];
for (int ip = 0; ip < 4; ++ip)
{
culled[ip] = 1;
culled[ip] &= dot(inputPatch[0].position - g_FrustumOrigin, g_FrustumNormals[ip].xyz) > 0;
culled[ip] &= dot(inputPatch[1].position - g_FrustumOrigin, g_FrustumNormals[ip].xyz) > 0;
culled[ip] &= dot(inputPatch[2].position - g_FrustumOrigin, g_FrustumNormals[ip].xyz) > 0;
}
if (culled[0] || culled[1] || culled[2] || culled[3]) output.Edges[0] = 0;
#ifdef PN_TRIANGLES
// computethe cubic geometry control points
// edgecontrol points
output.f3B210 = ( ( 2.0f* inputPatch[0].position ) + inputPatch[1].position - ( dot( ( inputPatch[1].position - inputPatch[0].position ), inputPatch[0].normal ) * inputPatch[0].normal ) ) / 3.0f;
output.f3B120 = ( ( 2.0f* inputPatch[1].position ) + inputPatch[0].position - ( dot( ( inputPatch[0].position - inputPatch[1].position ), inputPatch[1].normal ) * inputPatch[1].normal ) ) / 3.0f;
output.f3B021 = ( ( 2.0f* inputPatch[1].position ) + inputPatch[2].position - ( dot( ( inputPatch[2].position - inputPatch[1].position ), inputPatch[1].normal ) * inputPatch[1].normal ) ) / 3.0f;
output.f3B012 = ( ( 2.0f* inputPatch[2].position ) + inputPatch[1].position - ( dot( ( inputPatch[1].position - inputPatch[2].position ), inputPatch[2].normal ) * inputPatch[2].normal ) ) / 3.0f;
output.f3B102 = ( ( 2.0f* inputPatch[2].position ) + inputPatch[0].position - ( dot( ( inputPatch[0].position - inputPatch[2].position ), inputPatch[2].normal ) * inputPatch[2].normal ) ) / 3.0f;
output.f3B201 = ( ( 2.0f* inputPatch[0].position ) + inputPatch[2].position - ( dot( ( inputPatch[2].position - inputPatch[0].position ), inputPatch[0].normal ) * inputPatch[0].normal ) ) / 3.0f;
// centercontrol point
float3 f3E = ( output.f3B210 + output.f3B120 + output.f3B021 + output.f3B012 + output.f3B102 + output.f3B201 ) / 6.0f;
float3 f3V = ( inputPatch[0].position + inputPatch[1].position + inputPatch[2].position ) / 3.0f;
output.f3B111 = f3E + ( ( f3E - f3V ) / 2.0f );
#endif
output.Inside = (output.Edges[0] + output.Edges[1] + output.Edges[2]) / 3;
float2 t01 = inputPatch[1].texCoord - inputPatch[0].texCoord;
float2 t02 = inputPatch[2].texCoord - inputPatch[0].texCoord;
output.sign = t01.x * t02.y - t01.y * t02.x > 0.0f ?1 : -1;
return output;
}
(4)DomainShader
[domain("tri")]
PSIn_TessellatedDiffuse DiffuseDS( HS_CONSTANT_DATA_OUTPUT input,
float3 barycentricCoords : SV_DomainLocation,
OutputPatch<HSIn_Diffuse, 3> inputPatch )
{
PSIn_TessellatedDiffuse output;
float3 coordinates = barycentricCoords;
// Thebarycentric coordinates
float fU = barycentricCoords.x;
float fV = barycentricCoords.y;
float fW = barycentricCoords.z;
//Precompute squares and squares * 3
float fUU = fU * fU;
float fVV = fV * fV;
float fWW = fW * fW;
float fUU3 = fUU * 3.0f;
float fVV3 = fVV * 3.0f;
float fWW3 = fWW * 3.0f;
// Computeposition from cubic control points and barycentric coords
float3 position = inputPatch[0].position * fWW * fW + inputPatch[1].position * fUU * fU + inputPatch[2].position * fVV * fV +
input.f3B210 * fWW3 * fU + input.f3B120 * fW * fUU3 + input.f3B201 * fWW3 * fV + input.f3B021 * fUU3 * fV +
input.f3B102 * fW * fVV3 + input.f3B012 * fU * fVV3 + input.f3B111 * 6.0f * fW * fU * fV;
// Computenormal from quadratic control points and barycentric coords
float3 normal = inputPatch[0].normal * coordinates.z + inputPatch[1].normal * coordinates.x + inputPatch[2].normal * coordinates.y;
normal = normalize(normal);
float2 texCoord = inputPatch[0].texCoord * coordinates.z + inputPatch[1].texCoord * coordinates.x + inputPatch[2].texCoord * coordinates.y;
float2 displacementTexCoord = texCoord;
#ifdef FIX_THE_SEAMS
// Edgepoint
if(coordinates.z == 0)
displacementTexCoord = lerp(inputPatch[1].edgeCoord.xy, inputPatch[1].edgeCoord.zw, coordinates.y);
else if(coordinates.x == 0)
displacementTexCoord = lerp(inputPatch[2].edgeCoord.xy, inputPatch[2].edgeCoord.zw, coordinates.z);
else if(coordinates.y == 0)
displacementTexCoord = lerp(inputPatch[0].edgeCoord.xy, inputPatch[0].edgeCoord.zw, coordinates.x);
// Cornerpoint
if(coordinates.z == 1)
displacementTexCoord = inputPatch[0].cornerCoord;
else if(coordinates.x == 1)
displacementTexCoord = inputPatch[1].cornerCoord;
else if(coordinates.y == 1)
displacementTexCoord = inputPatch[2].cornerCoord;
#endif
#ifndef IGNORE_DISPLACEMENT
float offset = g_DisplacementTexture.SampleLevel(SamplerLinearClamp, displacementTexCoord, 0).x;
position += normal * offset;
#endif
float3 tangent = inputPatch[0].tangent * coordinates.z + inputPatch[1].tangent * coordinates.x + inputPatch[2].tangent * coordinates.y;
tangent = normalize(tangent);
output.position = mul(float4(position, 1.0f), g_ModelViewProjectionMatrix);
#ifdef SMOOTH_TCOORDS
output.texCoord = displacementTexCoord;
#else
output.texCoord = texCoord;
#endif
output.positionWS = position;
output.normal = normal;
output.tangent = tangent;
output.sign = input.sign;//inputPatch[0].sign;
return output;
}
(5)PixelShader
float4 RenderTessellatedDiffusePS(PSIn_TessellatedDiffuse input) : SV_Target
{
#ifdef FLAT_NORMAL
float3 dir_x = ddx(input.positionWS);
float3 dir_y = ddy(input.positionWS);
float3 normal = normalize(cross(dir_x, dir_y));
float3 lightDir = normalize(g_CameraPosition - input.positionWS);
#else
float3 normal = normalize(input.normal);
float3 tangent = normalize(input.tangent);
float3 bitangent = cross(normal, tangent) * input.sign;
float3x3 tangentBasis = float3x3(tangent, bitangent, normal);
float3 lightDir = normalize(g_CameraPosition - input.positionWS);
lightDir = normalize(mul(tangentBasis, lightDir));
normal = normalize(g_WSNormalMap.Sample(SamplerLinearClamp, input.texCoord).xyz);
#endif
float dotNL = max(dot(normal, lightDir), 0.0f);
float diffuse = dotNL * 0.75f +0.25f;
float specular = pow(dotNL, 100.0f);
float3 diffuseColor = float3(1.0, 0.5,0.35) * 0.75;//* 0.75 + (input.sign * 0.5 + 0.5) * 0.25;
float3 color = diffuseColor * diffuse + specular * 0.25;
return float4(color, 0);
}
3. 实时渲染截图
上图为初始的模型,下图为根据法线贴图和位移贴图进行细分得到的模型。
(1)正常渲染模式
(2)网格渲染模式