IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity Custom SRP Part 1 —— Custom Render Pipeline -> 正文阅读

[游戏开发]Unity Custom SRP Part 1 —— Custom Render Pipeline

最近做的东西要接触SRP了,所以这里跟着Catlike Coding做一做上面的教程,然后记录下来这个过程

参考:https://catlikecoding.com/unity/tutorials/custom-srp/custom-render-pipeline/

创建多种材质和场景物体

这里场景不同的材质,包括Standard Shader的材质、Unlit的透明和不透明Shader,如下图所示,不多说:
在这里插入图片描述



创建Scriptable Render Pipeline Aseet

这是默认项目的Graphics设置:
在这里插入图片描述
我写了个脚本,可以创建自定义的RP Asset:

using UnityEngine;
using UnityEngine.Rendering;

// 需要继承于RenderPipelineAsset, 此类代表一个pipeline object instance 
// Unity会使用它去做渲染, Asset本质只是一个Handle和一些Settings
[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset
{
    // 获取Handle
    protected override RenderPipeline CreatePipeline()
    {
        return null;
    }
}

菜单栏创建文件后,指派上去,发现可选的设置少了很多,少了Tier Settings、一些Built-in Shader Settings和Camera Settings:
在这里插入图片描述
除了一些设置问题, we’ve disabled the default RP without providing a valid replacement, so nothing gets rendered anymore. The game window, scene window, and material previews are no longer functional. If you open the frame debugger—via Window / Analysis / Frame Debugger—and enable it, you will see that indeed nothing gets drawn in the game window.



创建Scriptable Render Pipeline的Runtime Instance

此时再创建一个脚本CustomRenderPipeline.cs,感觉可以理解为Renderer,整体的代码如下:

// CustomRenderPipeline.cs脚本
using UnityEngine;
using UnityEngine.Rendering;

public class CustomRenderPipeline : RenderPipeline
{
	// Unity每帧都会调用这个Instance的Render函数, 会给Engine传递一个Render Context
	// RP需要按照提供的Camera数组的顺序, 逐个渲染这些Camera
    protected override void Render(ScriptableRenderContext context, Camera[] cameras)
    {
    }
}

// CustomRenderPipelineAsset.cs脚本
using UnityEngine;
using UnityEngine.Rendering;

// 需要继承于RenderPipelineAsset, 此类代表一个pipeline object instance 
// Unity会使用它去做渲染, Asset本质只是一个Handle和一些Settings
[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset
{
    // 获取Handle
    protected override RenderPipeline CreatePipeline()
    {
        return new CustomRenderPipeline();
    }
}


创建CameraRenderer类

这里的每个Camera都会各自被渲染,所以创建一个CameraRenderer.cs脚本:

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;
	}
}

原本RP Instance的Renderer函数就可以改写为:

public class CustomRenderPipeline : RenderPipeline
{
	// 这里用了统一的CameraRenderer来对相机进行渲染, 有点像URP的方式
	// 更高级的可以用不同的CameraRenderer实现不同的渲染
    CameraRenderer cameraRenderer = new CameraRenderer();

    protected override void Render(ScriptableRenderContext context, Camera[] cameras)
    {
        for (int i = 0; i < cameras.Length; i++)
        {
        	cameraRenderer.Render(context, cameras[i]);
        	//Debug.Log("第" + i + "个:" + cameras[i] + " 总个数:" + cameras.Length);
        }
    }
}

此时仍然没有调用任何Draw Call如果打开Analysis的Frame Debugger窗口,会发现里面没有任何东西:
在这里插入图片描述
这里我还额外看了看RenderPipeline里的Render函数每帧都有哪些Camera要渲染,结果发现每帧就一个Camra,取决于自己的鼠标放在哪个Window上,比如放Scene上,就是传的Scene Camera,如果是Game View就是Main Camera,Inspector上就是Preview Camera,但每次都是只传入一个Camera,如下图所示,而且只有鼠标在窗口上移动的时候才会调用RenderPipeline的Render函数,确实挺有意思:
在这里插入图片描述
额外看了下Camera的种类,一共五种:

