视锥体剔除
视锥体剔除
[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 进行授权