laojiong 发表于 2021-10-20 13:51:10

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

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

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

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

## 大面积渲染的方案

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

## 大面积草渲染

> 本系列参考(抄)自(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计算完成的数据,调用(https://docs.unity3d.com/cn/current/ScriptReference/Graphics.DrawProceduralIndirect.html)让GPU渲染草。

下图简单说明如何生成三角面
!(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个顶点组合成三角面
};

// 用于储存生成的三角形数据,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

void Main(uint3 id : SV_DispatchThreadID) {
    //超出顶点数限制,Return
    if ((int)id.x >= _NumSourceVertices) {
      return;
    }

    SourceVertex sv = _SourceVertices;

    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;

                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 = GrassVertex(newPos, segmentWidth, segmentHeight,
                                 offset, segmentForward, float2(0, t), transformMatrix, color);

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

    // 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;
      DrawVertex input = tri.vertices;

      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 lightypes 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;


public class GrassRenderer : MonoBehaviour {
   
    private Mesh sourceMesh = default;
    private Material material = default;
    private ComputeShader computeShader = default;

    // Blade
   
    public float grassHeight = 1;
    public float grassWidth = 0.06f;
    public float grassRandomHeight = 0.25f;
    public float bladeRadius = 0.6f;
    public float bladeForwardAmount = 0.38f;
    public float bladeCurveAmount = 2;

   
    ShaderInteractor interactor;

    // 风
   
    public float windSpeed = 10;
    public float windStrength = 0.05f;

    // 材质
   
    public Color topTint = new Color(1, 1, 1);
    public Color bottomTint = new Color(0, 0, 1);
    public float ambientStrength = 0.1f;
    // 其他
   
    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;
      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/questions/361275/cant-convert-bounds-from-world-coordinates-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,顶点色。

!(https://cdn.laojiong.site/Fr-8H5DkhfKqFdxaVG6iEWWZCptU)

下一篇将介绍LOD和Camera Culling等优化手段
页: [1]
查看完整版本: 草(一)Unity移动端可用的草海,概述与实现