// Describes different types of camera.
[Flags]
public enum CameraType
{
    // Used to indicate a regular in-game camera.
    Game = 1,
    // Used to indicate that a camera is used for rendering the Scene View in the Editor.
    SceneView = 2,
    // Used to indicate a camera that is used for rendering previews in the Editor.
    Preview = 4,
    // Used to indicate that a camera is used for rendering VR (in edit mode) in the Editor.
    VR = 8,
    // Used to indicate a camera that is used for rendering reflection probes.
    Reflection = 16
}


创建CameraRenderer在Render函数里绘制Skybox

代码如下所示,很简单:

public class CameraRenderer
{

    ScriptableRenderContext context;

    Camera camera;

    // 由RenderPipeline的Render函数每帧调用此函数
    public void Render(ScriptableRenderContext context, Camera camera)
    {
        this.context = context;
        this.camera = camera;

        context.DrawSkybox(camera);
        context.Submit();
    }
}

此时就可以在Game和Scene View下看到天空盒子了,而且Frame Debugger也出现了函数调用,这个数字1应该是代表一帧调用一次的意思(或者是代表其child的个数),但此时的Scene View下的Camera不支持任何的Input操作,不可以移动或者干啥:
在这里插入图片描述



设置Camera的VP矩阵

此时的天空盒完全是静态的,这是因为Camera的VP矩阵还没有设置,设置的代码如下:

// 由RenderPipeline的Render函数每帧调用此函数
public void Render(ScriptableRenderContext context, Camera camera)
{
    this.context = context;
    this.camera = camera;

    context.SetupCameraProperties(camera);
    context.DrawSkybox(camera);
    context.Submit();
}

此时就可以在Scene View和Main Camera的Transform里调整Camera的Zoom和Rotation了,不过天空盒永远是远平面的,调整滚轮不会有任何反应


创建CommandBuffer

这里创建了一个CommandBuffer,用于在Frame Debugger里插入profiler samples。然后把它在Frame Debugger里显示出来,感觉有点类似Unity的Profiler.BeginSample操作,代码如下,我感觉其实就是创建了一个函数指针的数组,然后把这些数组Copy到Context的Command Buffer里:

public class CameraRenderer
{

    ScriptableRenderContext context;

    Camera camera;

    const string bufferName = "Render Camera";

    CommandBuffer buffer = new CommandBuffer
    {
        name = bufferName
    };

    // 由RenderPipeline的Render函数每帧调用此函数
    public void Render(ScriptableRenderContext context, Camera camera)
    {
        this.context = context;
        this.camera = camera;

        // 在每次渲染开始时, 先begin sample
        buffer.BeginSample(bufferName);
        
		// 此函数会把Buffer里的Commands复制到context里,应该不会立马执行
        context.ExecuteCommandBuffer(buffer);
        buffer.Clear();

        // Update相机的属性
        context.SetupCameraProperties(camera);

        // 绘制
        context.DrawSkybox(camera);

        // 然后结束这次Sample工作
        buffer.EndSample(bufferName);
        context.ExecuteCommandBuffer(buffer);
        buffer.Clear();

        context.Submit();
    }
}

此时在Frame Debugger里,就会多一层嵌套了:
在这里插入图片描述


Clearing the Render Target

绘制的东西,最终都会到Camra对应的Render Target上,本质上,Render Target默认是一块Frame Buffer,但它也可以是一个Render Texture(所以是一个OpenGL里的FBO咯?),每帧渲染时,要把之前的绘制内容情况(感觉类似于OpenGL的glClear函数),所以这里加一行代码,旨在在绘制之前清空old contents:

// 第一个true代表clear depth, 第二个代表clear color, 第三个代表Color清除后的颜色
buffer.ClearRenderTarget(true, true, Color.clear);// Color.clear其实是黑色

// 类比于这两个函数
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(color.x, color.y, color.z, color.w);

// 后面的继续...
buffer.BeginSample(bufferName);
context.ExecuteCommandBuffer(buffer);
...

现在的Frame Debugger如下图所示,不过我还是很奇怪,Draw GL为什么会出现在Render Name这个Buffer的嵌套里面:
在这里插入图片描述

这里有个问题,就是调用了ClearBuffer的类似操作,为什么在Frame Debugger里展示的名字叫Draw GL?因为此时Camera用于清空Buffer的方式比较特殊,它调用了一个叫做Hidden/InternalClear的Shader,写入到Render Target上,绘制了一个Full-Screnn Quad。这是因为,Unity检测到:在调用ClearRenderTarget函数后,我又调用了SetCameraProperties来改变Camera的VP矩阵,所以它做了这样的Trick,没有直接清空Buffer。(更深层次的原因Remain,估计是改变了VP矩阵又得Clear一次吧,可能跟架构有关)

