草(一)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]