这是个人学习笔记,有错欢迎指出
参考学习:Custom Render Pipeline by catlikecoding
新的渲染管线
1.要渲染任何东西,Unity必须知道绘制的形状、什么时候渲染、在哪里渲染还有相关的设置,还有一些灯光、阴影、透明度、图片的效果、volumetric effects(体积效果)
2.Unity2018出了实验性的the Lightweight RP and the High Definition RP.
3.Unity 2019 出了通用渲染管线 the Universal RP in Unity 2019.3
The Universal RP
1.设计URP的目的: to replace the current legacy RP as the default.
2.主要思想:it is a one-size-fits-most RP that will also be fairly easy to customize. 就是可以适合大多数RP但是可以方便自定义
3.这个系列要制作一个完整的RP
创建步骤
1.基础搭建
1.1 将颜色色彩空间改为线性空间
gamma和linear的区别目前还没有弄清楚
目前的理解是:数值上的颜色变换,将数值做一个映射
1.2.在场景中添加一些物体
材质有:standard, unlit opaque and transparent materials.
Unlit/Transparent shader的UV使用材质贴图
红色的为standard,蓝色的为standard,并且把Rendering Mode设置为Transparent,绿色的shader为Unlit/Color,白色的为Unlit/Transparent
1.3 创建脚本CustomRenderPipelineAsset
在脚本中添加如下代码:
using UnityEngine;
using UnityEngine.Rendering;
[CreateAssetMenu(menuName ="Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset
{
protected override RenderPipeline CreatePipeline()
{
return null;
}
}
1.Rendering pipeline asset的作用是:获取负责渲染的管线对象实例
2.asset本身是一个handle(句柄)和储存settings(设置)的地方
3.RenderPipeline函数返回的是 RenderPipeline的实例,目前先返回null
4.我们需要向项目中添加这种类型的资产,做法是在头部添加CreateAssetMenu
更改pipeline settings
在edit/project settings/Graphics中添加新设置的RP
目前为止:
- Graphics中的设置选项变少
- Game/Scene窗口是黑色的
1.4 Render Pipeline Instance
同样创建一个CustomRenderPipeline脚本
添加代码
using UnityEngine;
using UnityEngine.Rendering;
public class CustomRenderPipeline : RenderPipeline
{
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
}
}
什么是ScriptableRenderContext?
官网解释:ScriptableRenderContext
主要:用于设置要提交给GPU的状态和绘制命令
之后把CustomRenderPipelineAsset的CreatePipeline()的return项改为改为
return new CustomRenderPipeline();
2.渲染
每一帧Unity都在RP实例上调用Render。它传递了一个上下文结构(context struct),该结构提供了到本机引擎的连接,我们可以用它来渲染。它还传递一个摄像机数组,因为场景中可能有多个活动摄像机。RP的负责按照提供的顺序渲染这些摄像机
2.1 Camera Renderer
创建一个CameraRenderer脚本
每个摄像机渲染是独立的,所以新建一个脚本,再自定义一个Render的方法
using UnityEngine;
using UnityEngine.Rendering;
public class CameraRenderer {
ScriptableRenderContext context;
Camera camera;
public void Render (ScriptableRenderContext context, Camera camera) {
this.context = context;
this.camera = camera;
}
}
在CustomRenderPipeline中创建
在CustomRenderPipeline渲染管线中,遍历ScriptableRenderContext与camera
CameraRenderer renderer = new CameraRenderer();
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
foreach(Camera camera in cameras)
{
renderer.Render(context, camera);
}
}
2.2 绘制天空盒
1.CameraRenderer.Render的工作是负责渲染所有摄像机视角范围内的物体,因此我们使用DrawVisibleGeometry完成这个特定的任务
2.调用context.DrawSkybox(camera);可以完成这个任务
3.还需要在最后调用context.submit来完成,因为context.DrawSkybox(camera)是缓存的。
注:在Window/Analysis/Frame Debug下查看以下窗口
CameraRenderer代码如下:
public void Render(ScriptableRenderContext context, Camera camera)
{
this.context = context;
this.camera = camera;
DrawVisibleGeometry();
Submit();
}
void Submit()
{
context.Submit();
}
void DrawVisibleGeometry()
{
context.DrawSkybox(camera);
}
效果:终于出来了!
补一个setup
为了正确渲染天空盒:我们必须设置视图投影矩阵。这个变换矩阵结合了相机的位置和方向-视图矩阵- 和相机的透视或正投影---- 投影矩阵。它在着色器中被称为 unity _ matrixvp
Setup();
DrawVisibleGeometry();
Submit();
}
void Setup()
{
context.SetupCameraProperties(camera);
}
2.2 Command Buffers
1.有些任务(比如绘制 skybox)可以通过专用方法发出,但其他命令必须通过单独的命令缓冲区间接发出。我们需要这样一个缓冲区来绘制场景中的其他几何图形
2.在创建新对象时,可以将代码块附加到构造函数的调用。然后就可以在块中设置对象的字段和属性,而不必显式地引用对象实例。(还未理解,这里讲的是下面代码的合理性)
//CameraRenderer
const string bufferName = "Render Camera";
CommandBuffer buffer = new CommandBuffer //创建缓冲区
{
name = bufferName
};
我们可以使用命令缓冲区来注入分析器示例(inject profiler samples),这将同时显示在分析器和帧调试器中。这是通过在适当的点调用BeginSample和EndSample来完成的
void Setup()
{
buffer.BeginSample(bufferName); //新添加
ExecuteBuffer(); //新添加
context.SetupCameraProperties(camera);
}
void Submit()
{
buffer.EndSample(bufferName);//新添加
ExecuteBuffer();//新添加
context.Submit();
}
void ExecuteBuffer()
{
context.ExecuteCommandBuffer(buffer);//新添加
buffer.Clear();//新添加
}
1.To guarantee proper rendering we have to clear the render target to get rid of its old contents.清屏操作
2.要注意这几个语句的顺序,比如:
(1)清屏之前需要设置摄像机,
(2)buffer.ClearRenderTarget要在buffer.BeginSample之前,不然会造成嵌套的重复
void Setup()
{
context.SetupCameraProperties(camera);
buffer.ClearRenderTarget(true, true, Color.clear);
buffer.BeginSample(bufferName);
ExecuteBuffer();
}
2.3 Culling
1.我们要剔除摄像机视角外的物体
2.要想确定哪些内容可以被剔除,我们需要跟踪多个摄像机设置和矩阵,为此我们可以使用ScriptableCullingParameters结构体
3.It returns whether the parameters could be successfully retrieved返回信息是否被成功检索
4.out是引用对象,指向参数所在的内存堆栈上的位置。当方法更改参数时,它影响的是该值,而不是副本。
CullingResults cullingResults;
public void Render(ScriptableRenderContext context, Camera camera)
{
this.context = context;
this.camera = camera;
if(!cull())
{
return;
}
...
}
bool cull()
{
if(camera.TryGetCullingParameters(out ScriptableCullingParameters p))
{
cullingResults = context.Cull(ref p);
return true;
}
return false;
}
2.4 几何体绘制
FilteringSettings:描述如何过滤要渲染的给定可见对象集
DrawingSettings :用哪个着色器(shaderPassName)和对可见对象排序(sortingSettings)
criteria的作用:看渲染顺序,会发现透明物体的显示有问题,这是因为目前场景中物体的渲染顺序是随机的,如果想正确显示透明物体,我们需要在所有不透明物体之后渲染。我们可以在sortingSettings的criteria属性中进行设置。
static ShaderTagId unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit");//着色器标记 ID
void DrawVisibleGeometry()
{
var sortingSettings = new SortingSettings(camera)
{
criteria = SortingCriteria.CommonOpaque
};
var drawingSettings = new DrawingSettings(unlitShaderTagId,sortingSettings);
var filteringSettings = new FilteringSettings(RenderQueueRange.all);
...
}
2.5 Drawing Opaque and Transparent Geometry Separately(分开渲染透明和不透明的)
问题:在绘制天空盒后,透明物体看不到
原因:这是因为透明着色器不会写入深度缓冲区。它们不会隐藏身后的东西,因为我们可以看穿它们。
解决方案是先画不透明的物体,然后是天空盒,然后才是透明的物体。
调整顺序如下:
//顺序:不透明->天空盒->透明
var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonOpaque };
var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings); //定义绘制选择
var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
context.DrawSkybox(camera);
//透明
sortingSettings.criteria = SortingCriteria.CommonTransparent;
drawingSettings.sortingSettings = sortingSettings;
filteringSettings.renderQueueRange = RenderQueueRange.transparent;
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
3.Editor Rendering
3.1 Drawing Legacy Shaders
1.为了覆盖所有Unity的默认着色器,我们必须为Always, ForwardBase, PrepassBase, Vertex, VertexLMRGBM和VertexLM通道使用着色器标签id。在静态数组中加入
2.绘制不支持的着色器:我们可以通过 FilteringSettings.defaultValue 属性获得默认的过滤设置
static ShaderTagId[] legacyShaderTagIds = {
new ShaderTagId("Always"),
new ShaderTagId("ForwardBase"),
new ShaderTagId("PrepassBase"),
new ShaderTagId("Vertex"),
new ShaderTagId("VertexLMRGBM"),
new ShaderTagId("VertexLM")
};
public void Render(ScriptableRenderContext context, Camera camera)
{
...
Setup();
DrawVisibleGeometry();
DrawUnsupportedShaders(); //新加
Submit();
...
}
void DrawUnsupportedShaders() //绘制所有不支持的着色器
{
var drawingSettings = new DrawingSettings(
legacyShaderTagIds[0], new SortingSettings(camera)
);
for (int i = 1; i < legacyShaderTagIds.Length; i++)
{
drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
}
var filteringSettings = FilteringSettings.defaultValue;
context.DrawRenderers(
cullingResults, ref drawingSettings, ref filteringSettings //可以通过 FilteringSettings.defaultValue 属性获得默认的过滤设置
);
}
用标准着色器渲染的物体出现了,但它们现在是纯黑色的,因为RP还没有为它们设置所需的着色器属性
3.2 Error Material
为了清楚地指出哪些对象使用了不支持的着色器,我们将用Unity的错误着色器(Hidden/InternalErrorShader)绘制它们。用shader作为参数构造一个新的材质,我们可以通过Find.shader来找到它。使用Hidden/InternalErrorShader字符串作为参数进行查找。
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
};
...
}
添加partial
1.这是一种将类(或结构)定义拆分为多个部分、存储在不同文件中的方法。
2.唯一的目的是组织代码。典型的用例是将自动生成的代码与手动编写的代码分开。就编译器而言,它们都是同一个类定义的一部分
public partial class CameraRenderer
添加脚本CameraRenderer.Editor
把static ShaderTagId[] legacyShaderTagIds、static Material errorMaterial、DrawUnsupportedShaders() 移到新的脚本中
//注意3个partial的位置
partial class CameraRenderer
{
partial void DrawUnsupportedShaders();
#if UNITY_EDITOR
static ShaderTagId[] legacyShaderTagIds = {...};
static Material errorMaterial;
partial void DrawUnsupportedShaders() {...} //绘制所有不支持的着色器
#endif
3.3 Drawing Gizmos
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor;
partial class CameraRenderer
{
partial void DrawGizmos();
...
#if UNITY_EDITOR
...
partial void DrawGizmos () {
if (Handles.ShouldRenderGizmos()) {
context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
}
}
...
#endif
CameraRenderer中添加
public void Render(ScriptableRenderContext context, Camera camera)
{
...
Setup();
DrawVisibleGeometry();
DrawUnsupportedShaders();
DrawGizmos(); //新加
Submit();
...
}
3.4 Drawing Unity UI
添加UI后发现,在scene中看不到,而在game视图中看得到,UI是单独渲染的
解决方法:We have to explicitly add the UI to the world geometry when rendering for the scene window
partial void PrepareForSceneWindow();
#if UNITY_EDITOR
...
partial void PrepareForSceneWindow()
{
if (camera.cameraType == CameraType.SceneView)
{
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
}
}
public void Render(ScriptableRenderContext context, Camera camera)
{
this.context = context;
this.camera = camera;
PrepareForSceneWindow();
if (!cull())
4.Multiple Cameras
4.1 Two Cameras
在场景中可以有多个活动的摄像机。如果是这样的话,我们必须确保它们一起工作。
partial void PrepareBuffer ();
#if UNITY_EDITOR
…
partial void PrepareBuffer () {
buffer.name = camera.name;
}
#endif
PrepareBuffer();
PrepareForSceneWindow();
4.2 Dealing with Changing Buffer Names
尽管帧调试器现在在每个摄像头上显示一个单独的样本层次结构,但当我们进入游戏模式时,Unity的控制台将会被警告我们BeginSample和EndSample计数必须被匹配的消息所填满。
这一部分目前没有看懂,先复制上来等以后再分析
using UnityEngine.Profiling;
#if UNITY_EDITOR
…
string SampleName { get; set; }
…
partial void PrepareBuffer () {
Profiler.BeginSample("Editor Only");
buffer.name = SampleName = camera.name;
Profiler.EndSample();
}
#else
const string SampleName = bufferName;
#endif
void Setup () {
context.SetupCameraProperties(camera);
buffer.ClearRenderTarget(true, true, Color.clear);
buffer.BeginSample(SampleName);
ExecuteBuffer();
}
void Submit () {
buffer.EndSample(SampleName);
ExecuteBuffer();
context.Submit();
}
4.3 Layers
摄像头也可以设置成只能看到特定层面上的东西。这是通过调整他们的culling mask来完成的。
步骤:
1.设置物体的Layer为Ignore Raycaster
2.设置Main camera的Culling Mask不包含Ignore Raycaster
3.设置Secondary camera的Culling Mask只有Ignore Raycaster
效果:game视图只有一个正方体
4.4 Clear Flags
1.我们可以通过调整第二个渲染的clear flags来结合两个相机的结果。
2.它们是由 CameraClearFlags enum 定义的,我们可以通过相机的 clearFlags 属性获取它们。
3.在清除之前在安装程序中执行此操作。
void Setup()
{
context.SetupCameraProperties(camera);
CameraClearFlags flags = camera.clearFlags;
buffer.ClearRenderTarget(flags <= CameraClearFlags.Depth, flags == CameraClearFlags.Color, flags == CameraClearFlags.Color ?
camera.backgroundColor.linear : Color.clear);
buffer.BeginSample(SampleName);
ExecuteBuffer();
}
感动~~第一小节完成,撒花★,°:.☆( ̄▽ ̄)/$:.°★ 。
2021-07-23