但这样效率并不高,所以需要把ClearRenderTarget提前,如下图所示:
在这里插入图片描述
此时的Frame Debugger就正常了,注意这里的Z和stencil共享了一个Buffer:
在这里插入图片描述



借助Culling来绘制Camera里的对象

目前Camera里除了绘制Skybox,没有任何其他的Scene里的内容,这里做Culling,依据有两个:

  • 只绘制挂载了Renderer组件的GameObject
  • 只绘制相机Frustum里的东西,对于外部的东西进行Culling

执行Culling操作需要得到Camera的相关参数,代码如下:

using UnityEngine;
using UnityEngine.Rendering;

public class CameraRenderer
{
	...// 一些Fields

    CullingResults cullingResults;// Field用于记录Culling的结果

    // 由RenderPipeline的Render函数每帧调用此函数
    public void Render(ScriptableRenderContext context, Camera camera)
    {
        this.context = context;
        this.camera = camera;

        if (!Cull())
            return;

        // Update相机的属性
        context.SetupCameraProperties(camera);
		...
        context.Submit();
    }

    bool Cull()
    {
        // 从Camera里获取Culling信息, 这里的parameters作用域是Cull整个函数
        if (camera.TryGetCullingParameters(out ScriptableCullingParameters parameters))
        {
        	// 在Context执行Culling操作, 并获得结果
            cullingResults = context.Cull(ref parameters);
            return true;
        }
        return false; 
    }// parameters作用域结束
}


绘制Geometry

通过上面的Contex.Culling操作,得到了Culling Result,现在就可以知道哪些Geometry是在相机的Frustum里了,相关代码如下:

// ShaderTagId是一个Struct, 本质就是一个String, 代表Shader的Tag
static ShaderTagId unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit");

// 由RenderPipeline的Render函数每帧调用此函数
public void Render(ScriptableRenderContext context, Camera camera)
{
	...
	// 绘制
	// 其实就是调用一堆封装好的API而言
	
	// 根据Camera的设置, 判断是使用 orthographic还是distance based sorting.
    var sortingSettings = new SortingSettings(camera);
    // 指定可以使用哪种类型的Shader Passes, 这里只支持Unlit Shaders
    var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings);
    // 指定是否指出RenderQueue, 这里表示要Render所有Queue里的东西
    var filteringSettings = new FilteringSettings(RenderQueueRange.all);

    // 调用context的DrawRenderers函数
    context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
    context.DrawSkybox(camera);
	...
}

此时,就可以在GameView和Scene View下看到东西,不过这里的透明物体是看不到的,除非选中它。Frame Debugger里使用的Camera是Game View下的Camera,如下图所示,从上往下一行行的换,可以看到一个个对象逐渐按顺序被画出来,很有意思,而且我移动Main Camera的话,这个Draw Loop列表会随着Frustum进行Culling:
在这里插入图片描述

但是这里有个问题,此时的绘制顺序可以说是完全随机的,好像是和GameObject在Hierarchy里的顺序有关,但总归是乱的,如下图所示:
在这里插入图片描述
这里可以在代码里规定渲染物体的顺序:

var sortingSettings = new SortingSettings(camera) 
{
	criteria = SortingCriteria.CommonOpaque
};

这里的SortingCriteria是个枚举,还挺多Sorting的方法的:

// How to sort objects during rendering.
[Flags]
public enum SortingCriteria
{
    // Do not sort objects.
    None = 0,
    // Sort by renderer sorting layer.
    SortingLayer = 1,
    // Sort by material render queue. 根据材质的render queue排序
    RenderQueue = 2,
    // Sort objects back to front. 从离相机远的开始绘制
    BackToFront = 4,
    // Sort objects in rough front-to-back buckets.
    QuantizedFrontToBack = 8,
    // Sort objects to reduce draw state changes.
    OptimizeStateChanges = 16,
    // Typical sorting for transparencies.
    CommonTransparent = 23,
    // Sort renderers taking canvas order into account.
    CanvasOrder = 32,
    // Typical sorting for opaque objects. 只为非透明的物体排序, 从离相机近的开始绘制, 这样被挡住的就不会画了
    // 这种排序其实也包含了the render queue and materials两种排序
    CommonOpaque = 59,
    // Sorts objects by renderer priority.
    RendererPriority = 64
}

