要求
实现一个LOD(Level Of Detail)效果,实现物体距视口不同距离来呈现不同的图元细节。
思路
因为需要对图元进行一个分割,所以采用在几何着色器中进行。
- 根据距离设置细分次数subdivCnt。
- 根据三角形图元的三个顶点和细分次数,分割图元并将分割后顶点加入三角形输出流中。
这里主要对第二步进行细述:
首先,如果细分次数为0,则不用细分,输出到三角形流中的三个顶点直接是原始三顶点。
第二,如果细分次数为1,则需要在三角形三边寻找中点,并构成4个三角形,在按照三角形带的方式指定输出顺序放入输出流中。
第三,如果细分次数为2,则需要在细分1次的基础上,在4个三角形中再次细分,分出6个三角形并同样按照指定顺序放入输出流中。
所以,这里采用矩阵记录顶点,每次划分即对每两个顶点之间的边求取中间点,并将中间点放入两顶点之间的矩阵位置的方式来进行划分。具体来看,对于每个顶点,都需要与其上顶点、右顶点、右下方顶点进行上述操作。
代码
//***************************************************************************************
// TreeSprite.hlsl by Frank Luna (C) 2015 All Rights Reserved.
//***************************************************************************************
// Defaults for number of lights.
#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 structures and functions for lighting.
#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);
// Constant data that varies per frame.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gWorldInvTranspose;
float4x4 gTexTransform;
};
// Constant data that varies per material.
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 PosL : POSITION;
float3 NormalL : NORMAL;
float2 TexC : TEXCOORD;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float2 TexC : TEXCOORD;
};
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;
// Just pass data over to geometry shader.
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
vout.PosH = mul(posW, gViewProj);
vout.PosL = vin.PosL;
vout.NormalL = vin.NormalL;
vout.TexC = vin.TexC;
return vout;
}
VertexOut MidPoint(VertexOut lhs, VertexOut rhs)
{
lhs.PosL = normalize(lhs.PosL);
rhs.PosL = normalize(rhs.PosL);
VertexOut m;
m.PosL = 0.5f * (lhs.PosL + rhs.PosL);
m.PosL = normalize(m.PosL);
m.NormalL = m.PosL;
m.TexC = 0.5f * (lhs.TexC + rhs.TexC);
return m;
}
GeoOut ToGeoOut(VertexOut inVert, uint primID)
{
GeoOut gout;
// 将顶点变换到世界空间
gout.PosW = mul(float4(inVert.PosL, 1.0f), gWorld).xyz;
gout.NormalW = mul(inVert.NormalL, (float3x3)gWorldInvTranspose);
// 把顶点变换到齐次裁剪空间
gout.PosH = mul(float4(gout.PosW, 1.0f), gViewProj);
gout.TexC = inVert.TexC;
gout.PrimID = primID;
return gout;
}
void SubdivideAndOutput(VertexOut inVerts[3], int subCnt, inout TriangleStream<GeoOut> triStream, uint primID)
{
VertexOut m[10][10], t[10][10];
int row = 2, col = 2, i = 0, j = 0;
m[0][0] = inVerts[0];
m[1][0] = inVerts[1];
m[0][1] = inVerts[2];
while (subCnt--)
{
for (i = 0; i < row; ++i)
{
int cur_col = col - i; // 当前行的列数
for (j = 0; j < cur_col; ++j)
{
int targetRow = 2 * i, targetCol = 2 * j;
t[targetRow][targetCol] = m[i][j];
if (j < cur_col - 1)
{
// 向上寻找中点
t[targetRow + 1][targetCol] = MidPoint(m[i][j], m[i + 1][j]);
// 向右寻找中点
t[targetRow][targetCol + 1] = MidPoint(m[i][j], m[i][j + 1]);
}
if (i > 0)
{
// 向斜下方寻找中点
t[targetRow - 1][targetCol + 1] = MidPoint(m[i][j], m[i - 1][j + 1]);
}
}
}
row = 2 * row - 1;
col = 2 * col - 1;
// 拷贝t->m
for (i = 0; i < row; ++i)
for (j = 0; j < col - i; ++j)
m[i][j] = t[i][j];
}
// 将顶点按三角形带加入输出流
for (i = 0; i < row; ++i)
{
int cur_col = col - i;
for (j = 0; j < cur_col - 1; ++j)
{
triStream.Append(ToGeoOut(m[i][j], primID));
triStream.Append(ToGeoOut(m[i + 1][j], primID));
}
if (i + 1 != row)
triStream.Append(ToGeoOut(m[i][cur_col - 1], primID));
triStream.RestartStrip(); // 记录完一行三角形,重新开始记录下一行
}
}
[maxvertexcount(8)]
void GS(triangle VertexOut gin[3], uint primID : SV_PrimitiveID, inout TriangleStream<GeoOut> triStream)
{
float3 posW = float3(gWorld._m30, gWorld._m31, gWorld._m32);
float dis = distance(posW, gEyePosW);
int subCnt = 0;
if (dis < 15)
subCnt = 2;
else if (dis < 30)
subCnt = 1;
else
subCnt = 0;
SubdivideAndOutput(gin, subCnt, triStream, primID);
}
float4 PS(VertexOut pin) : SV_Target
{
return float4(1.0f, 1.0f, 1.0f, 1.0f);
}
问题
在细分2次时,结果不正确,产生了如下情况,推测第二、三层的节点没加入或三角形带绘制出错。