文章

视锥体剔除

视锥体剔除

[TOC2]

ComputeShader

视锥体剔除

ComputeShader

主函数编译指定

1
2
3
4
5
6
7
8
9
10
//定义线程编译的函数名称
#pragma kernel FrustumCulling

//该线程的主函数
[numthreads(64,1,1)]
void FrustumCulling(uint3 id : SV_DispatchThreadID)
{
    if(id.x>=inputcount) return;
	...
}

需定义字段

  • 输入的StructureBuffer(需要过滤的数据)
  • 输入的StructureBuffer中的对象数量(在ComputeShader中只用于判断当分配的线程数量大于对象数量时进行return)
  • 其他所需数据(过滤计算需要的数据)
  • 输出的AppendStructureBuffer(过滤后的数据)
1
2
3
4
5
StructureBuffer<float4x4> Input; uint inputCount;
float4 cameraPlanes[6];
float3 boxCenter;
float3 boxExtents;
AppendStructureBuffer Output;

计算相关的函数

!!! note out 修饰符 在hlsl中, 定义函数时, 参数有out修饰符的, 在调用时, 无需再标识out修饰符. 如下方的”通过输入的boxCenter和boxExtents, 得到8个顶点”, 定义的out float4 boundVerts[8], 在使用时, 是无需写入out修饰符的.

  • 判断一个点是否在某个平面外
1
2
3
4
5
6
7
/**
 * \brief 判断一个点是否在平面外(法线正向)
 * \param plane 平面
 * \param pointPos 点
 * \return 是否在平面外
 */
bool IsOutThePlane(float4 plane, float3 pointPos){ return dot(plane.xyz, pointPos) + plane.w > 0;}
  • 通过输入的boxCenter和boxExtents, 得到8个顶点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * \brief 将包围盒的8个顶点通过各自的localToWorldMatrix转换到世界坐标.
 * \param boxCenter 包围盒中心
 * \param boxExtents 包围盒扩展
 * \param instance Local2World矩阵
 * \param boundVerts 包围盒8个顶点
 */
void boundVerts_Local2World(float3 boxCenter, float3 boxExtents, float4x4 instance, out float4 boundVerts[8])
{
    // instance实际上是localToWorldMatrix.
    // Matrix4x4 localToWorldMatrix = Matrix4x4.TRS(position, quaternion, scale)

    float3 boundMin = boxCenter - boxExtents;
    float3 boundMax = boxCenter + boxExtents;

    boundVerts[0] = mul(instance, float4(boundMin, 1.0f)); 
    boundVerts[1] = mul(instance, float4(boundMax, 1.0f));
    boundVerts[2] = mul(instance, float4(boundMax.x, boundMax.y, boundMin.z, 1.0f));
    boundVerts[3] = mul(instance, float4(boundMax.x, boundMin.y, boundMax.z, 1.0f));
    boundVerts[4] = mul(instance, float4(boundMax.x, boundMin.y, boundMin.z, 1.0f));
    boundVerts[5] = mul(instance, float4(boundMin.x, boundMax.y, boundMax.z, 1.0f));
    boundVerts[6] = mul(instance, float4(boundMin.x, boundMax.y, boundMin.z, 1.0f));
    boundVerts[7] = mul(instance, float4(boundMin.x, boundMin.y, boundMax.z, 1.0f));
}
  • 判断包围盒的8个顶点各自是否都在视锥体的6个平面外
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * \brief 计算输入的顶点每一个是否都在摄像机6个面之外
 * \param boundVerts 包围盒的8个顶点
 * \param cameraPlanes 摄像机视锥体的6个面
 * \return 顶点全在视锥体的面之外?
 */
bool IsCulled(float4 boundVerts[8], float4 cameraPlanes[6])
{
    for (int i=0;i<6;i++)
    {
        for (int j=0;j<8;j++)
        {
            if(!IsOutThePlane(cameraPlanes[i],boundVerts[j].xyz)) break;
            if(j==7) return true;
        }
    }
    return false;
}
  • 注: 由于摄像机的6个面只需要计算一次, 这部分不需要走ComputeShader, 所以在C#中完成后输入即可.

线程主函数

  • 判断分配线程数是否超过对象数量, 超过即return
  • 定义并获取当前象的LocalToWorld矩阵
  • 定义顶点组
  • 将顶点通过LocalToWorld矩阵转换到世界坐标
  • 判断是否当前的矩阵需要加入输出的AppendStructureBuffer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[numthreads(64,1,1)]