如下所示,是这些Enum的源代码:

[Flags]
public enum SortingCriteria
{
    None = 0,

    SortingLayer = (1 << 0), // by global sorting layer
    RenderQueue = (1 << 1), // by material render queue
    BackToFront = (1 << 2), // distance back to front, sorting group order, same distance sort priority, material index on renderer
    QuantizedFrontToBack = (1 << 3), // front to back by quantized distance
    OptimizeStateChanges = (1 << 4), // combination of: static batching, lightmaps, material sort key, geometry ID
    CanvasOrder = (1 << 5), // same distance sort priority (used in Canvas)
    RendererPriority = (1 << 6), // by renderer priority (if render queues are not equal)

    CommonOpaque = SortingLayer | RenderQueue | QuantizedFrontToBack | OptimizeStateChanges | CanvasOrder,
    CommonTransparent = SortingLayer | RenderQueue | BackToFront | OptimizeStateChanges,
}

然后此时的带颜色的非透明的Cube就可以按照从近到远的顺序进行绘制了,但是透明物体还是乱的,如下图所示:
在这里插入图片描述
额外提一下,这里的距离是根据相机坐标系的,物体的坐标在Z轴上的距离的,所以相机要摆正,比如我之前的相机x轴有角度,就导致渲染不太对,我还查了半天的问题,以为是精度问题。



单独绘制透明和不透明的物体

可以看一下目前的渲染状态,渲染到这里还是正常的,可以看到透明物体,在Loop里是先画不透明物体,再画半透明物体,这个逻辑是对的:
在这里插入图片描述
然后再往下,绘制skybox的时候,就发现半透明物体没了,准确的说,是在天空盒部分的半透明物体没了,如下图所示:
在这里插入图片描述
这是因为,半透明物体的绘制,不会写入任何Z-buffer(因为它不想阻挡后面东西,因为后面的东西还能看到),而除了不透明物体占据像素的这些位置,写入了Z值,其他的位置,都没有写过任何Z值,所以绘制Skybox的时候,它就直接写入了颜色,导致半透明物体填入的颜色被overwrite了。

所以这里的绘制顺序是不对,应该是先绘制不透明物体=> 再绘制skybox => 再绘制半透明物体,代码变成如下所示:

var sortingSettings = new SortingSettings(camera)
{
    criteria = SortingCriteria.CommonOpaque
};

var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings);
// 这里不再是all了, 而是opaque
var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);

// 1. 绘制opaque
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);

// 2. 绘制skybox
context.DrawSkybox(camera);

// 改变排序规则
sortingSettings.criteria = SortingCriteria.CommonTransparent;
drawingSettings.sortingSettings = sortingSettings;
// 改变渲染filter设置
filteringSettings.renderQueueRange = RenderQueueRange.transparent;

// 3. 绘制transparent
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);

此时效果就对了,果然渲染还是有意思啊:
在这里插入图片描述
这里有个奇怪的现象,就是半透明的物体,渲染顺序跟不透明物体不一样,不透明物体是从近到远的,而半透明物体是从远到近的,这里有两个原因:

  • 半透明物体不会写入ZBuffer,所以从近到远绘制没意义,不会有任何效率上的提升
  • 基于视觉上的考虑,一个离屏幕最近的半透明物体,应该颜色是其后面的半透明的物体的Blend结果,所以从后往前绘制逻辑是对的

不过还是可能会有问题,因为sorting is per-object and only based on the object’s position,就不深究了

到此为止,一个基本的RP就跑通了,不过目前还只能支持Unlit类型的Shader Pass.



Editor Rendering

现在有个文件,如果一个Material用的Shader不是Unlit Shader Passes,或者本身是个错误的Shader,那它也应该在场景中显示出来,而不是不显示。在Unity里,如果一个材质的shader丢了,那么应该是洋红色的。这其实也是普通Project升级到URP项目时,很多Shader的变化。

所以这里就进行相关的处理,代码如下:

