应用阶段最后是CPU向GPU提交需要渲染的数据。通常数据会被复制到显存中,然后设置渲染参数,最后调用渲染接口。PC中是这样的,但是移动设备一般没有单独的显存。使用内存为GPU服务。他们使用同一内存地址。除非要读/写这段内存内容才会复制出一份调整CPU和GPU之间协作。
【从UnityURP开始探索游戏渲染】专栏-直达
渲染状态:
- 一连串开关或方法,以及方法的地址指向(阶段中的各种可配置的阶段等都是在这里)。SetPassCall。例如是否开启混合、用哪个纹理、哪个顶点着色器、是否背面剔除等,在Unity中则是ShaderLab语法规则中规定的各种标签。
渲染指令:
- 调用具体渲染的对象。drawcall是一个渲染指令,这个指令仅指向一连串图元(点线面 网格拆分后的状态),并不会包含任何其他材质信息。每个状态前都伴随着一连串渲染状态设置,所以渲染命令队列中,渲染状态和渲染指令是交替出现。
渲染命令队列:
- 其中包含渲染状态、渲染指令的缓冲区。CPU向缓冲区放入指令,GPU执行指令。CPU发送渲染状态后,CPU需要控制总线将数据从内存搬运到显存,搬运过程耗费大量时间。drawcall多了后会导致大量内存搬运,运行速度下降。
打包数据
模型信息
模型数据主要从网格资源(Mesh)中获取,包含以下核心属性:
- 顶点坐标描述模型局部空间的顶点位置坐标(x, y, z)。
- 法线信息定义顶点朝向,用于光照计算和表面平滑度。
- UV信息二维纹理映射坐标(u, v),范围[0,1],控制贴图在模型表面的分布。
- 切线向量与法线配合构建切线空间,用于法线贴图等高级渲染效果。
- 顶点颜色存储逐顶点颜色值,可用于特殊着色效果。
- 索引列表定义顶点连接顺序,优化绘制效率(减少重复顶点)
- 数据来源 :
-
由建模工具(如Blender/Maya)导出时生成,随模型文件(.fbx/.obj)导入Unity。
-
程序化网格通过
Mesh
类API动态设置(如mesh.vertices
,mesh.uv
) -
变换矩阵
矩阵数据由CPU计算并传递给GPU:
-
模型矩阵 Model Matrix(M) 模型局部坐标→世界坐标,由物体的
Transform
组件(位置/旋转/缩放)计算得出。- 计算顺序:缩放 → 旋转 → 平移(SRT)
-
视图矩阵 View Matrix(V) 世界坐标→摄像机坐标,基于
Camera
组件的位姿(位置/朝向/上方向)生成。- 计算原理:先逆平移(摄像机到原点),再逆旋转(对齐坐标轴)
-
投影矩阵 Projection Matrix(P) 摄像机坐标→齐次裁剪坐标,通过相机参数计算:
- 参数来源:FOV、近/远裁剪面、宽高比
- 透视投影(近大远小)或正交投影(等比例缩放)
Field of View (FOV)
:视角范围Near/Far Clipping Planes
:近远裁剪平面Aspect Ratio
:屏幕宽高比。
-
MVP矩阵 最终变换矩阵:
MVP = P × V × M
-
计算主体与存储位置
矩阵类型 计算者 存储位置(GPU端) 访问方式(Shader) 模型矩阵 (M) Transform组件 (CPU计算) unity_ObjectToWorld
UNITY_MATRIX_M
视图矩阵 (V) 摄像机组件 (CPU计算) unity_MatrixV
UNITY_MATRIX_V
投影矩阵 (P) 摄像机投影参数 (CPU计算) unity_MatrixP
UNITY_MATRIX_P
MVP矩阵 Shader运行时组合 无独立存储 mul(UNITY_MATRIX_VP, mul(UNITY_MATRIX_M, pos))
-
计算时机
- CPU端:每帧渲染前更新(物体Transform或摄像机移动时)。
- GPU端:通过
UNITY_MATRIX_VP
(视图投影矩阵)与UNITY_MATRIX_M
(模型矩阵)在顶点着色器动态组合
-
优化机制
-
URP预计算
VP矩阵
(视图投影联合矩阵),减少GPU计算量。 -
使用
UnityObjectToClipPos
内置函数直接完成MVP变换:hlslhlsl float4 clipPos = UnityObjectToClipPos(v.vertex); // 内部实现:mul(UNITY_MATRIX_VP, mul(UNITY_MATRIX_M, v.vertex))
-
-
-
灯光、材质参数
- 灯光参数
- 光源属性 :位置、颜色、强度、衰减等,源自场景中的
Light
组件。 - 阴影参数 :阴影强度、分辨率,通过URP光源设置(如
UniversalAdditionalLightData
)配置。
- 光源属性 :位置、颜色、强度、衰减等,源自场景中的
- 材质数据
- Shader与材质属性 :漫反射颜色、高光强度等,由
Material
实例定义。 - 纹理贴图 :通过材质绑定(如
_MainTex
),从纹理资源加载。
- Shader与材质属性 :漫反射颜色、高光强度等,由
数据传递流程:
应用阶段通过SetPassCall
设置渲染状态(Shader/材质),并通过DrawCall
提交图元列表
Batch:
- 把数据加载到显存,设置渲染状态,CPU调用GPU渲染的过程称之为一个Batch。
Unity URP渲染管线中的渲染状态和渲染命令的实现
一、渲染状态设置(SetPassCall)
-
ScriptableRenderer类
- 位于
UniversalRenderer.cs
中,负责管理URP的默认渲染流程。 - 调用
EnqueuePass
方法将渲染Pass(如DrawObjectsPass
)加入队列。
- 位于
-
CommandBuffer类
-
通过
CommandBufferPool.Get
获取实例,录制渲染指令。 -
关键方法:
csharpcsharp cmd.SetRenderTarget()// 绑定渲染目标 cmd.SetGlobalTexture()// 设置全局纹理 cmd.SetViewProjectionMatrices()// 设置VP矩阵
-
-
Material与Shader
- 材质状态通过
Material.SetPass
方法设置,触发底层SetPassCall
。 - URP通过
ShaderData
类管理着色器变体(Variant)的切换。
- 材质状态通过
二、图元提交(DrawCall)
-
ScriptableRenderContext类
-
核心方法
Submit
提交所有录制的CommandBuffer
到GPU。 -
调用链:
csharpcsharp context.ExecuteCommandBuffer(cmd);// 执行指令 context.DrawRenderers()// 触发DrawCall
-
-
DrawingSettings与FilteringSettings
-
在
DrawObjectsPass.Execute
中配置:csharpcsharp var drawSettings = new DrawingSettings(...);// 指定Shader Pass和排序var filterSettings = new FilteringSettings(...);// 设置渲染队列和层级 context.DrawRenderers(...);// 最终提交
-
-
Graphics.DrawMesh
- 直接提交网格数据的备选API,绕过URP流程但效率较低。
三、关键脚本位置
功能 | 脚本文件 | 核心方法 |
---|---|---|
渲染流程控制 | UniversalRenderer.cs |
AddRenderPasses , Execute |
指令录制 | CommandBuffer.cs |
Clear , DrawMesh , Blit |
材质状态管理 | Material.cs |
SetPass , SetShaderPassEnabled |
数据提交 | ScriptableRenderContext.cs |
Submit , DrawRenderers |
四、执行流程示例
csharp
csharp
// 在ScriptableRenderPass中实现
public override void Execute(ScriptableRenderContext context, ref RenderingData data) {
CommandBuffer cmd = CommandBufferPool.Get("CustomPass");
cmd.SetRenderTarget(...);// SetPassCall
cmd.DrawMesh(...);// DrawCall
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
Unity URP渲染管线中的应用阶段渲染命令队列及渲染队列实现
在Unity URP渲染管线的应用阶段,渲染命令队列包含一系列图形命令,主要用于调度和执⾏渲染操作,例如清除缓冲区、绘制几何体、设置材质和着色器参数、处理光源阴影,以及执行后处理效果。
这些命令通过ScriptableRenderContext
接口进行管理,该接口作为C#代码与Unity底层图形引擎的桥梁,确保命令按序列化顺序提交GPU处理。队列内容包括:
- 缓冲区清除命令(如颜色缓冲和深度缓冲)。
- 几何体绘制命令(调用
DrawMesh
或DrawProcedural
)。 - 状态设置命令(如设置视口、混合模式)。
- 阴影贴图生成命令(针对动态光源)。
- 后处理Pass(如抗锯齿或景深应用)。这些命令在每帧的渲染循环中被动态生成和执行,以支持前向渲染策略和性能优化。
URP中的渲染队列实现主要由ScriptableRenderPass
类完成,它定义了Pass的执行顺序和具体渲染逻辑。具体脚本流程如下:
渲染队列管理:
-
渲染队列 (如
RenderQueueRange.opaque
或RenderQueueRange.transparent
)在ScriptableRenderPass
的构造函数中指定,通过字段如renderPassEvent
控制Pass的执行时机(例如在相机渲染前或后)。 -
例如 ,一个基本的Pass脚本会继承自
ScriptableRenderPass
,并在其Configure方法中设置队列优先级:这里,Execute
方法包含具体命令队列的实现,使用CommandBuffer
来录制命令(如cmd.ClearRenderTarget
),并通过ScriptableRenderContext
提交。csharpcsharp public class CustomRenderPass : ScriptableRenderPass { public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { // 设置队列范围为不透明对象 renderPassEvent = RenderPassEvent.BeforeRenderingOpaques; } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { // 执行命令,如绘制或清除 CommandBuffer cmd = CommandBufferPool.Get(); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } }
脚本集成:
- 在
ScriptableRenderer
(如UniversalRenderer
)中,渲染队列通过m_ActiveRenderPassQueue
列表管理。 - Setup阶段调用
AddRenderPasses
方法收集所有关联的ScriptableRenderPass
实例(来自RendererFeature
),并按事件顺序排序;Execute
阶段遍历列表执行每个Pass的Execute
方法。 - 例如,
UniversalRenderPipeline.Render
方法驱动整个流程:此脚本位于URP核心程序集(如UniversalRenderPipeline.cs
),依赖于UniversalRenderPipelineAsset
提供配置
csharp
csharp
protected override void Render(ScriptableRenderContext context, List<Camera> cameras)
{
// 排序相机并逐个处理foreach (var camera in cameras)
{
var renderer = cameraData.renderer;
renderer.Execute(context, ref renderingData);// 执行Pass队列
}
}
在Unity URP中,ShaderLab配置的渲染状态(如剔除、深度测试、混合模式等)的处理流程
一、状态读取与存储机制
ShaderLab解析阶段
- URP通过
ShaderCompiler
解析ShaderLab代码,将Cull
、ZTest
、Blend
等指令转换为底层渲染状态标识符。 - 解析结果存储在
ShaderData
结构中,包含渲染状态变体(Variant)和材质属性。
GPU状态设置阶段
- 运行时由
CommandBuffer
录制指令(如cmd.SetRenderTarget
、cmd.SetGlobalDepthBias
),通过ScriptableRenderContext.Submit
提交到GPU。 - 关键存储位置:
- 剔除模式 :存于
RenderStateBlock.cullMode
,通过DrawingSettings
传递给DrawRenderers
调用。 - 深度测试/写入 :通过
DepthState
结构(含ZWrite
、ZTest
)配置,最终写入GPU深度缓冲区。 - 混合模式 :由
BlendState
管理(含BlendOp
、SrcFactor
等参数),绑定到渲染管线状态。
- 剔除模式 :存于
URP运行时管理
UniversalRenderer
在AddRenderPasses
阶段收集所有Pass的渲染状态,合并到RenderStateBlock
。- 通过
MaterialPropertyBlock
动态覆盖材质属性(如运行时修改_ZWrite
)。
二、关键脚本与调用链
功能 | 脚本/类 | 核心方法 | 数据流向 |
---|---|---|---|
Shader解析 | ShaderCompiler |
CompileShader |
ShaderLab → ShaderData |
状态录制 | CommandBuffer |
SetRenderState |
CPU → GPU指令队列 |
Pass执行 | DrawObjectsPass |
Execute |
通过DrawingSettings 传递状态 |
动态修改 | MaterialPropertyBlock |
SetFloat /SetInt |
运行时覆盖Shader参数 |
三、使用示例(URP中动态修改深度测试)
hlsl
hlsl
// ShaderLab中声明深度测试
SubShader {
Pass {
ZWrite On
ZTest LEqual
}
}
-
运行时读取 :通过
Material.GetInt("_ZWrite")
获取状态。 -
动态修改:
csharpcsharp var block = new MaterialPropertyBlock(); block.SetInt("_ZWrite", 0);// 禁用深度写入 renderer.SetPropertyBlock(block);
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)