赞
踩
要在屏幕上绘制图像,我们得知道几个要素:绘制什么东西(What),何时绘制(When),在屏幕上哪个位置绘制(Where),用什么设置绘制(How)。另外还要把灯光、阴影、透明效果、后处理效果等等都要正确的绘制出来,才能得到最后的一张图像,这也是渲染管线所要做的。
以前Unity只开放了很小部分供我们对绘制进行设置,Unity2018开始,Unity支持自定义渲染管线(Scriptable Render Pipeline),虽然还是要依赖Unity的一些基础方法,比如调用绘制天空盒,剔除等,但这样我们可以自己对绘制进行更多的设置。Unity 2018提供了两个实验性的自定义渲染管线:轻量级渲染管线和高清渲染管线。在Unity 2019中轻量级渲染管线成为正式功能,并且在2019.3中改名为通用渲染管线。
通用渲染管线的目的是替换现在用的旧的默认管线,顾名思义,就是希望这个渲染管线能够满足大部分的需求,并且呢,也容易去自己修改一部分内容。
我们这里主要是从0开始自己定义渲染管线,并不是使用Unity提供的管线。
我使用的Unity版本:Unity2020.3.10f1
因为我们是自定义渲染管线,所以不要选Unity的渲染管线,选择3D
打开设置,将色彩空间切换为线性空间
然后我们像下面随便创建一些物体,使用不同的材质,Unlit/Color,Standard(Mode设置Transparent),Unlit/Transparent
新建脚本(因为直接上的都是最终代码,有些地方直接贴上去可能会报错,一步一步做的话,可以先注掉报错的部分)
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);
}
}
}
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();
}
}
然后创建我们的渲染管线资源
完成创建后,我们需要在ProjectSettings-Graphics中设置我们的渲染管线,设置后我们会发现整个设置页面都变了,很多设置都消失了。另外由于我们的渲染管线中还没有写任何的渲染设置,因此现在什么东西都不会被绘制了,无论是在Scene视图还是Game视图,甚至FrameDebugger中也是什么东西都没有了。这就是渲染管线的真正开始,从0开始自定义我们的渲染管线
我们先来绘制下天空盒,很简单的操作
关键代码
context.DrawSkybox(camera); //添加绘制Skybox的命令
context.Submit(); //提交绘制命令,进行绘制
然后我们就能看到天空盒了,并且在FrameDebuger中可以看到绘制的DrawCall
但是这个时候当我们旋转相机时,画面并不会发生变化,这是因为我们没有将相机的旋转这些属性应用到Shader中
关键代码
//将相机的属性设置到绘制命令中,比如相机的位置和旋转,透视还是正交投影等,这会决定视图矩阵和投影矩阵
//就是Shader中所使用的unity_MatrixVP, View Matrix, Projection Matrix
//我们可以在FrameDebuger中的ShaderProperties中看到unity_MatrixVP
//如果我们不进行这项设置,unity_MatrixVP都是一样的,所以当我们旋转相机时,相机看到的东西不会发生变化
context.SetupCameraProperties(camera);
另外,我们可以通过添加BufferName来让Profile和FrameDebuger中相应的部分显示我们想要的名称,这样便于分析
关键代码
const string bufferName = "Render Camera";
CommandBuffer buffer = new CommandBuffer
{ //创建实例时可以这样直接初始化
name = bufferName,
};
buffer.BeginSample(SampleName);
buffer.EndSample(SampleName);
我们在每一次绘制之前,都应该清除一些必要的数据,这样以保证正确的绘制
关键代码
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 //我们用的是线性空间,所以当要使用背景颜色时,需要将其转化为线性空间的颜色
);
要注意ClearRenderTarget的调用位置,不然会使用内置Shader绘制的方法来清除,这样子性能不好
通过Cull来确定是否绘制
关键代码
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;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。