// 由RenderPipeline的Render函数每帧调用此函数
public void Render(ScriptableRenderContext context, Camera camera)
{
	...// 绘制前面正常的部分
	
    // 绘制Shader不正常的本不应该可见的Geometry(megenta color)
    
    // 创建新的绘制Settings, DrawingSettings里最少需要一个基本的Pass, 这里传入第一个
    var drawingSettings2 = new DrawingSettings(
        legacyShaderTagIds[0], new SortingSettings(camera)
    );
    
    // 然后添加剩下的Shader Passes
    for (int i = 1; i < legacyShaderTagIds.Length; i++)
    {
        // 添加Shader的名字, 只加一个名字有啥用啊?
        drawingSettings2.SetShaderPassName(i, legacyShaderTagIds[i]);
    }

    // 这里用的默认的filtering, 也就是不做任何filter处理吧
    var filteringSettings2 = FilteringSettings.defaultValue;
    
    // cullingResults是整帧都不会变化的数据
    context.DrawRenderers(
        cullingResults, ref drawingSettings2, ref filteringSettings2
    };
	
	...// EndSample再Submmit
}

此时之前用的不正确的Shader就也可以显示,不过教程里说的这些物体都呈现黑色,但是我的颜色是正常的,如下图所示:
在这里插入图片描述

非Unlit的Shader全部用洋红色表示
虽然不知道为啥上面的显示是正确的,可能是新版本的Unity做了处理吧,但是我并不确定这个Shader是不是真的可用,这里还是做额外的处理,把不支持的Shader全部变为洋红色。代码如下:

static Material errorMaterial;
// 由RenderPipeline的Render函数每帧调用此函数
public void Render(ScriptableRenderContext context, Camera camera)
{
	...// 绘制前面正常的部分
	
    // 绘制Shader不正常的本不应该可见的Geometry(megenta color)
	
    if (errorMaterial == null)
    {
    	// 代码只跑一次, 用Unity默认提供的错误的洋红色Shader给它赋值
        errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
    }
    
    // 创建新的绘制Settings, DrawingSettings里最少需要一个基本的Pass, 这里传入第一个
    var drawingSettings2 = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))
    {
		overrideMaterial = errorMaterial
	};
	
    ...//其他的不变
}

此时所有不支持的Shader,就都是洋红色了,如下图所示:
在这里插入图片描述


Partial Class
这一段代码应该只放在Editor下执行,Release版本不应该出现这些洋红色的东西,这里创建一个CameraRenderer.Editor.cs文件,把相关Editor的代码放进去,在CameraRenderer.cs里调用DrawUnsupportedShaders即可:

public partial class CameraRenderer
{
	// 使用partial method将函数的声明和定义分离
    partial void DrawUnsupportedShaders();

#if UNITY_EDITOR
    static Material errorMaterial;

    // 代表所有Unity默认的shaders
    static ShaderTagId[] legacyShaderTagIds = {
        new ShaderTagId("Always"),
		...
    };

    // 绘制Shader不正常的本不应该可见的Geometry(megenta color)
    partial void DrawUnsupportedShaders()
    {
    	...//
    }
#endif
}


Drawing Gizmos

根据教程,目前是没gizmos的,如下图所示:
在这里插入图片描述
但我发现我是有Gizmos的,但是不完整,比如点选相机时,没有对应的Frustum,也看不到Light的Icon,如下图所示:
在这里插入图片描述
这里加一块代码就行了:

// CameraRenderer.Editor.cs文件里
public partial class CameraRenderer
{
    partial void DrawGizmos();
    partial void DrawUnsupportedShaders();

#if UNITY_EDITOR
	...
    partial void DrawGizmos()
    {
        if (Handles.ShouldRenderGizmos())
        {
        	// 目前并不支持image effects, 所以两种Subset都要绘制
            context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
            context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
        }
    }
	...
#endif
}

// CameraRenderer.cs文件里

// 由RenderPipeline的Render函数每帧调用此函数
public void Render(ScriptableRenderContext context, Camera camera)
{
	...
	// 画opaque
	... 
    // 画skybox
	...
    // 画transparent
	...

	DrawUnsupportedShaders();
	// 在绘制完所有场景里的物体之后再绘制Gizmos, 因为gizmos是无视Depth的
    DrawGizmos();
    
	...
}

Drawing Unity UI

此时在Hierarchy里,可以右键添加UI Button,此时会在Game View下出现Button,Scene View下会有个Canvas,但是看不到Button。

