QQ登录

只需一步,快速开始

 找回密码
 定下契约(新注册)

QQ登录

只需一步,快速开始

查看: 1809|回复: 0
收起左侧

[原创] 草(一)Unity移动端可用的草海,概述与实现

[复制链接]

Tech Artist

Rank: 16

UID
60078
宝石
0 粒
金币
29 枚
节操
3 斤
灵石
0 块
精力
49 ℃
发表于 2021-10-20 13:51:10 | 显示全部楼层 |阅读模式

你这样只看不注册,真的大丈夫?~

您需要 登录 才可以下载或查看,没有账号?定下契约(新注册)

x
本帖最后由 laojiong 于 2021-10-20 13:54 编辑

[md]帖最后由 laojiong 于 2021-10-20 13:53 编辑## 大面积渲染瓶颈

> 在实时渲染引擎中,对于大场景和大量同物体渲染都有不同的优化方案。至于为啥需要优化,简单来说就是数据量多,需要或频繁或大量的数据提交给**GPU大兄弟**,占用了**传输带宽**,而调用传输命令需要***CPU小兄弟***去协调数据和发送数据,又因为现代计算机的*总线*架构,**传输带宽**影响了各个计算设备的上限,因此需要有特殊的方法去避免***传输带宽***和***CPU***影响了***GPU***的计算。

## 大面积渲染的方案

> 现代实时渲染引擎中有多种优化**传输带宽**的方案,在Unity中有批处理Static Batch、Dynamic Batch和SRP Batch技术,还有GPU Instancing都可以用于优化大面积渲染。

## 大面积草渲染

