学习记录<GPU Driven Terrain 入门>

本文探讨了如何在Unity项目中使用ComputeShader进行GPU驱动的地形渲染,涉及LOD四叉树、Hiz遮挡裁剪技术,以及ComputeShader在视锥裁剪中的作用。重点介绍了ComputeShader的执行方式和与传统Shader的区别,以及DispatchCompute在场景构建中的运用。
摘要由CSDN通过智能技术生成

原文链接:GitHub - MashiroShina/GPUDrivenTerrainLearn: A Unity Project used for Learning GPU Driven Terrain Render

原文包含:

  • LOD四叉树构建
  • 视锥裁剪
  • Hiz遮挡裁剪
  • LOD接缝处理

Compute Shader

  1. 功能和用途

    • 其他Shader:顶点着色器、片段着色器等主要用于图形渲染,处理顶点位置、颜色、纹理等,以及片段的光照、纹理采样等。它们在渲染管线中的特定阶段执行,用于产生最终的图像输出。
    • Compute Shader:计算着色器主要用于通用计算任务,与图形渲染无关。它们可以在GPU上执行各种非图形相关的计算,如物理模拟、图像处理、数据处理等。
  2. 执行位置

    • 其他Shader:顶点着色器和片段着色器在图形渲染管线的特定阶段执行,用于处理顶点和片段。
    • Compute Shader:可以在渲染管线之外执行,独立于图形渲染过程。这使得它们可以用于各种通用计算任务,而不受图形渲染管线的限制。
  3. 输入和输出

    • 其他Shader:通常以顶点、纹理、光照等为输入,并生成最终的图像输出。
    • Compute Shader:可以接受各种类型的输入数据,如纹理、缓冲区等,并生成计算结果作为输出。它们的输入和输出不限于图形数据,可以用于任意类型的计算任务。
  4. 执行方式

    • 其他Shader:通常以一组输入数据为基础,对每个顶点或片段执行相同的操作,以并行地生成输出图像。
    • Compute Shader:可以自定义执行方式,通过指定线程组的大小和数量,可以实现各种并行计算模式,更灵活地利用GPU的并行处理能力。

GPU Driven Terrain

GPU Driven Terrain指的是利用图形处理单元(GPU)来生成和渲染地形的技术或方法。在计算机图形学和游戏开发中,地形是一个重要的元素,用于创建游戏世界的地貌和环境。传统上,地形的生成和渲染是由中央处理单元(CPU)来处理的,但是随着图形处理单元的不断发展和强大,GPU也开始承担越来越多的地形生成和渲染工作。

GPU Driven Terrain的主要优势在于其并行处理能力和专门设计的图形处理功能,可以高效地处理大量地形数据,并且在实时渲染中提供更好的性能和视觉效果。这种方法通常使用各种技术,如地形生成算法、着色器程序、纹理映射等,来实现地形的细节和真实感。

总的来说,GPU Driven Terrain利用了GPU的计算和渲染能力,使得地形的生成和渲染更加高效和逼真,这在游戏开发、虚拟现实和模拟等领域都有广泛的应用。

四叉树

四叉树是一种数据结构,通常用于管理和组织在二维空间中的对象。它之所以被称为“四叉树”,是因为每个节点最多可以有四个子节点,将二维空间递归地划分成四个象限。这些象限分别是左上、右上、左下和右下,每个象限代表了二维空间的一个部分。

想象一下你有一张地图,现在你想要将这张地图分成更小的区域以便更有效地管理其中的信息。四叉树可以帮助你做到这一点。它从根节点开始,根据需要递归地将空间划分为四个象限,每个象限都是一个节点。如果一个象限内的对象太多,那么这个象限将继续被划分,直到每个象限内的对象数目达到某个设定的阈值或者达到了叶子节点。

四叉树的主要优点是它可以快速地定位空间中的对象,尤其是在需要频繁地进行空间查询的情况下。通过对空间进行逐级划分,我们可以在较短的时间内缩小搜索范围,从而更有效地找到所需的对象。这种结构在计算机图形学、碰撞检测、地理信息系统等领域有着广泛的应用。

Compute Shader Kernel

在 Unity Shader 中,Kernel 通常指的是 Compute Shader 中的一个计算核心,也就是一段在 GPU 上并行执行的代码。Kernel 是 Compute Shader 中的一个执行单元,可以执行一组相关的计算操作。

在 Unity 中,一个 Compute Shader 中可以定义多个 Kernel,但实际上在一个 Compute Shader 中定义的 Kernel 数量是没有硬性限制的。每个 Kernel 都包含一系列的线程(Thread)组成的线程组(Thread Group)。每个线程都会执行 Kernel 中定义的计算操作,这些计算操作通常是独立的,可以并行执行。然而,实际使用时需要考虑一些因素:

  1. 硬件支持:不同的 GPU 可能对 Kernel 数量有一定的限制。通常情况下,现代的 GPU 可以支持大量的 Kernel,但具体的限制可能因 GPU 型号、驱动程序版本等而有所不同。

  2. 性能影响:每个 Kernel 都会在 GPU 上分配一定的资源,包括线程组数目、寄存器、共享内存等。如果定义过多的 Kernel,可能会影响到 GPU 的性能。

  3. 代码维护:定义太多的 Kernel 可能会使 Compute Shader 的代码结构变得复杂,降低代码的可读性和可维护性。

在 Unity 中,通过使用 #pragma kernel 指令来定义 Kernel。例如:

#pragma kernel ComputeKernel