此时打开Frame Debugger,会发现出现了一个额外的UI节点,说明相关的UI不是用的我们的RP绘制的,这里调用了两个DrawMesh,第一个绘制了Button的白色底板,第二个DrawMesh绘制了Button对应的字:
在这里插入图片描述
至于为什么,创建的RP没有用于绘制Canvas上面的UI,这是因为Canvas组件的默认RenderMode(绘制模式)是Screen Space - Overlay,如下图所示:
在这里插入图片描述
此时只要把它改为Screen Space - Camera,然后拖进去Hierarchy里的Main Camera,就可以在Frame Debugger里看到,所有的内容都是交给我们的RP来绘制了,而且UI这一块是算在Transparent栏目里的,顺序是UI、再是由远到近的Transparent(因为opaque的UI会挡住所有的Transparent,所以opaque的UI会写入Z值?):
在这里插入图片描述
这里的Scene View下的UI仍然是看不到的,而且选中它的时候,会发现相关UI往往都很大,这是因为Scene View下的UI往往是以World Space的Render Mode进行绘制的,所以说屏幕分辨率的1920*1080尺寸,可能UI就有1920米。。。如下图所示:
在这里插入图片描述
想要在Scene下看到UI,需要在渲染Scene窗口的时候明确的把UI加入到World Geometry里,相关代码如下,是Editor-Only的:

// CameraRenderer.Editor.cs文件下
partial void PrepareForSceneWindow();

#if UNITY_EDITOR

partial void PrepareForSceneWindow() {
	// 如果Camera绘制的是Scene View
	if (camera.cameraType == CameraType.SceneView) {
		ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
	}
}
...
#endif

// CameraRenderer.cs文件下
// 加入到Culling之前, 也就是绘制任何物体之前, 因为Culling还能剔除不需要绘制的UI
PrepareForSceneWindow();
if (!Cull()) {
	return;
}

现在就可以看到Scene里面的UI了,美滋滋:
在这里插入图片描述

多个Cameras

每个Camera都有一个Depth值,main camera的默认Depth值为-1,渲染时会按照相机的深度递增顺序,依次渲染不同深度的相机,这里复制一个Main Camera,深度值改为0,其Main Camera的Tag换成别的(因为Main Camera的Tag理论上应该只有一个Camera可以挂)

此时Frame Debugger里的可以看到,同样的渲染内容,出现了两次,第二次Camera渲染的时候,会把之前渲染的清空掉,所以画面跟之前没有任何变化:
在这里插入图片描述
这里的两个Camera都在一个Sample的Scope里,不太美观,改成两个好了,Scope的名字就用Camera的名字来代替:

// CameraRenderer.cs文件下
public void Render(ScriptableRenderContext context, Camera camera)
{
      	...
        buffer.ClearRenderTarget(true, true, Color.clear);
        // 在每次渲染开始时, 先begin sample
        UpdateBufferName();
        buffer.name = camera.name;
        // 这段代码, buffer.name被用于Frame Debugger里的名字,
        // 而传入的camera.name则是用于Profiler里的Sample的名字
        buffer.BeginSample(camera.name);
		...
		buffer.EndSample(camera.name);
		...
}

此时两个Debugger窗口就都可以看到相关内容了:
在这里插入图片描述


Layers

Camera可以通过Layer来实现,Camera只可以看到特定Layer的对象,核心在于改变相机的Culling Mask

接下来的操作很简单,把使用Standard Shader的GameObject的Layer全部设置为Ignore Raycast,然后让Main Camera的Culling Mask看向所有除了Ignore Raycast的Layer,然后让Second Camera的Culling Mask只看Ignore Raycast,然后就是如下图所示了,因为Second Camera后渲染,所以只绘制了使用标准Shader的物体(不过Scene View好像没有变化):
在这里插入图片描述


Clear Flags

如下图所示,相机有个属性叫做Clear Flags,下面一共有四种选项:
在这里插入图片描述
这里的ClearFlags类似于OpenGL里的glClear里的选项,OpenGL里有GL_COLOR、GL_DEPTH和GL_STENCIL三个选项,我其实不太懂这些具体的代表什么,Clear Depth Only我还能理解,就是清除Z Buffer,由于Unity里的ZBuffer和Stencil Buffer共用一个Buffer,所以这里的清除Depth-only实际上是同时清除Z和模板缓冲。