> 本系列参考(抄)自[MinionsArt](https://www.patreon.com/posts/urp-compute-54164790)。本文采用的应该属于GPU Instancing技术吧_(:з」∠)_,我也不是很确定,反正也是抄过来的,我感觉应该属于Instancing技术。

本篇属于草体渲染第一篇,总共三篇。
本篇主要介绍如何利用Compute Shader处理渲染数据以提高降低CPU和带宽占用。
++**声明:本篇开发环境是Unity2019.4,URP7.3.1。阅读本篇需要对Compute Shader有初步的了解。(我也不是特别懂,反正能用就行)**++

### 流程说明

1. C#(CPU)端收集或生成位置点结构体数据,结构体包含坐标点positionOS,法线normalOS,生成草宽度和高度的变量参数uv,颜色color。
2. C#(CPU)端生成ComputeBuffer,并绑定对应的数据,并提交到ComputeShader计算生成三角面。
3. ComputeShader(GPU)端根据草位置点数据,生成草的三角面,由于渲染shader采用双面渲染,因此组成三角面顶点的顺序不需要特别处理。
4. C#(CPU)端使用ComputeShader计算完成的数据,调用[Graphics.DrawProceduralIndirect](https://docs.unity3d.com/cn/curr ... eduralIndirect.html)让GPU渲染草。

下图简单说明如何生成三角面
![QQ截图20210906152235.png](https://cdn.laojiong.site/Fg3ThL2PGF10f6slU9HXGtW-hNnZ)

直接贴代码,具体说明在注释中
***GrassCompute.compute***

```cpp
// #kernel 指明哪个方法是内核方法,类似普通shader的vertex和fragment
#pragma kernel Main


// 定义一些常量
#define PI          3.14159265358979323846
#define TWO_PI      6.28318530717958647693

// 定义源数据结构体,用于生成草的三角形
struct SourceVertex {
    float3 positionOS; // 模型空间位置,每一批草的原点位置
    float3 normalOS; // 模型空间法线
    float2 uv;  // 包含 widthMultiplier, heightMultiplier,控制草的宽度和高度
    float3 color; // 颜色
};

// 用于接收和储存来自CPU提交过来的源数据,kernel只需要读取
StructuredBuffer<SourceVertex> _SourceVertices;

// 定义顶点渲染数据结构体
struct DrawVertex {
    float3 positionWS; // 顶点世界坐标
    float2 uv; // uv
    float3 diffuseColor; //颜色
};

// 定义三角形渲染结构体
struct DrawTriangle {
    float3 normalOS; //法线
    DrawVertex vertices[3]; // 3个顶点组合成三角面
};

// 用于储存生成的三角形数据,kernel用于Append
AppendStructuredBuffer<DrawTriangle> _DrawTriangles;

// 定义DrawIndirect方法需要用到的结构体,记录SV_VertexID数据
struct IndirectArgs {
    uint numVerticesPerInstance;
    uint numInstances;
    uint startVertexIndex;
    uint startInstanceIndex;
};

// kernel用于修改,因此需要RW
RWStructuredBuffer<IndirectArgs> _IndirectArgsBuffer;

// 定义生成草三角形的参数
#define GRASS_BLADES 4  // 每批草会有多少根
#define GRASS_SEGMENTS 5  // 每根草有多少分段
#define GRASS_NUM_VERTICES_PER_BLADE (GRASS_SEGMENTS * 2 + 1) // 每根草的顶点数

// ----------------------------------------

// 源数据数量,由C#端控制
int _NumSourceVertices;

// Local to world matrix
float4x4 _LocalToWorld;

// Time
float _Time;

// 草的参数
half _GrassHeight;
half _GrassWidth;
float _GrassRandomHeight;

// 风力参数
half _WindSpeed;
float _WindStrength;

// 每批草的参数
half _BladeRadius; //每批草的分布范围
float _BladeForward; //草的前方向,用于弯曲草
float _BladeCurve; //草的弯曲曲线
int _MaxBladesPerVertex; //从C#传入,搭配 GRASS_BLADES控制草的数量
int _MaxSegmentsPerBlade; //从C#传入,搭配GRASS_SEGMENTS控制草的分段数


// ----------------------------------------

// 通用方法
// 随机数
float rand(float3 co) {
    return frac(
        sin(dot(co.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
}

// 根据旋转轴和角度生成一个旋转矩阵
// By Keijiro Takahashi
float3x3 AngleAxis3x3(float angle, float3 axis) {
    float c, s;
    sincos(angle, s, c);

    float t = 1 - c;
    float x = axis.x;
    float y = axis.y;
    float z = axis.z;

    return float3x3(
        t * x * x + c, t * x * y - s * z, t * x * z + s * y,
        t * x * y + s * z, t * y * y + c, t * y * z - s * x,
        t * x * z - s * y, t * y * z + s * x, t * z * z + c);
}

// 生成顶点
DrawVertex GrassVertex(float3 positionOS, float width, float height,
float offset, float curve, float2 uv, float3x3 rotation, float3 color) {
    DrawVertex output = (DrawVertex)0;
  
    float3 newPosOS = positionOS + mul(rotation, float3(width, height, curve) + float3(0, 0, offset));
    output.positionWS = mul(_LocalToWorld, float4(newPosOS, 1)).xyz;
    output.uv = uv;
    output.diffuseColor = color;
    return output;
}

// ----------------------------------------

// The main kernel
[numthreads(128, 1, 1)]
void Main(uint3 id : SV_DispatchThreadID) {
    //超出顶点数限制,Return
    if ((int)id.x >= _NumSourceVertices) {
        return;
    }
  
    SourceVertex sv = _SourceVertices[id.x];

    float forward = _BladeForward;
  
    float3 perpendicularAngle = float3(0, 0, 1);
    float3 faceNormal = sv.normalOS;
    float3 worldPos = mul(_LocalToWorld, float4(sv.positionOS, 1)).xyz;

    // 风
    float3 v0 = sv.positionOS.xyz;
    float3 wind1 = float3(
        sin(_Time.x * _WindSpeed + v0.x) + sin(
            _Time.x * _WindSpeed + v0.z * 2) + sin(
                _Time.x * _WindSpeed * 0.1 + v0.x), 0,
                cos(_Time.x * _WindSpeed + v0.x * 2) + cos(
                    _Time.x * _WindSpeed + v0.z));

                wind1 *= _WindStrength;

                float3 color = sv.color;
        
                // 设置草的高度
                _GrassWidth *= sv.uv.x;  // UV.x == width multiplier
                _GrassHeight *= sv.uv.y;  // UV.y == height multiplier
                _GrassHeight *= clamp(rand(sv.positionOS.xyz), 1 - _GrassRandomHeight,
                1 + _GrassRandomHeight);

                // 计算每批草数量和每根草的分段数
                int numBladesPerVertex = min(GRASS_BLADES, max(1, _MaxBladesPerVertex));
                int numSegmentsPerBlade = min(GRASS_SEGMENTS, max(1, _MaxSegmentsPerBlade));
                int numTrianglesPerBlade = (numSegmentsPerBlade - 1) * 2 + 1;
        
                DrawVertex drawVertices[GRASS_NUM_VERTICES_PER_BLADE];

                for (int j = 0; j < numBladesPerVertex; ++j) {
                    // 设置旋转和位置偏移
                    float3x3 facingRotationMatrix = AngleAxis3x3(
                        rand(sv.positionOS.xyz) * TWO_PI + j, float3(0, 1, -0.1));
                    float3x3 transformationMatrix = facingRotationMatrix;
                    float bladeRadius = j / (float) numBladesPerVertex;
                    float offset = (1 - bladeRadius) * _BladeRadius;

                    for (int i = 0; i < numSegmentsPerBlade; ++i) {
                        // 逐分段调整宽度和高度
                        float t = i / (float) numSegmentsPerBlade;
                        float segmentHeight = _GrassHeight * t;
                        float segmentWidth = _GrassWidth * (1 - t);

                        // 第一个分段更细
                        segmentWidth = i == 0 ? _GrassWidth * 0.3 : segmentWidth;

                        float segmentForward = pow(abs(t), _BladeCurve) * forward;

                        float3x3 transformMatrix = (i == 0) ? facingRotationMatrix :
                                                              transformationMatrix;

                        // 第一个分段不受风力影响
                        float3 newPos = (i == 0) ? v0 : v0 + wind1 * t;
               
                        // 第一个顶点
                        drawVertices[i * 2] = GrassVertex(newPos, segmentWidth, segmentHeight,
                                   offset, segmentForward, float2(0, t), transformMatrix, color);

                        // 第二个顶点
                        drawVertices[i * 2 + 1] = GrassVertex(newPos, -segmentWidth, segmentHeight,
                                   offset, segmentForward, float2(1, t), transformMatrix, color);
                    }
                    // 最上面的顶点
                    float3 topPosOS = v0 + wind1;
                    drawVertices[numSegmentsPerBlade * 2] = GrassVertex(topPosOS, 0, _GrassHeight,
                                   offset, forward, float2(0.5, 1), transformationMatrix, color);
                    // 添加三角形
                    for (int k = 0; k < numTrianglesPerBlade; ++k) {
                        DrawTriangle tri = (DrawTriangle)0;
                        tri.normalOS = faceNormal;
                        tri.vertices[0] = drawVertices[k];
                        tri.vertices[1] = drawVertices[k + 1];
                        tri.vertices[2] = drawVertices[k + 2];
                        _DrawTriangles.Append(tri);
                    }
                }// 一批草
        
                // InterlockedAdd(a, b) 是一个原子操作,给变量a加上b,并存在a中,告诉CPU需要渲染多少草
                // InterlockedAdd(_IndirectArgsBuffer[0].numVerticesPerInstance, 3);
                // 在b中加入如numTrianglesPerBlade * numBladesPerVertex这种运行时才确定的数值时会有问题
                // 不知有没有大神指导优化一下
                const int addVertexCount = numTrianglesPerBlade * numBladesPerVertex;
                for (int i = 0; i < addVertexCount; i++)
                {
                    InterlockedAdd(_IndirectArgsBuffer[0].numVerticesPerInstance,3);
                }        
            }

```

***GrassCompute.shader***

```cpp

Shader "Custom/GrassCompute"
{
    Properties
    {
        _ShadowReceiveStrength("Shadow Receive Strength", Range(0,1)) = 0.5
    }

    HLSLINCLUDE

    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"

    // 顶点
    struct DrawVertex
    {
        float3 positionWS; // 顶点世界坐标
        float2 uv;
        float3 diffuseColor;
    };

    // 三角面
    struct DrawTriangle
    {
        float3 normalOS;
        DrawVertex vertices[3];
    };

    // ComputeShader计算后的三角面数据集
    StructuredBuffer<DrawTriangle> _DrawTriangles;

    struct v2f
    {
        float4 positionCS : SV_POSITION;
        float2 uv : TEXCOORD0;
        float3 positionWS : TEXCOORD1;
        float3 normalWS : TEXCOORD2;
        float3 diffuseColor : COLOR;
        float fogFactor : TEXCOORD5;
    };

    // Properties
    float4 _TopTint; //草的顶部颜色
    float4 _BottomTint; //草的底部颜色
    float _AmbientStrength; //环境光强度

    float _ShadowReceiveStrength;

    // ----------------------------------------

    // Vertex function

    // 从ComputeShader获取数据
    v2f vert(uint vertexID : SV_VertexID)
    {
        // 初始化输出结构体
        v2f output = (v2f)0;

        // 获取三角形数据
        // 因为是以三角面为数据,所以要以3为除数索引
        DrawTriangle tri = _DrawTriangles[vertexID / 3];
        DrawVertex input = tri.vertices[vertexID % 3];

        output.positionCS = TransformWorldToHClip(input.positionWS);
        output.positionWS = input.positionWS;
  
        output.normalWS = TransformObjectToWorldNormal(tri.normalOS);
        float fogFactor = ComputeFogFactor(output.positionCS.z);
        output.fogFactor = fogFactor;
        output.uv = input.uv;

        output.diffuseColor = input.diffuseColor;

        return output;
    }

    // ----------------------------------------

    // Fragment function

    half4 frag(v2f i) : SV_Target
    {
        // Shadow Caster Pass
        #ifdef SHADERPASS_SHADOWCASTER
            return 0;
        #else
   
            #if SHADOWS_SCREEN
                half4 shadowCoord = ComputeScreenPos(i.positionCS);
            #else
                half4 shadowCoord = TransformWorldToShadowCoord(i.positionWS);
            #endif  
            #if _MAIN_LIGHT_SHADOWS_CASCADE || _MAIN_LIGHT_SHADOWS
                Light mainLight = GetMainLight(shadowCoord);
            #else
                Light mainLight = GetMainLight();
            #endif
            float shadow = mainLight.shadowAttenuation;
            shadow = saturate((1-_ShadowReceiveStrength)+ shadow );
            // 额外光源
            float3 extraLights;
            int pixelLightCount = GetAdditionalLightsCount();
            for (int j = 0; j < pixelLightCount; ++j) {
                Light light = GetAdditionalLight(j, i.positionWS);
                float3 attenuatedLightColor = light.color *
                                (light.distanceAttenuation * light.shadowAttenuation);
                extraLights += attenuatedLightColor;
            }
            float4 baseColor = lerp(_BottomTint, _TopTint, saturate(i.uv.y)) *
                               float4(i.diffuseColor, 1);

            // 主光源颜色
            float4 litColor = (baseColor * float4(mainLight.color,1));

            litColor += float4(extraLights,1);
            // 顶点颜色和阴影
            float4 final = litColor * shadow;
            // 无光时添加baseColor
            final += saturate((1 - shadow) * baseColor * 0.2);
            // fog
            float fogFactor = i.fogFactor;

            // 雾
            final.rgb = MixFog(final.rgb, fogFactor);
            // 环境光
            final += (unity_AmbientSky * _AmbientStrength);
            final.a = 1;

            return final;

        #endif  // SHADERPASS_SHADOWCASTER
    }
    ENDHLSL

    SubShader {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"
               "IgnoreProjector" = "True" }

        // Forward Lit Pass
        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode" = "UniversalForward" }
            Cull Off // 双面显示

            HLSLPROGRAM
            // 需要ComputeBuffer,设置target
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma target 5.0

            // Lighting and shadow keywords
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _ADDITIONAL_LIGHTS
            #pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
            #pragma multi_compile _ _SHADOWS_SOFT
            #pragma multi_compile_fog

            #pragma vertex vert
            #pragma fragment frag

            ENDHLSL
        }
  
        // 投射阴影的Pass
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }
            ZWrite On
            ZTest LEqual
            Cull Off
   
            HLSLPROGRAM
            // Signal this shader requires geometry function support
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma target 5.0

            // Support all the various light  ypes and shadow paths
            #pragma multi_compile_shadowcaster

            // Register our functions
            #pragma vertex vert
            #pragma fragment frag

            // A custom keyword to modify logic during the shadow caster pass
            #define SHADERPASS_SHADOWCASTER

            #pragma shader_feature_local _ DISTANCE_DETAIL
   
            // Include vertex and fragment functions
   
            ENDHLSL
        }
    }
}

```

***GrassComputeScript***

```csharp

using UnityEngine;
using UnityEditor;

[ExecuteInEditMode]
public class GrassRenderer : MonoBehaviour {
    [Header("Components")]
    [SerializeField] private Mesh sourceMesh = default;
    [SerializeField] private Material material = default;
    [SerializeField] private ComputeShader computeShader = default;

    // Blade
    [Header("Blade")]
    public float grassHeight = 1;
    public float grassWidth = 0.06f;
    public float grassRandomHeight = 0.25f;
    [Range(0, 1)] public float bladeRadius = 0.6f;
    [Range(0, 1)] public float bladeForwardAmount = 0.38f;
    [Range(1, 5)] public float bladeCurveAmount = 2;

    [SerializeField]
    ShaderInteractor interactor;

    // 风
    [Header("Wind")]
    public float windSpeed = 10;
    public float windStrength = 0.05f;

    // 材质
    [Header("Material")]
    public Color topTint = new Color(1, 1, 1);
    public Color bottomTint = new Color(0, 0, 1);
    public float ambientStrength = 0.1f;
    // 其他
    [Header("Other")]
    public UnityEngine.Rendering.ShadowCastingMode castShadow;


    private readonly int m_AllowedBladesPerVertex = 4;
    private readonly int m_AllowedSegmentsPerBlade = 5;

    // 发送到ComputeShader的数据,需要保证内存结构
    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind
        .Sequential)]
    private struct SourceVertex {
        public Vector3 position;
        public Vector3 normal;
        public Vector2 uv;
        public Vector3 color;
    }

    // 是否已经初始化
    private bool m_Initialized;
    // 源数据
    private ComputeBuffer m_SourceVertBuffer;
    // 绘制数据
    private ComputeBuffer m_DrawBuffer;
    // DrawIndirect需要的数据
    private ComputeBuffer m_ArgsBuffer;
    // ComputeShader和材质实例化对象
    private ComputeShader m_InstantiatedComputeShader;
    private Material m_InstantiatedMaterial;
    // ComputeShader的kernel id
    private int m_IdGrassKernel;
    // ComputeShader x分发大小
    private int m_DispatchSize;
    // 包围盒
    private Bounds m_LocalBounds;

    // 各个computebuffer的单位大小
    private const int SOURCE_VERT_STRIDE = sizeof(float) * (3 + 3 + 2 + 3);
    private const int DRAW_STRIDE = sizeof(float) * (3 + (3 + 2 + 3) * 3);
    private const int INDIRECT_ARGS_STRIDE = sizeof(int) * 4;

    // 每帧重置argsBuffer
    // 0: 每次drawcall调用需要处理顶点数,本例只使用一个instance
    // 1: instance数量
    // 2: start vertex location if using a Graphics Buffer,不是很懂
    // 3: and start instance location if using a Graphics Buffer,不是很懂
    private int[] argsBufferReset = new int[] { 0, 1, 0, 0 };

    private void OnValidate() {  
        sourceMesh = GetComponent<MeshFilter>().sharedMesh;
    }

    private void OnEnable() {
        // 如果已经初始化,先清理旧数据
        if (m_Initialized) {
            OnDisable();
        }

        //material和compute shader需要手动指定

        if (sourceMesh == null || computeShader == null || material == null) {
            return;
        }
        sourceMesh = GetComponent<MeshFilter>().sharedMesh;

        if (sourceMesh.vertexCount == 0) {
            return;
        }

        m_Initialized = true;

        // 生成computeShader和material
        m_InstantiatedComputeShader = Instantiate(computeShader);
        m_InstantiatedMaterial = Instantiate(material);

        // 从模型中获取数据
        Vector3[] positions = sourceMesh.vertices;
        Vector3[] normals = sourceMesh.normals;
        Vector2[] uvs = sourceMesh.uv;
        Color[] colors = sourceMesh.colors;

        // 创建需要传输的数据
        SourceVertex[] vertices = new SourceVertex[positions.Length];
        for (int i = 0; i < vertices.Length; i++) {
            Color color = colors;
            vertices = new SourceVertex() {
                position = positions,
                normal = normals,
                uv = uvs,
                color = new Vector3(color.r, color.g, color.b)
            };
        }

        int numSourceVertices = vertices.Length;

        // 批数量和分段
        int maxBladesPerVertex = Mathf.Max(1, m_AllowedBladesPerVertex);
        int maxSegmentsPerBlade = Mathf.Max(1, m_AllowedSegmentsPerBlade);
        int maxBladeTriangles = maxBladesPerVertex * ((maxSegmentsPerBlade - 1) * 2 + 1);

        // 创建ComputeBuffer
        m_SourceVertBuffer = new ComputeBuffer(vertices.Length, SOURCE_VERT_STRIDE,
            ComputeBufferType.Structured, ComputeBufferMode.Immutable);
        m_SourceVertBuffer.SetData(vertices);

        m_DrawBuffer = new ComputeBuffer(numSourceVertices * maxBladeTriangles, DRAW_STRIDE,
            ComputeBufferType.Append);
        m_DrawBuffer.SetCounterValue(0);

        m_ArgsBuffer =
            new ComputeBuffer(1, INDIRECT_ARGS_STRIDE, ComputeBufferType.IndirectArguments);

        // 记录kernel id
        m_IdGrassKernel = m_InstantiatedComputeShader.FindKernel("Main");

        // 绑定computeBuffer
        m_InstantiatedComputeShader.SetBuffer(m_IdGrassKernel, "_SourceVertices",
            m_SourceVertBuffer);
        m_InstantiatedComputeShader.SetBuffer(m_IdGrassKernel, "_DrawTriangles", m_DrawBuffer);
        m_InstantiatedComputeShader.SetBuffer(m_IdGrassKernel, "_IndirectArgsBuffer",
            m_ArgsBuffer);
        // 设置参数
        m_InstantiatedComputeShader.SetInt("_NumSourceVertices", numSourceVertices);
        m_InstantiatedComputeShader.SetInt("_MaxBladesPerVertex", maxBladesPerVertex);
        m_InstantiatedComputeShader.SetInt("_MaxSegmentsPerBlade", maxSegmentsPerBlade);

        m_InstantiatedMaterial.SetBuffer("_DrawTriangles", m_DrawBuffer);

        m_InstantiatedMaterial.SetColor("_TopTint", topTint);
        m_InstantiatedMaterial.SetColor("_BottomTint", bottomTint);
        m_InstantiatedMaterial.SetFloat("_AmbientStrength", ambientStrength);


        // 获取线程数并计算需要分发的数量
        m_InstantiatedComputeShader.GetKernelThreadGroupSizes(m_IdGrassKernel,
            out uint threadGroupSize, out _, out _);
        m_DispatchSize = Mathf.CeilToInt((float)numSourceVertices / threadGroupSize);

        // 计算包围盒
        m_LocalBounds = sourceMesh.bounds;
        m_LocalBounds.Expand(Mathf.Max(grassHeight + grassRandomHeight, grassWidth));

        SetGrassDataBase();
    }

    private void OnDisable() {
        if (m_Initialized) {
            if (Application.isPlaying) {
                Destroy(m_InstantiatedComputeShader);
                Destroy(m_InstantiatedMaterial);
            }
            else {
                DestroyImmediate(m_InstantiatedComputeShader);
                DestroyImmediate(m_InstantiatedMaterial);
            }

            m_SourceVertBuffer?.Release();
            m_DrawBuffer?.Release();
            m_ArgsBuffer?.Release();
        }

        m_Initialized = false;
    }

    private void LateUpdate() {
        // 在编辑模式下,保证修改的参数能应用上
        if (Application.isPlaying == false) {
            OnDisable();
            OnEnable();
        }

        if (!m_Initialized) {
            return;
        }

        // 清理上一帧残留的数据
        m_DrawBuffer.SetCounterValue(0);
        m_ArgsBuffer.SetData(argsBufferReset);

        // 转换包围盒坐标
        Bounds bounds = TransformBounds(m_LocalBounds);

        // 更新每帧刷新的数据
        SetGrassDataUpdate();

        // 派发任务到ComputeShader,这会在GPU执行
        m_InstantiatedComputeShader.Dispatch(m_IdGrassKernel, m_DispatchSize, 1, 1);

        // DrawProceduralIndirect产生一个drawcall,绘制草
        Graphics.DrawProceduralIndirect(m_InstantiatedMaterial, bounds, MeshTopology.Triangles,
            m_ArgsBuffer, 0, null, null, castShadow, true, gameObject.layer);
    }

    private void SetGrassDataBase() {
        // 非每帧更新的数据
        m_InstantiatedComputeShader.SetMatrix("_LocalToWorld", transform.localToWorldMatrix);
        m_InstantiatedComputeShader.SetFloat("_Time", Time.time);

        m_InstantiatedComputeShader.SetFloat("_GrassHeight", grassHeight);
        m_InstantiatedComputeShader.SetFloat("_GrassWidth", grassWidth);
        m_InstantiatedComputeShader.SetFloat("_GrassRandomHeight", grassRandomHeight);

        m_InstantiatedComputeShader.SetFloat("_WindSpeed", windSpeed);
        m_InstantiatedComputeShader.SetFloat("_WindStrength", windStrength);

        m_InstantiatedComputeShader.SetFloat("_BladeRadius", bladeRadius);
        m_InstantiatedComputeShader.SetFloat("_BladeForward", bladeForwardAmount);
        m_InstantiatedComputeShader.SetFloat("_BladeCurve", Mathf.Max(0, bladeCurveAmount));

    }

    private void SetGrassDataUpdate() {
        // 每帧更新的数据
        m_InstantiatedComputeShader.SetFloat("_Time", Time.time);
        if (interactor != null) {
            m_InstantiatedComputeShader.SetVector("_PositionMoving", interactor.transform.position);
        }
        else {
            m_InstantiatedComputeShader.SetVector("_PositionMoving", Vector3.zero);
        }
    }


    // 计算世界坐标的包围盒
    // https://answers.unity.com/questi ... inates-to-loca.html
    private Bounds TransformBounds(Bounds boundsOS) {
        var center = transform.TransformPoint(boundsOS.center);

        // transform the local extents' axes
        var extents = boundsOS.extents;
        var axisX = transform.TransformVector(extents.x, 0, 0);
        var axisY = transform.TransformVector(0, extents.y, 0);
        var axisZ = transform.TransformVector(0, 0, extents.z);

        // sum their absolute value to get the world extents
        extents.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x);
        extents.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y);
        extents.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z);

        return new Bounds { center = center, extents = extents };
    }
}
```

### 测试

> 场景中放个空物体,挂上GrassRenderer.cs,添加meshFilter,挂上mesh,mesh必须包含uv,顶点色。

![QQ图片20210906183543.png](https://cdn.laojiong.site/Fr-8H5DkhfKqFdxaVG6iEWWZCptU)

下一篇将介绍LOD和Camera Culling等优化手段
[/md]

本版积分规则

    切换繁體
    Archiver|手机版|小黑屋|

GMT+8, 2024-5-9 16:38 , Processed in 0.062285 second(s), 34 queries .

沪ICP备2021020632号-1

快速回复 返回顶部 返回列表