绘制物体
绘制物体,包括不透明的物体,透明物体,再加上之前的天空盒
Camera
void DrawVisibleGeometry()
{
//我们需要将不透明物体和透明物体分开绘制
//如果我们直接先绘制所有的物体,然后再绘制天空盒,我们就会看到对于透明的物体,如果其对于相机视线后面没有不透明的物体的话,就会被天空球给遮挡住
//我们的透明shader没有写入深度缓冲,绘制天空盒时,在这个片元上如果没有不透明的物体写入过深度的话,天空盒的绘制就会直接覆盖掉
//所以我们的绘制顺序是先不透明物体,再绘制天空盒,最后绘制透明物体
//先进行不透明物体的绘制
var sortingSettings = new SortingSettings(camera) //相机的透明排序取决于使用的是正交还是基于距离的排序
{
// 不给定排序规则的话,绘制排序是随机混乱的
// 这个排序是从前往后的,因为对于后面的不透明物体,当检测到其的深度大于缓冲区的深度时,说明被挡住了,可以直接丢弃不绘制
// 而如果从后往前的话,就会需要不断的覆盖绘制,性能不如从前往后
criteria = SortingCriteria.CommonOpaque,
};
var drawSettings = new DrawingSettings(unlitShaderTagId, sortingSettings); //DrawingSettings主要用于对物体的渲染顺序和使用哪个Shader Pass
var filteringSettings = new FilteringSettings(RenderQueueRange.opaque); //FilteringSettings主要决定哪些物体渲染,哪些不渲染根据LayerMask,RenderQueue,sortingLayer等
//在Cull中我们得到了cullingResults,因此我们知道了哪些物体是需要绘制的
context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
context.DrawSkybox(camera); //添加绘制Skybox的命令
// 再进行透明物体的绘制
// 这个绘制顺序是从后往前的,与不透明的绘制顺序是相反的
// 不透明的物体并不写入深度缓冲,因此从前往后并没有性能更优
// 而且透明物体之间需要进行混合,从后往前才能得到正确的混合
// 但事实上并不能完全保证正确,因为排序是基于整个物体的位置的,但是对于复杂的大型物体间,可能会有部分穿插,有部分在前,有部分在后,这样子就会得到错误的效果
sortingSettings.criteria = SortingCriteria.CommonTransparent;
drawSettings.sortingSettings = sortingSettings;
filteringSettings.renderQueueRange = RenderQueueRange.transparent;
context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
}
//使用Unity默认的一些Shader绘制出使用了不支持的Shader的物体
partial void DrawUnsupportedShaders() //如果不改成分部方法的定义和实现声明,打包就会出错,因为这部分代码是仅在编辑器下使用的
{
if (errorMaterial == null)
{
errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
}
//我们将不支持的Shader用Unity默认的设置直接绘制出来
//没有设置好Shader的属性之前,绘制出来的物体是黑色的
var drawSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))
{
overrideMaterial = errorMaterial, //使用内置错误材质来绘制所有不支持的Shader的物体
}
;
for (int i = 1; i < legacyShaderTagIds.Length; i++)
{
drawSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
}
var filteringSettings = FilteringSettings.defaultValue;
context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
}
没有对绘制排序前
排序后
将不透明物体和透明物体分开渲染
先用默认Shader渲染不支持的Shader
用内置的错误材质在编辑器下绘制
分部代码,让代码更清晰,且可以让Editor代码不会出现打包错误
绘制Gizmos
partial void DrawGizmos()
{
if (Handles.ShouldRenderGizmos()) //获取Gizmos是否开启
{
context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
}
}
绘制Gizmos前
绘制Gizmos后
UI
如果是Overlay的UI,则不受我们的管线影响
改成ScreenCamera或者WorldSpaceCamera后
但是Scene中没有UI
添加绘制Scene视图的代码后
partial void PrepareForSceneWindow()
{
if (camera.cameraType == CameraType.SceneView)
{
//绘制Scene视图中的UI
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
}
}
多相机
添加新的相机
两个相机的绘制在FrameDebuger中都混在了一起
我们对每个相机单独进行Sample
partial void PrepareBuffer()
{
//我们将Buffer的名称修改为相机名称,这样子就可以在FrameDebuger中看到相机对应的渲染在自己的组中,查看会比较清晰
Profiler.BeginSample("Editor Only"); //这里会产生GC,会混下我们性能分析,所以直接标明是Editor Only
buffer.name = SampleName = camera.name;
Profiler.EndSample();
}
有多余的GC
把Editor下的GC单独分开
相机的ClearFlags
根据相机的ClearFlags的设置去调用buffer.ClearRenderTarget,主要就是决定是否清除颜色缓冲,深度缓冲
var flags = camera.clearFlags;
//ClearRenderTarget需要在SetupCameraProperties之后进行Execute,不然Clear的方式会变成Draw GL(可以在FrameDebuger中看到)
//Draw GL是使用Hidden/InternalClear Shader绘制了一个全屏的Quad来达到清除的效果,这种方式不是性能最好的
//ClearRenderTarget需要在BeginSample之前,不然FrameDebuger中会被多一次嵌套在"Render Camera"中
buffer.ClearRenderTarget(
flags != CameraClearFlags.Nothing,
flags == CameraClearFlags.Color, //为什么可以不用管Skybox时的清理颜色缓冲?因为当选择Skybox时,会清理深度缓冲,且需要绘制Skybox,没有深度缓冲此时一定会覆盖掉之前的颜色,相当于清理了颜色缓冲
flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear //我们用的是线性空间,所以当要使用背景颜色时,需要将其转化为线性空间的颜色
);
Solid Color
Don’t Clear
改变Viewport后的Skybox
改变Viewport后的SolidColor
改变Viewport后的Depth Only
改变Viewport后的Don’t Clear
没有改变Viewport之前是直接用Clear的方式
改变Viewport后,由于需要将清理的范围限定在Viewport中,并非整个屏幕,因此使用Shader绘制的方式清理,将范围限定
参考
本文主要学习自:https://catlikecoding.com/unity/tutorials/custom-srp/custom-render-pipeline/
catlikecoding是大神的博客,里面有很多教程,膜拜大神,感恩大神。
具体代码
CustomRenderPipeline.cs
using UnityEngine;
using UnityEngine.Rendering;
public class CustomRenderPipeline : RenderPipeline
{
CameraRenderer cameraRenderer = new CameraRenderer();
//每一帧渲染调用
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
foreach(var camera in cameras)
{
cameraRenderer.Render(context, camera);
}
}
}
CustomRenderPipelineAsset.cs
using UnityEngine;
using UnityEngine.Rendering; //需要使用UnityEngine.Rendering命名空间
//用于存储自定义渲染管线的设置
[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset //需要继承自RenderPipelineAsset
{
protected override RenderPipeline CreatePipeline()
{
return new CustomRenderPipeline();
}
}
CameraRenderer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
//用于进行单个相机的画面渲染
public partial class CameraRenderer
{
static ShaderTagId unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit");
ScriptableRenderContext context;
Camera camera;
CullingResults cullingResults;
const string bufferName = "Render Camera";
CommandBuffer buffer = new CommandBuffer
{ //创建实例时可以这样直接初始化
name = bufferName,
};
public void Render(ScriptableRenderContext context, Camera camera)
{
this.context = context;
this.camera = camera;
PrepareBuffer();
PrepareForSceneWindow();
if (!Cull())
return;
Setup();
DrawVisibleGeometry();
DrawUnsupportedShaders();
DrawGizmos();
Submit();
}
bool Cull()
{
//返回值表示cullingParameters是否有效,比如当相机viewport rectangle宽高被设置为0,0,无效的裁剪平面设置等
//因此返回值能代表这个相机是否需要绘制
if (camera.TryGetCullingParameters(out var cullingParameters))
{
//我们会发现在这里,对ScriptableCullingParameters的操作基本都是out和ref,这是因为ScriptableCullingParameters比较大,这样子可以优化
cullingResults = context.Cull(ref cullingParameters);
return true;
}
return false;
}
void Setup()
{
//将相机的属性设置到绘制命令中,比如相机的位置和旋转,透视还是正交投影等,这会决定视图矩阵和投影矩阵
//就是Shader中所使用的unity_MatrixVP, View Matrix, Projection Matrix
//我们可以在FrameDebuger中的ShaderProperties中看到unity_MatrixVP
//如果我们不进行这项设置,unity_MatrixVP都是一样的,所以当我们旋转相机时,相机看到的东西不会发生变化
context.SetupCameraProperties(camera);
var flags = camera.clearFlags;
//ClearRenderTarget需要在SetupCameraProperties之后进行Execute,不然Clear的方式会变成Draw GL(可以在FrameDebuger中看到)
//Draw GL是使用Hidden/InternalClear Shader绘制了一个全屏的Quad来达到清除的效果,这种方式不是性能最好的
//ClearRenderTarget需要在BeginSample之前,不然FrameDebuger中会被多一次嵌套在"Render Camera"中
buffer.ClearRenderTarget(
flags != CameraClearFlags.Nothing,
flags == CameraClearFlags.Color, //我们的Skybox是在最后绘制的
flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear //我们用的是线性空间,所以当要使用背景颜色时,需要将其转化为线性空间的颜色
);
buffer.BeginSample(SampleName);
ExecuteBuffer(); //Execute后,Buffer中的命令才会加入到context中
}
void DrawVisibleGeometry()
{
//我们需要将不透明物体和透明物体分开绘制
//如果我们直接先绘制所有的物体,然后再绘制天空盒,我们就会看到对于透明的物体,如果其对于相机视线后面没有不透明的物体的话,就会被天空球给遮挡住
//我们的透明shader没有写入深度缓冲,绘制天空盒时,在这个片元上如果没有不透明的物体写入过深度的话,天空盒的绘制就会直接覆盖掉
//所以我们的绘制顺序是先不透明物体,再绘制天空盒,最后绘制透明物体
//先进行不透明物体的绘制
var sortingSettings = new SortingSettings(camera) //相机的透明排序取决于使用的是正交还是基于距离的排序
{
// 不给定排序规则的话,绘制排序是随机混乱的
// 这个排序是从前往后的,因为对于后面的不透明物体,当检测到其的深度大于缓冲区的深度时,说明被挡住了,可以直接丢弃不绘制
// 而如果从后往前的话,就会需要不断的覆盖绘制,性能不如从前往后
criteria = SortingCriteria.CommonOpaque,
};
var drawSettings = new DrawingSettings(unlitShaderTagId, sortingSettings); //DrawingSettings主要用于对物体的渲染顺序和使用哪个Shader Pass
var filteringSettings = new FilteringSettings(RenderQueueRange.opaque); //FilteringSettings主要决定哪些物体渲染,哪些不渲染根据LayerMask,RenderQueue,sortingLayer等
//在Cull中我们得到了cullingResults,因此我们知道了哪些物体是需要绘制的
context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
context.DrawSkybox(camera); //添加绘制Skybox的命令
// 再进行透明物体的绘制
// 这个绘制顺序是从后往前的,与不透明的绘制顺序是相反的
// 不透明的物体并不写入深度缓冲,因此从前往后并没有性能更优
// 而且透明物体之间需要进行混合,从后往前才能得到正确的混合
// 但事实上并不能完全保证正确,因为排序是基于整个物体的位置的,但是对于复杂的大型物体间,可能会有部分穿插,有部分在前,有部分在后,这样子就会得到错误的效果
sortingSettings.criteria = SortingCriteria.CommonTransparent;
drawSettings.sortingSettings = sortingSettings;
filteringSettings.renderQueueRange = RenderQueueRange.transparent;
context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
}
void Submit()
{
buffer.EndSample(SampleName);
ExecuteBuffer();
context.Submit(); //提交绘制命令,进行绘制
}
void ExecuteBuffer()
{
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
}
}
CameraRenderer.Editor.cs
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine.Profiling;
using UnityEngine;
using UnityEngine.Rendering;
//用于进行单个相机的画面渲染
partial class CameraRenderer
{
//为分部方法定义声明,这样子编译时如果没有找到实现声明的方法的话,就会剔除掉对这个方法的调用
//使用这种方法可以比较好的避免Build出错,再来查错
partial void PrepareBuffer();
partial void PrepareForSceneWindow();
partial void DrawUnsupportedShaders();
partial void DrawGizmos();
#if UNITY_EDITOR
string SampleName { get; set; }
static Material errorMaterial; //用于绘制不支持的shader
//Unity默认的所有的Shader Pass的Tag
static ShaderTagId[] legacyShaderTagIds = {
new ShaderTagId("Always"),
new ShaderTagId("ForwardBase"),
new ShaderTagId("PrepassBase"),
new ShaderTagId("Vertex"),
new ShaderTagId("VertexLMRGBM"),
new ShaderTagId("VertexLM"),
};
partial void PrepareBuffer()
{
//我们将Buffer的名称修改为相机名称,这样子就可以在FrameDebuger中看到相机对应的渲染在自己的组中,查看会比较清晰
Profiler.BeginSample("Editor Only"); //这里会产生GC,会混下我们性能分析,所以直接标明是Editor Only
buffer.name = SampleName = camera.name;
Profiler.EndSample();
}
partial void PrepareForSceneWindow()
{
if (camera.cameraType == CameraType.SceneView)
{
//绘制Scene视图中的UI
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
}
}
//使用Unity默认的一些Shader绘制出使用了不支持的Shader的物体
partial void DrawUnsupportedShaders() //如果不改成分部方法的定义和实现声明,打包就会出错,因为这部分代码是仅在编辑器下使用的
{
if (errorMaterial == null)
{
errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
}
//我们将不支持的Shader用Unity默认的设置直接绘制出来
//没有设置好Shader的属性之前,绘制出来的物体是黑色的
var drawSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))
{
overrideMaterial = errorMaterial, //使用内置错误材质来绘制所有不支持的Shader的物体
}
;
for (int i = 1; i < legacyShaderTagIds.Length; i++)
{
drawSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
}
var filteringSettings = FilteringSettings.defaultValue;
context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
}
partial void DrawGizmos()
{
if (Handles.ShouldRenderGizmos()) //获取Gizmos是否开启
{
context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
}
}
#else
const string SampleName = bufferName;
#endif
}