void FrustumCulling (uint3 id : SV_DispatchThreadID)
{
    if (id.x>=inputCount) return;//判断分配线程数是否超过对象数量, 超过即return
        
    float4x4 instance = Input[id.x];//定义并获取当前象的LocalToWorld矩阵

    float4 boundVerts[8];//定义顶点组
    boundVerts_Local2World(boxCenter,boxExtents,instance,boundVerts);//将顶点通过LocalToWorld矩阵转换到世界坐标

    if (!IsCulled(boundVerts,cameraPlanes))//判断是否当前的矩阵需要加入输出的AppendStructureBuffer
    {
        VisibleBuffer.Append(instance);
    }
}

完整的ComputeShader代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel FrustumCulling

StructuredBuffer<float4x4> Input;
uint inputCount;
float4 cameraPlanes[6];

float3 boxCenter;
float3 boxExtents;

AppendStructuredBuffer<float4x4> VisibleBuffer;

/**
 * \brief 判断一个点是否在平面外(法线正向)
 * \param plane 平面
 * \param pointPos 点
 * \return 是否在平面外
 */
bool IsOutThePlane(float4 plane, float3 pointPos){ return dot(plane.xyz, pointPos) + plane.w > 0;}

/**
 * \brief 将包围盒的8个顶点通过各自的LocalToWorldMatrix转换到世界坐标.
 * \param boxCenter 包围盒中心
 * \param boxExtents 包围盒扩展
 * \param instance Local2World矩阵
 * \param boundVerts 包围盒8个顶点
 */
void boundVerts_Local2World(float3 boxCenter, float3 boxExtents, float4x4 instance, out float4 boundVerts[8])
{
    // instance实际上是localToWorldMatrix.
    // Matrix4x4 localToWorldMatrix = Matrix4x4.TRS(position, quaternion, scale)

    float3 boundMin = boxCenter - boxExtents;
    float3 boundMax = boxCenter + boxExtents;

    boundVerts[0] = mul(instance, float4(boundMin, 1.0f)); 
    boundVerts[1] = mul(instance, float4(boundMax, 1.0f));
    boundVerts[2] = mul(instance, float4(boundMax.x, boundMax.y, boundMin.z, 1.0f));
    boundVerts[3] = mul(instance, float4(boundMax.x, boundMin.y, boundMax.z, 1.0f));
    boundVerts[4] = mul(instance, float4(boundMax.x, boundMin.y, boundMin.z, 1.0f));
    boundVerts[5] = mul(instance, float4(boundMin.x, boundMax.y, boundMax.z, 1.0f));
    boundVerts[6] = mul(instance, float4(boundMin.x, boundMax.y, boundMin.z, 1.0f));
    boundVerts[7] = mul(instance, float4(boundMin.x, boundMin.y, boundMax.z, 1.0f));
}

/**
 * \brief 计算输入的顶点每一个是否都在摄像机6个面之外
 * \param boundVerts 包围盒的8个顶点
 * \param cameraPlanes 摄像机视锥体的6个面
 * \return 顶点全在视锥体的面之外?
 */
bool IsCulled(float4 boundVerts[8], float4 cameraPlanes[6])
{
    for (int i=0;i<6;i++)
    {
        for (int j=0;j<8;j++)
        {
            if(!IsOutThePlane(cameraPlanes[i],boundVerts[j].xyz)) break;
            if(j==7) return true;
        }
    }
    return false;
}

[numthreads(64,1,1)]
void FrustumCulling (uint3 id : SV_DispatchThreadID)
{
    if (id.x>=inputCount) return;//判断分配线程数是否超过对象数量, 超过即return
    /* outputCount的初始化和释放在JobSystemRuntime中完成
     * output[outputCount[0]++] = instanceTRS;
     * Job的问题在于, 要取得确切的输出数量以确定Buffer大小.
    */
        
    float4x4 instance = Input[id.x];//定义并获取当前象的LocalToWorld矩阵

    /* Job声明
     * var boundVerts = new NativeArray<float4>(8, Allocator.Temp);
    */
    float4 boundVerts[8];
    boundVerts_Local2World(boxCenter,boxExtents,instance,boundVerts);//将顶点通过LocalToWorld矩阵转换到世界坐标

    if (!IsCulled(boundVerts,cameraPlanes))//判断是否当前的矩阵需要加入输出的AppendStructureBuffer
    {
        VisibleBuffer.Append(instance);
    }
}
本文由作者按照 CC BY 4.0 进行授权