来源:
https://edu.uwa4d.com/lesson-detail/282/1308/0?isPreview=false
文章目录
- 来源:
- 自定义渲染管线
- 前置工作
- 渲染管线资产
- 渲染管线实例
- 正式渲染
- CommandBuffer
- 清除渲染目标
- 剔除(Culling)
- 绘制
- 绘制集合体
- 透明和不透明物体分开绘制
- 编辑器优化
- 绘制SRP不支持的着色器类型
- 使用Unity的ErrorShader来绘制不支持的着色器
- 将Unity编辑器中使用的代码单独放在一个局部类中管理
- 绘制辅助线框
- UI绘制(在Scene视图中把UI绘制出来)
- 多摄像机
- 多个摄像机渲染不同类型的物体Culling Mask
- 多个摄像机渲染结果的混合Clear Flags
自定义渲染管线
前置工作
下载 Core RP Library 库。SRP、URP、HDRP都是依据该包进行拓展的,它是Unity开放出来供我们使用调用的C#接口,通过调用更底层的C++提供的渲染接口。包中还包括一些基本的着色器文件。
渲染管线资产
using UnityEngine.Rendering;
继承RenderPipelineAsset
,即可成为一个资产管理资源文件的定义文件。
继承后需要重写CreatePipeline()函数,返回一个RenderPipeline 【渲染管线实例】;
protected override RenderPipeline CreatePipeline()
{
return new CustomRenderPipeline();
}
记得将资产管理资源添加到菜单栏
//该类作为一个管线资产,可通过如下菜单创建
[CreateAssetMenu(menuName = "Rendering/CreateCustomRenderPipeline")]
之后在unity中创建该资源文件,并将该资源文件添加到Editor->Project Setting->Graphics->Scriptable Render Pipeline Setting中即可创建成功。
渲染管线实例
新建渲染管线脚本文件,继承RenderPipeline,实现方法Render。
public class CustomRenderPipeline : RenderPipeline
{
CameraRenderer renderer = new CameraRenderer();
/// <summary>
/// Unity每帧都会调用CustomRenderPipeline实例的Render()方法进行画面渲染
/// 是SRP的入口
/// </summary>
/// <param name="context"> 传入的当前上下文 </param>
/// <param name="cameras"> 参与这一帧渲染的所有对象 </param>
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
foreach (var camera in cameras)
{
renderer.Render(context, camera);
}
}
}
在渲染管线资产中中返回该渲染管线实例类的一个实例即可。
Unity每帧都会调用实例的Render方法进行画面渲染,Render()方法是SRP的入口,进行渲染时底层接口会调用它并传递两个参数,一个是ScriptableRenderContext渲染上下文,一个是Camera[]对象,储存了参与这一帧渲染的所有相机对象
正式渲染
调用ScriptableRenderContext.DrawSkybox来绘制一个天空盒
因为通过context发送的渲染命令都是缓存,所以需要通过调用Submit()方法来正式提交渲染命令。
// 设置相机view属性
context.SetupCameraProperties(camera);
context.DrawSkybox(camera);
context.Submit();
CommandBuffer
控制Unity渲染流程的一种手段。一些命令需要单独的命令缓冲区简介发出,CommandBuffer是一个容器,保存了这些将要执行的渲染命令。
新建CommanfBuffer
const string bufferName = "Render Camera";
CommandBuffer commandBuffer = new CommandBuffer//是一个容器,保存将要执行的渲染命令
{
//给缓冲区起个名字,用于在Frame Debugger中识别他
name = bufferName
};
开启采样过程,这样就可以在Frame Debugger中显示,通常放在整个渲染过程的开始和结束。
commandBuffer.BeginSample(bufferName);
context.ExecuteCommandBuffer(commandBuffer);//执行缓存区命令
commandBuffer.Clear();
渲染设置...
渲染命令...
commandBuffer.EndSample(bufferName);
context.ExecuteCommandBuffer(commandBuffer);
commandBuffer.Clear();
命令提交...
清除渲染目标
buffer.BeginSample();
//*************************************
//buffer.ClearRenderTarget(是否清除深度,是否清除颜色,清除颜色数据的颜色);
buffer.ClearRenderTarget(true,true,Color.clear);
//*************************************
context.ExecuteCommandBuffer(commandBuffer);
commandBuffer.Clear();
......
ClearRenderTarget操作会自动包裹在一个使用命名缓冲区名字的样本条目中,因此一般不包含在其他命名缓冲区之内。
上段代码修改为
//*************************************
buffer.ClearRenderTarget(true,true,Color.clear);
//*************************************
buffer.BeginSample();
context.ExecuteCommandBuffer(commandBuffer);
commandBuffer.Clear();
......
然而,在帧调试中,Clear操作显示为Draw GL条目,而不是Clear。原因是我们需要提前设置Camera的属性,才能让Unity知道要调用Clear的硬件方法,而不是Hidden/InternalClear,SubShader #0着色器。
剔除(Culling)
剔除在相机渲染 Render() 最开始执行。
通过camera.TryGetCullingParameters(out p)
得到需要进行剔除检查的所有物体,正式的剔除通过context.Cull()实现,最后会返回一个CullingResults结构,里面存储了我们相机剔除后所有视野内可见的物体的数据信息。
CullingResults cullingResults;
bool Cull(){
ScriptableCullingParameters p;
if(camera.TryGetCullingParameters(out p)){
cullingResults = context.Cull.cull(ref p);
return true;
}
return false;
}
绘制
当剔除完毕后,我们就知道需要渲染那些可见物体了。
正是渲染需要调用context.DrawRenderers方法实现。
绘制集合体
static ShaderTagId unlitShaderTagID = new ShaderTagId("SRPDefaultUnlit");//渲染使用的Shader
private void DrawVisibleGeometry()
{
// 设置绘制顺序和指定渲染相机
var sortingSettings = new SortingSettings(camera)
{
criteria = SortingCriteria.CommonOpaque//不透明对象的典型排序模式
};
// 设置渲染的 Shader Pass 和排序模式
var drawingSettings = new DrawingSettings(unlitShaderTagID,sortingSettings);//使用哪个ShaderID,以什么一定顺序渲染的设定
// 设置哪些类型的渲染队列可以被绘制
var filteringSettig = new FilteringSettings(RenderQueueRange.all);//过滤给定的渲染对象,这里使用all渲染所有对象
//图像绘制
context.DrawRenderers(cullingResults,ref drawingSettings,ref filteringSettig);
context.DrawSkybox(camera);
}
透明和不透明物体分开绘制
先绘制不透明物体,首先将过滤设置设置为Opaque,渲染不透明物体。
在渲染天空盒
最后渲染透明物体
private void DrawVisibleGeometry()
{
// 设置绘制顺序和指定渲染相机
var sortingSettings = new SortingSettings(camera)
{
criteria = SortingCriteria.CommonOpaque//不透明对象的典型排序模式
};
// 设置渲染的 Shader Pass 和排序模式
var drawingSettings = new DrawingSettings(unlitShaderTagID,sortingSettings);//使用哪个ShaderID,以什么一定顺序渲染的设定
// 设置哪些类型的渲染队列可以被绘制
//var filteringSettig = new FilteringSettings(RenderQueueRange.all);//过滤给定的渲染对象,这里使用all渲染所有对象
var filteringSetting = new FilteringSettings(RenderQueueRange.opaque);//过滤出不透明物体
//图像绘制(不透明物体)
context.DrawRenderers(cullingResults,ref drawingSettings,ref filteringSetting);
context.DrawSkybox(camera);
// 绘制透明物体
sortingSettings.criteria = SortingCriteria.CommonTransparent; //透明对象的典型排序模式
drawingSettings.sortingSettings = sortingSettings;
// 过滤出透明物体
filteringSetting.renderQueueRange = RenderQueueRange.transparent;
// 绘制
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSetting);
}
渲染透明物体要注意渲染顺序,这里设置渲染顺序为SortingCriteria.CommonTransparent
。
编辑器优化
绘制SRP不支持的着色器类型
SRP不支持的着色器类型
static ShaderTagId[] legacyShaderTagId =
{
new ShaderTagId("Always"),
new ShaderTagId("ForwardBase"),
new ShaderTagId("PrepassBase"),
new ShaderTagId("Vertex"),
new ShaderTagId("VertexMRGBM"),
new ShaderTagId("VertexLM"),
};
private void DrawUnsupportedShaders()
{
var drawingSettings = new DrawingSettings(legacyShaderTagId[0], new SortingSettings(camera));
for (int i = 1; i < legacyShaderTagId.Length; i++)
{
drawingSettings.SetShaderPassName(i, legacyShaderTagId[i]);
}
var filteringSettings = FilteringSettings.defaultValue;
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
}
使用函数DrawUnsupportedShaders();
绘制不支持的材质渲染
public void Render(ScriptableRenderContext context, Camera camera)
{
...
SetUp();
DrawVisibleGeometry();
DrawUnsupportedShaders();
...
}
使用Unity的ErrorShader来绘制不支持的着色器
static Material errorMaterial;
void DrawUnsupportedShaders(){
if(errorMaterial == null)
{
errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
}
var drawingSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))
{
overrideMaterial = errorMaterial
};
}
将Unity编辑器中使用的代码单独放在一个局部类中管理
一些代码只适合调试时使用,但不会在发行版中运行,因此使用 动静代码分离:局部类 将代码进行分离管理。
使用partial
分离class
,将编辑器部分分离出来。
using UnityEngine;
using UnityEngine.Rendering;
public partial class CameraRenderer
{
partial void DrawUnsupportedShaders();
// build and run 时 不运行这部分
#if UNITY_EDITOR
static ShaderTagId[] legacyShaderTagIds =
{
new ShaderTagId("Always"),
new ShaderTagId("ForwardBase"),
new ShaderTagId("PrepassBase"),
new ShaderTagId("Vertex"),
new ShaderTagId("VertexMRGBM"),
new ShaderTagId("VertexLM"),
};
static Material errorMaterial;
/// <summary>
/// 使用Unity内置ErrorShader。
/// </summary>
partial void DrawUnsupportedShaders()
{
if (errorMaterial == null)
{
errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
}
var drawingSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))
{
overrideMaterial = errorMaterial
};
for (int i = 1; i < legacyShaderTagIds.Length; i++)
{
drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
}
var filteringSettings = FilteringSettings.defaultValue;
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
}
#endif
}
对编辑器内使用的函数代码,使用#if UNITY_EDITOR
#endif
将其包入其中。注意,若将函数定义在宏编译内,在编译条件不通过时,该函数如果在其他函数内调用,则会报错。使用partial添加函数定义,即可。
如:partial void DrawUnsupportedShaders();
绘制辅助线框
未绘制辅助线框
绘制辅助线框
绘制辅助线框代码
#if UNITY_EDITOR
...
partial void DrawGizmos()
{
if (Handles.ShouldRenderGizmos())
{
context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
}
}
#endif
UI绘制(在Scene视图中把UI绘制出来)
如果将 Canvas 的 RenderMode 设置为 ScreenSpace Overlay 时
UI不是由我们管线进行绘制的,而是单独绘制的。
如果我们将 Canvas 的 RenderMode 设置为 ScreenSpace Camera 时
UI绘制由我们管线管理。
调用如下函数,即可将Games视图下的UI绘制到Scene视图下。
partial void PrepareForSceneWindow()
{
if (camera.cameraType == CameraType.SceneView)
{
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
}
}
注意:调用PrepareForSceneWindow
函数需要在剔除之前,因为UI会在场景中添加几何体,这个集合体可能会被剔除操作剔除。
多摄像机
多摄像机绘制。绘制顺序按照深度递增渲染。【先绘制小数,再绘制大数】。
多个摄像机渲染不同类型的物体Culling Mask
需要使用Layer
将物体进行分类。使用摄像机的Culling Mask
剔除不需要渲染的可见物。
多个摄像机渲染结果的混合Clear Flags
将第二个 Camera 的 clearFlag 设置为Depth Only
,然后根据该设置写代码:
private void SetUp()
{
// 设置相机属性
context.SetupCameraProperties(camera);
// 得到Camera的CameraClearFlags对象
CameraClearFlags flags = camera.clearFlags;
//设置相机清除状态
commandBuffer.ClearRenderTarget(flags <= CameraClearFlags.Depth,//Skybox,Color,Depth 无论哪一个都要清理深度缓存
flags == CameraClearFlags.Color,
flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear);
}
根据camera的Clear Flags修改帧缓存的深度缓存,颜色缓存是否重置。
- 若设置为
Depth Only
,则只重置深度,颜色不变,这样就可以将上一个摄像机的渲染作为这个摄像机的背景。 - 若设置为
Don't Clear
,则深度,颜色都不重置,这样相当于在原有帧数据的基础上继续渲染【只修改了摄像机数据】。