对应到脚本里面,是Camera类里的一个枚举属性:CameraClearFlags:

// Values for Camera.clearFlags, determining what to clear when rendering a Camera.
public enum CameraClearFlags
{
	// 这里没有Clear Stencil的选项, 原因上面提到了
    // Clear with the skybox.
    Skybox = 1,
    // 这里的Color和SolidColor居然值是相同的
    Color = 2,
    // Clear with a background color. 不是很清楚具体指的什么
    SolidColor = 2,
    // Clear only the depth buffer.
    Depth = 3,
    // Don't clear anything.
    Nothing = 4
}

注意,从上到下的枚举之间是包含关系,比如说Skybox的ClearFlag实际上包含了ClearColor和ClearDepth的操作。

为什么ClearFlags里会有Clear Skybox
感觉在OpenGL里并没有这个东西,只有Clear 颜色、模板和深度的操作,这里的Clear Skybox我不太理解,所以额外提一下。

这里的Clear Skybox其实可以理解为,Clear Everything,直到剩下一个Skybox,接下来的内容都会基于这个Skybox上去绘制。

它其实类似于OpenGL的:

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );

只不过,它这里的Clear Color的操作好像有点特殊,因为它是重新画了个Skybox,而不是直接去Clear Color,不过也差不多,因为如果Camera里面没有指定Skybox,那么会用Camera.backgroundColor去Clear Color。


Solid Color和Color
Color应该只是Solid Color的别名,作用是一样的,其实就是Clear颜色+深度+模板。

The solid color clear flag means that when the camera renders a new frame, it clears everything from the old frame and it displays the new image on top of a solid color.

目前两个Camera绘制的东西完全相同,没有任何意义。现在想要实现这么个功能,就是让两个相机绘制不同的内容,Main Camera绘制主场景,而Second Camera把不支持的Shader的物体绘制出来,作为一张图片叠起来,类似于两个Frame Buffer渲染组合到一起,如下图所示:
在这里插入图片描述

那么现在需要,根据相机的ClearFlag属性,选择性的调用ClearRenderTarget函数,之前都是无脑写死的,绘制的东西完全相同:

// 原来的代码如下, 每个相机都Clear了Depth和Color Buffer
buffer.ClearRenderTarget(true, true, Color.clear);

// 函数签名是:
// public void ClearRenderTarget(bool clearDepth, bool clearColor, Color backgroundColor);

现在会根据Camera的Flag来判断:

// 获取flags
CameraClearFlags flags = camera.clearFlags;
buffer.ClearRenderTarget(
			// 小于Depth的Flag都包含了清除Depth功能, 所以用<=, 不过这种写法感觉挺蠢的, 就不能用位运算么
			flags <= CameraClearFlags.Depth,
			// 这里不用<=是因为, skybox的绘制flag本身就算是ClearColor了, 所以不用再额外Clear
			flags == CameraClearFlags.Color,
			// 如果清除的是solid color, 那么需要使用camera的background color, 而且要用linearSpace下的背景颜色
			// 因为前面在ProjSettings里把Color Space从Gamma改为了Linear
			flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear
		);

接下来就可以去改变Second Camera的Inspector里的Clear Flag属性了,如果改为Skybox是这样的,第二次绘制的时候把第一次的内容清空了,然后画了个Skybox:
在这里插入图片描述

如果改成Solid Color也差不多,无非是背景颜色改为了Camera.backgroundColor在linear space下的颜色:
在这里插入图片描述

如果换成了Depth,那么会保留颜色,但是新绘制的都会基于原本的作为Canvas,可以看到红色全部覆盖了原本的颜色。

在这里插入图片描述
而如果改为Not Clear,那么原本的深度信息还在,新的红色可能会被绿色这些opaque物体遮挡,如下图所示:
在这里插入图片描述

最后,通过视口变换,调整Second Camera的Viewport Rect,就能得到最终的效果(这一课终于搞完了。。。)
在这里插入图片描述


最后提一下,每一帧渲染多个Camera,意味着每帧多次Culing、Setup和Sorting等操作,每个视角只使用一个camera其实是最高效的做法

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-08-03 11:32:18  更:2021-08-03 11:33:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 4:50:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码