这个指令告诉 Unity 编译器,在当前 Compute Shader 中定义了一个名为 ComputeKernel 的 Kernel。在实际使用时,你可以调用 ComputeKernel 来执行对应的计算操作。

Kernel 在 Unity Shader 中非常重要,特别是在需要大规模并行计算的情况下,比如图形渲染、物理模拟等方面。通过利用 GPU 的并行计算能力,可以大大提高计算效率和性能。

例子中获取kernel

 _kernelOfBuildPatches = _computeShader.FindKernel("BuildPatches");

Shader Log

如果你想查看Compute Shader中的特定float变量的值,你可以通过将该变量写入结构化缓冲区,然后在C#脚本中读取这个缓冲区的值来实现。

下面是一个示例,演示了如何在Compute Shader中计算某个float值,然后将其写入结构化缓冲区,并在C#脚本中读取并查看这个值:

#pragma kernel CSMain
struct ResultData {
    float floatValue;
};
RWStructuredBuffer<ResultData> resultBuffer;

[numthreads(8, 8, 1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    float floatValue = 10.0f; // 这里是你的计算结果,假设为10.0f

    ResultData data;
    data.floatValue = floatValue;

    // 将结果写入结构化缓冲区
    resultBuffer[id.x + id.y * 8] = data;
}
using UnityEngine;

public class ComputeShaderDebugger : MonoBehaviour
{
    public ComputeShader computeShader;

    private ComputeBuffer resultBuffer;
    private ResultData[] resultDataArray;

    struct ResultData {
        public float floatValue;
    }

    void Start()
    {
        resultBuffer = new ComputeBuffer(64, sizeof(float));
        resultDataArray = new ResultData[64];
        computeShader.SetBuffer(kernelIndex, "resultBuffer", resultBuffer);
    }

    void Update()
    {
        computeShader.Dispatch(kernelIndex, 8, 8, 1);

        resultBuffer.GetData(resultDataArray);

        // 读取并输出特定索引处的float值
        int index = 0; // 这是你要查看的特定索引
        float value = resultDataArray[index].floatValue;
        Debug.Log("Float Value at index " + index + ": " + value);
    }

    void OnDestroy()
    {
        resultBuffer.Release();
    }
}

项目GPUTerrain.cs调试测试

TerrainAsset

WorldSize: 10240   = 一个PatchMesh边长8米 * Map1280 

FrustumCullEnabled 视锥体裁剪

视椎体裁剪推导:视锥体平面公式推导 - 知乎

通过面板set isFrustumCullEnabled会触发_computeShader.EnableKeyword("ENABLE_FRUS_CULL");

在TerrainBuildCompute.compute的BuildPatches()方法中执行剔除Cull()
Cull中执行视椎体裁剪FrustumCull(_CameraFrustumPlanes,bounds))

参数:

  • CameraFrustumPlanes 镜头视椎体平面
    C#通过GeometryUtility.CalculateFrustumPlanes(camera, _cameraFrustumPlanes);获取镜头视椎体平面
    处理后设置到compute shader中: _computeShader.SetVectorArray(ShaderConstants.CameraFrustumPlanes, _cameraFrustumPlanesV4);
  • bounds
    由C#执行_commandBuffer.DispatchCompute(_computeShader, _kernelOfBuildPatches, _indirectArgsBuffer, 0);

    参数说明:
    1)_commandBuffer Shader的buff
    2)_computeShader 此次实现地形的computeShader
    3)_indirectArgsBuffer传入最终数据
    计算过程:
    1.将世界在xz平面上分割成相等的5 x 5块相同的区域,每个区域视作一个Node
    2.GPU对四叉树遍历分割,进行节点评价,生成AppendFinalNodeList和NodeDescriptors:TraverseQuadTree
    3.生成Patch
    4.Patch渲染
DispatchCompute

方法是一个用于在 Unity 中执行 Compute Shader 的函数。它允许你在 GPU 上调度一个 Compute Shader 的核心(Kernel)来执行计算操作。
DispatchCompute 方法允许你通过传递一个 Compute Buffer 来指定 Compute Shader 的参数,而不是直接传递参数给 Compute Shader。这种方式可以提高灵活性,允许你在运行时动态修改参数信息,而无需修改代码。通常情况下,indirectBuffer 中存储的参数信息是以一定的格式排列的,例如包含了线程组数量等信息。
这个方法的语法如下:参数说明如下:
1.computeShader:要执行的 Compute Shader 对象。
2,kernelIndex:要执行的 Compute Shader 的核心索引。
3.indirectBuffer:一个ComputeBuffer,其中包含了要传递给ComputeShader的参数信息。
4.argsOffset:参数偏移量,用于指定在 indirectBuffer 中存储参数信息的起始位置。

[numthreads(x, y, z)]

在Unity中,[numthreads(x, y, z)]是用于指定Compute Shader(计算着色器)中每个线程组内的线程数量的指令。Compute Shader是一种在GPU上执行通用计算任务的特殊类型的着色器。numthreads(x, y, z)指令告诉GPU在每个线程组内创建多少个线程。

具体来说,x, y, 和 z 分别指定了在每个维度上的线程数量。例如,[numthreads(8, 8, 1)]指令表示在每个线程组内有 8 * 8 = 64 个线程。这意味着在X和Y维度上有8个线程,而Z维度上只有一个线程。

在编写Compute Shader时,你可以根据任务的需求调整这些参数,以确保在GPU上并行执行任务时能够充分利用硬件资源。通常情况下,你需要根据具体任务的特性和硬件的限制来选择合适的线程数量,以实现最佳的性能和效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值