赞
踩
一句话描述,我们可以把SRP分解成两个部分,分别是SRP Asset,SRP Instance。
SRP Asset是一个Unity Asset文件,用来存储渲染管线的特定配置信息,包含的信息有:游戏物体是否应该投射阴影;使用什么样的着色器质量级别;影子距离;默认材质配置。
此外还可以保存所有像在配置中保存和控制的信息和unity需要序列化的数据。SRP Asset定义了SRP的类型和所有配置数据的设置。
SRP Instance是实际执行渲染的类,当unity发现工程使用SRP的时候,就会寻找当前使用的SRP Asset并请求一个渲染实例,该实例必须包含一个Render函数,
渲染实例表示管道配置。在渲染调用中,Unity可以执行如下操作:
SRP Asset实际是一个继承RenderPipelineAsset接口的类,需要实现CreatePipeline接口。当unity首次执行渲染命令的时候,则会调用这个接口并返回一个可用的渲染实例。
下面所展示的就是一个简单的SRP Asset类。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Rendering;
-
- [CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
- public class CustomRenderPipelineAsset : RenderPipelineAsset
- {
- protected override RenderPipeline CreatePipeline()
- {
- return new CustomRenderPipeline();
- }
- }
在上面的叙述中,CustomRenderPipelineAsset类重写了CreatePipeline方法,并返回了一个CustomRenderPipeline实例对象,该对象则是unity SRP渲染的入口。CustomRenderPipeline是一个继承RenderPipeline的类,而且必须实现虚函数Render,该函数包含了两个参数:
ScriptableRenderContext context:是一个command buffer对象,可以向其输入您想执行的渲染指令。
Camera[] cameras:一系列用于渲染的相机。
先介绍一个简单的例子:
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Rendering;
-
- public class CustomRenderPipeline : RenderPipeline
- {
- public CameraRender cameraRender = new CameraRender();
-
- public CustomRenderPipeline()
- {
- }
-
- protected override void Render(ScriptableRenderContext context, Camera[] cameras)
- {
- foreach(var camera in cameras)
- {
- cameraRender.Render(context, camera);
- }
- }
- }
如上所示,我们把渲染的处理逻辑封装到CameraRender类里,对每一个场景中的活动相机进行单独处理。
下一步先定义CameraRender类:
- 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;
- }
- }
然后正式定义Render函数,实现我们的自定义渲染管线。
在Render函数中是要绘制所有Camera可见的几何体,我们声明一个函数DrawVisibleGeometry用于处理一些特殊的工作,绘制天空盒也是在这个方法中处理,不过我们还另外声明了一个方法来完成这个工作。代码如下:
- public void Render (ScriptableRenderContext context, Camera camera) {
- this.context = context;
- this.camera = camera;
-
- DrawVisibleGeometry();
- }
-
- void DrawVisibleGeometry () {
- context.DrawSkybox(camera);
- }
然而这样仍不能显示天空盒,这是因为我们调用DrawSkybox只是把命令添加到context的缓存队列里,还需要调用context.Submit()方法才会正式执行缓存队列里面的命令。
- public void Render (ScriptableRenderContext context, Camera camera) {
- this.context = context;
- this.camera = camera;
-
- DrawVisibleGeometry();
- Submit();
- }
-
- void Submit () {
- context.Submit();
- }
最后我们可以看到下面的效果。
但是,这个渲染结果还是有问题,当我们调整相机的角度或位置时,渲染结果并没有发生变化,这是因为我们并没有将视角投影矩阵传递给Command Buffer。这个变换矩阵在Unity Shader中是unity_MatrixVP。当我们打开frame debugger面板,选择一个draw call,这个变量就会显示在右侧的监视板上。当前,不管我们怎么调整相机的姿态,这个变量都会保持不变,通过调用SetupCameraProperties方法可以构建这个矩阵。如下所示
但是,这个渲染结果还是有问题,当我们调整相机的角度或位置时,渲染结果并没有发生变化,这是因为我们并没有将视角投影矩阵传递给Command Buffer。这个变换矩阵在Unity Shader中是unity_MatrixVP。当我们打开frame debugger面板,选择一个draw call,这个变量就会显示在右侧的监视板上。当前,不管我们怎么调整相机的姿态,这个变量都会保持不变,通过调用SetupCameraProperties方法可以构建这个矩阵。如下所示
- public void Render (ScriptableRenderContext context, Camera camera) {
- this.context = context;
- this.camera = camera;
- Setup();
- DrawVisibleGeometry();
- Submit();
- }
-
- void Setup () {
- context.SetupCameraProperties(camera);
- }
2. Command buffer
前面已经提到,context只有在调用Submit方法时才会执行渲染指令,在这之前都是将指令添加到渲染队列中。像天空盒可以通过专业的方法添加指令,但是像其他任务则不能通过这样添加,需要通过一个单独的command buffer间接添加。因此我们需要另建一个buffer用来绘制其他的几何对象。
- const string bufferName = "Render Camera";
-
- CommandBuffer buffer = new CommandBuffer {
- name = bufferName
- };
在项目开发中,profiler是一个非常强大和有用的工具,这里我们可以把我们的Command Buffer加入到profiler样例中,这个样例可以同时显示在profiler和frame debugger面板,方法是在适当的位置调用BeginSample和EndSample方法,在我们的案例中将会分别放在Setup()方法和Submit()方法的开头位置。BeginSample和EndSample方法必须使用相同的字符串参数作为样例的名字,这里使用的是buffer的名字。
- void Setup () {
- buffer.BeginSample(bufferName);
- context.SetupCameraProperties(camera);
- }
-
- void Submit () {
- buffer.EndSample(bufferName);
- context.Submit();
- }
执行Command Buffer中的命令只需要调用context的ExecuteCommandBuffer方法,并把buffer作为参数传递进去。
- void Setup () {
- buffer.BeginSample(bufferName);
- ExecuteBuffer();
- context.SetupCameraProperties(camera);
- }
-
- void Submit () {
- buffer.EndSample(bufferName);
- ExecuteBuffer();
- context.Submit();
- }
-
- void ExecuteBuffer () {
- context.ExecuteCommandBuffer(buffer);
- buffer.Clear();
- }
这时候打开frame debugger我们就可以看到渲染的案例如下:
3. 清除渲染目标
不管我们想要绘制什么东西,最终都会渲染到相机的渲染目标中。相机的渲染目标一般是帧缓存区,也可以是渲染纹理。渲染目标一般不会自动清除,也就是说之前渲染的结果会一直保存在渲染目标中,这样会干扰我们渲染新的图像。为了保证正确的渲染,我们必须清除渲染目标,以摆脱其旧的内容。通过调用ClearRenderTarget方法可以实现这个目的。
ClearRenderTarget函数需要至少三个参数,前两个参数指定是否清理颜色缓存和深度缓存,一般都设为true,第三个参数指定清理缓存的颜色,这里使用Color.clear。
- void Setup () {
- buffer.BeginSample(bufferName);
- buffer.ClearRenderTarget(true, true, Color.clear);
- ExecuteBuffer();
- context.SetupCameraProperties(camera);
- }
打开frame debugger,可以看到清理渲染目标活动对应了一个叫DrawGL的样例。而且这个案例被镶嵌在另一个Render Camera样例中,这是因为 ClearRenderTarget将清除任务包装在了一个使用Command buffer的名字命名的样例中。为了避免这种情况,可以调整一下清除目标的代码的位置。
- void Setup () {
- context.SetupCameraProperties(camera);
- buffer.ClearRenderTarget(true, true, Color.clear);
- buffer.BeginSample(bufferName);
- ExecuteBuffer();
- //context.SetupCameraProperties(camera);
- }
现在我们可以看到Clear(color+Z+stencil),这表明颜色跟深度缓冲都没清除了,stencil数据是同一缓存区的一部分。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。