Unity CommandBuffer:高效渲染的利器
Unity CommandBuffer:高效渲染的利器 🚀
在 Unity 中,CommandBuffer 是 优化渲染性能 的重要工具。它允许我们 手动控制 GPU 绘制流程,批量提交渲染指令,减少 DrawCall,并实现自定义后处理、动态阴影、特效渲染等功能。
🔹 一、什么是 CommandBuffer?
简单来说,CommandBuffer 就是一个 GPU 渲染命令列表,它可以:
- 延迟提交 渲染指令(减少 CPU/GPU 负担)。
- 批量执行
DrawCall,避免 CPU 过度调用 API。 - 插入自定义渲染指令(如深度处理、后处理效果)。
- 脱离 GameObject 直接控制渲染(如画辅助线、调试网格)。
🔹 什么时候用?
- 需要 高效绘制大量对象(比如迷雾效果、MiniMap、辅助线)。
- 批量渲染(比如多个同心圆、UI 叠加特效)。
- 后处理效果(比如自定义屏幕后处理 Shader)。
- 需要 不依赖 GameObject 进行渲染(比如网格调试)。
🔥 二、核心 API(20% 代码解决 80% 问题)
1️⃣ 创建和释放 CommandBuffer
CommandBuffer commandBuffer = new CommandBuffer { name = "MyBuffer" };
CommandBuffer 是一个 独立的 GPU 指令列表,需要手动创建,并绑定到 Camera 或 Renderer。
🛑 注意: 创建后必须绑定到 Camera 才能生效:
xxxxxxxxxx
Camera.main.AddCommandBuffer(CameraEvent.AfterForwardOpaque, commandBuffer);
释放时:
xxxxxxxxxx
Camera.main.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, commandBuffer);
commandBuffer.Release(); // 释放 GPU 资源
2️⃣ 画 Mesh
CommandBuffer.DrawMesh() 用于绘制网格,适用于 绘制辅助线、调试模型、动态阴影:
xxxxxxxxxx
commandBuffer.DrawMesh(myMesh, Matrix4x4.identity, myMaterial, 0, 0);
| 参数 | 作用 |
|---|---|
myMesh |
要绘制的网格 |
Matrix4x4.identity |
物体的世界变换矩阵 |
myMaterial |
渲染时使用的材质 |
0, 0 |
子网格索引 & Shader Pass |
✅ 例子:绘制多个同心圆
xxxxxxxxxx
foreach (float radius in new float[] {10, 20, 50, 100})
{
Mesh circle = CreateWireCircle(radius);
commandBuffer.DrawMesh(circle, Matrix4x4.identity, lineMaterial, 0, 0);
}
3️⃣ 绑定到 Camera
xxxxxxxxxx
Camera.main.AddCommandBuffer(CameraEvent.AfterForwardOpaque, commandBuffer);
📌 常见绑定点(CameraEvent):
| 事件 | 作用 |
|---|---|
BeforeForwardOpaque |
在不透明物体渲染之前 |
AfterForwardOpaque |
在不透明物体渲染之后(最常用) |
BeforeSkybox |
在天空盒之前 |
AfterSkybox |
在天空盒之后 |
BeforeImageEffects |
在后处理特效之前 |
4️⃣ 清除 & 更新 CommandBuffer
如果需要动态修改 CommandBuffer:
xxxxxxxxxx
commandBuffer.Clear(); // 清除之前的命令
commandBuffer.DrawMesh(newMesh, Matrix4x4.identity, newMaterial);
📌 Clear() vs Release():
| 方法 | 作用 |
|---|---|
Clear() |
清除指令,但 不释放 GPU 资源(可复用) |
Release() |
彻底删除 CommandBuffer,释放 GPU 资源 |
⚡ 三、实战案例
🎯 实战 1:绘制同心圆
目标:使用 CommandBuffer 画出 10m-1000m 的同心圆,并绘制 X/Z 轴辅助线。
x
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
using Battlehub.RTCommon;
public class DrawGrid : MonoBehaviour
{
public Material lineMaterial;
private CommandBuffer commandBuffer;
private Camera mainCamera;
private static readonly float[] radii = { 10, 20, 50, 100, 200, 500, 1000 };
void Start()
{
mainCamera = Camera.main;
commandBuffer = new CommandBuffer { name = "Draw Concentric Circles" };
// 画同心圆
foreach (float r in radii)
{
Mesh circle = GraphicsUtility.CreateWireCircle(r);
commandBuffer.DrawMesh(circle, Matrix4x4.identity, lineMaterial, 0, 0);
}
// 画X轴 & Z轴
Mesh axis = CreateWireCircle(1000, 2);
commandBuffer.DrawMesh(axis, Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 90, 0), Vector3.one), lineMaterial, 0, 0);
commandBuffer.DrawMesh(axis, Matrix4x4.identity, lineMaterial, 0, 0);
// 绑定到摄像机
mainCamera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, commandBuffer);
}
public static Mesh CreateWireCircle(float radius = 1, int pointsCount = 64)
{
return CreateWireArc(Vector3.zero, radius, pointsCount, 0, Mathf.PI * 2);
}
public static Mesh CreateWireArc(Vector3 offset, float radius = 1, int pointsCount = 64, float fromAngle = 0, float toAngle = Mathf.PI * 2)
{
Vector3[] vertices = new Vector3[pointsCount + 1];
List<int> indices = new List<int>();
for(int i = 0; i < pointsCount; ++i)
{
indices.Add(i);
indices.Add(i + 1);
}
float currentAngle = fromAngle;
float deltaAngle = toAngle - fromAngle;
float z = 0.0f;
float x = Mathf.Cos(currentAngle) * radius;
float y = Mathf.Sin(currentAngle) * radius;
Vector3 prevPoint = new Vector3(x, y, z) + offset;
for (int i = 0; i < pointsCount; i++)
{
vertices[i] = prevPoint;
currentAngle += deltaAngle / pointsCount;
x = Mathf.Cos(currentAngle) * radius;
y = Mathf.Sin(currentAngle) * radius;
Vector3 point = new Vector3(x, y, z) + offset;
vertices[i + 1] = point;
prevPoint = point;
}
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.SetIndices(indices.ToArray(), MeshTopology.Lines, 0);
return mesh;
}
void OnDestroy()
{
if (mainCamera != null)
{
mainCamera.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, commandBuffer);
commandBuffer.Release();
}
}
}
✅ 结果:场景中始终显示 同心圆 & X/Z 轴,不会随物体销毁而消失。
⏩ 四、优化 & 陷阱
✅ 最佳实践
- 避免每帧创建
CommandBuffer(使用Clear()代替Release())。 - 只创建一次
Mesh,避免动态生成(可以用Dictionary缓存)。 - 绑定
CommandBuffer到Camera,确保其不会被回收。
🚨 常见错误
| 问题 | 解决方案 |
|---|---|
每帧创建新 CommandBuffer |
改为 Clear(),只创建一次 |
Resource ID out of range |
确保 CommandBuffer 在 OnDestroy() 里释放 |
| 物体绘制后消失 | 绑定到 Camera,并确保 CommandBuffer 存在 |
🎯 总结
| 🎯 功能 | 🚀 核心 API |
|---|---|
创建 CommandBuffer |
new CommandBuffer { name = "MyBuffer" } |
| 绘制网格 | DrawMesh(mesh, transform, material) |
| 绑定到 Camera | Camera.main.AddCommandBuffer(event, buffer) |
| 清理 & 释放 | commandBuffer.Clear() / Release() |
通过掌握 CommandBuffer,你可以 高效渲染复杂图形,降低 DrawCall,让 Unity 项目运行更流畅! 🚀🔥
点赞鼓励下,(づ ̄3 ̄)づ╭❤~
作者:世纪末的魔术师
出处:https://www.cnblogs.com/Firepad-magic/
Unity最受欢迎插件推荐:点击查看
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号