作者:哲纸 | 来源:互联网 | 2024-12-08 19:19
本文通过实现一个基于几何着色器(GeometryShader)的Billboard案例,深入探讨DirectX11中几何着色器的功能与应用。文章详细介绍了如何在顶点着色器中处理顶点数据,并在几何着色器中生成面向摄像机的四边形。
本文将通过一个具体的案例——基于几何着色器(GS)的Billboard实现,来深入了解DirectX 11中几何着色器的应用。Billboard是一种常用的图形技术,用于创建始终面向摄像机的对象,如树木或标志牌等。
在顶点着色器(VS)中,我们处理的是单个顶点的数据,包括位置和属性,输出同样为顶点的位置和属性。而几何着色器(GS)则接收整个基本图元(如点、线、三角形等),并可以生成新的顶点、顶点属性和图元信息。
例如,对于一个三角形,顶点着色器会分别处理每个顶点,而在几何着色器中,整个三角形作为输入,输出可以是一个包含多个三角形的复杂形状。通过这种方式,几何着色器能够实现更为复杂的图形变换和生成。
在实现Billboard时,我们使用一个点来表示Billboard的中心,通过一个参数控制其宽度和高度。几何着色器接收这个点作为输入,生成四个顶点及其纹理坐标,形成两个三角形来表示Billboard。此外,还会计算一个转换矩阵,确保Billboard始终面向摄像机。
以下是顶点着色器(VS)的代码示例,它主要负责将输入的顶点数据直接传递给几何着色器,不做额外处理:
GeometryInputType treeVertexShader(VertexInputType input)
{
GeometryInputType output;
output.centerW = input.centerW;
output.sizeW = input.sizeW;
return output;
}
接下来是几何着色器(GS)的关键部分,它负责生成面向摄像机的四边形,并计算纹理坐标:
[maxvertexcount(4)]
void treeGeometryShader(point GeometryInputType gIn[1], uint primID : SV_PrimitiveID, inout TriangleStream triStream)
{
// 转换中心点到世界坐标系
gIn[0].centerW = mul(gIn[0].centerW, (float3x3)worldMatrix);
// 计算Billboard的四个顶点
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);
// 计算使Billboard面向摄像机的世界矩阵
float3 up = float3(0.0f, 1.0f, 0.0f);
float3 look = cameraPosition.xyz - gIn[0].centerW;
look.y = 0.0f;
look = normalize(look);
float3 right = cross(up, look);
matrix W;
matrix gViewProj;
gViewProj = mul(viewMatrix, projectionMatrix);
W[0] = float4(right, 0.0f);
W[1] = float4(up, 0.0f);
W[2] = float4(look, 0.0f);
W[3] = float4(gIn[0].centerW, 1.0f);
matrix WVP = mul(W, gViewProj);
PixelInputType gOut;
[unroll]
for (int i = 0; i <4; ++i)
{
gOut.posH = mul(v[i], WVP);
gOut.posW = mul(v[i], W);
gOut.normalW = look;
gOut.texC = texC[i];
gOut.primID = primID; // 基本图元ID
triStream.Append(gOut);
}
}
像素着色器(PS)负责采样纹理并输出最终的颜色值。如果纹理的Alpha值低于0.25,则丢弃该像素:
float4 treePixelShader(PixelInputType pIn) : SV_Target
{
float4 diffuse = shaderTexture.Sample(SampleType, pIn.texC);
clip(diffuse.a - 0.25f);
return diffuse;
}
为了支持多个树的渲染,我们在像素着色器中引入了一个纹理数组,根据基本图元ID选择不同的纹理。这样可以在同一场景中使用多种不同的树纹理:
Texture2D shaderTexture[4];
SamplerState SampleType;
struct PixelInputType
{
float4 posH : SV_POSITION;
float3 posW : POSITION;
float3 normalW : NORMAL;
float2 texC : TEXCOORD;
uint primID : SV_PrimitiveID;
};
float4 treePixelShader(PixelInputType pIn) : SV_Target
{
int i = pIn.primID % 4;
float4 diffuse;
if (i == 0)
diffuse = shaderTexture[0].Sample(SampleType, pIn.texC);
else if (i == 1)
diffuse = shaderTexture[1].Sample(SampleType, pIn.texC);
else if (i == 2)
diffuse = shaderTexture[2].Sample(SampleType, pIn.texC);
else
diffuse = shaderTexture[3].Sample(SampleType, pIn.texC);
clip(diffuse.a - 0.25f);
return diffuse;
}
为了确保其他着色器不会误用几何着色器,需要在渲染时显式地禁用几何着色器:
deviceContext->GSSetShader(NULL, NULL, 0);
完整的项目文件和源代码可以在这里下载:http://files.cnblogs.com/mikewolf2002/d3d1139-49.zip 和 http://files.cnblogs.com/mikewolf2002/pictures.zip。