Catlike Coding | Chapter 1 Pipeline Asset | Blurred code

Catlike Coding | Chapter 1 Pipeline Asset

2022/03/08

LastMod:2022/08/17

Categories: CG

笔记栏文章声明

Warning

笔记栏所记录文章往往未经校对,或包含错误观点以及认知,亦或采用只有自身能够理解的表达。

这是来自对Catlike Coding的SRP教程的整理,约等于简单的复制粘贴,其LicenseMIT-0,版权作者为Jasper Flick。 目录包括


Chapter 1 Pipeline Asset

Pipeline Asset

需要创建一个Asset文件,没什么特殊的就是一个保存设置的序列化文件,里面有一个虚函数CreatePipeline作为工厂函数返回一个IRenderPipeline抽象类,该工厂函数需要被override

menuName允许在右键菜单中加入一个新的Rendering的菜单。

[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset { … }

Pipeline Constructor

Camera Render

有了工厂函数需要真正的pipeline对象,其需要继承RenderPipeline类,并且override其中的纯虚函数Render

Render函数会被每帧调用,在这个主循环里处理渲染事件。 可以创建CameraRender类接收一个cameracontext对象并处理这个相机的渲染事件,主循环里把渲染事件发给每个相机处理。

Draw Skybox

Unity自带方法: context.DrawSkybox(camera)绘制天空盒。

这些渲染指令并非立刻提交,而是需要通过一个context.Submit()方法提交给GPU。

为了正确渲染画面,调用draw指令前需要设置camera的参数,需要用到context.SetupCameraProperties(camera) 方法。

Command Buf

老朋友cmdbuf了。DrawSkybox这种API是特化的,可以通过context对象调用,其他的普通的绘制指令需要用cmdbuf来记录,最后提交给GPU。

Clear RenderTargets

如果我们单独写一条buffer.ClearRenderTarget(true, true, Color.clear);,Unity会尝试画一个Quad来清空当前的rt。

如果我们合并写

		context.SetupCameraProperties(camera);
		buffer.ClearRenderTarget(true, true, Color.clear);

会调用glClearColor这种API来清空(猜测),反正是更高效的方式。

正确的清空姿势

Culling

绘制的时候需要剔除不在视椎体内的物体,剔除所需要的参数被记录在ScriptableCullingParameters结构体中。 TODO: 查看该结构体的内容

剔除所需要的参数不用手动计算,用bool camera.TryGetCullingParameters(out p)计算,该函数在成功计算参数的时候返回True,并把内容填充到p里。如果返回False说明这个相机参数设置不合法,可以直接报错。

实际执行提出的指令为CullingResults context.Cull(ref p)指令,在buf里记录一个提出指令,其返回一个CullingResults对象,ref是为了引用传递,CullingResultscontext.DrawRenderers的时候会被用到。(This data includes information about visible objects, lights, and reflection probes. )

Drawing Geometry

DrawRenderers是绘制接口,需要cullingResultsdrawingSettingsfilterSettings

context.DrawRenderers(
    cullingResults, ref drawingSettings, ref filteringSettings
);

drawingSettings需要指定物体绘制的顺序和使用的ShaderTagIDFilterSettings需要筛选渲染队列中允许的对象。 典型的绘制不透明物体的参数设置

        var sortingSettings = new SortingSettings(camera)
        {
            //https://docs.unity3d.com/ScriptReference/Rendering.SortingCriteria.CommonOpaque.html
            criteria = SortingCriteria.CommonOpaque //大概是从前到后,多个flags的组合,和材质也有关系
        };
        var drawingSettings = new DrawingSettings(
            unlitShaderTagId, sortingSettings
        )       
        // 这里需要注意的是DrawSettings里面的SetShaderPassName查找的不不不(重要的事情说三遍)是Shader的Pass中那个Name “xxx” 不是那个!!!找的是Tags中的LightMode!
        // https://www.cnblogs.com/shenyibo/p/12485235.html
        // 按顺序查找Pass,先查找Unlit,再查找Lit
        drawingSettings.SetShaderPassName(1,litShaderTagId);
        var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);
        context.DrawRenderers(
            cullingResults, ref drawingSettings, ref filteringSettings
        );

通过调整FilterSettings可以筛选出渲染队列的不同物体,从而实现透明物体和不透明物体的不同批次渲染。(需要调整sorting到CommanTransparent),因为透明物体不写深度,所以从前到后渲染不能起到剔除的效果。而要实现正确的颜色混合需要从后到前渲染。 然而其实透明渲染从后到前也并不一定正确,考虑一个非常细长的透明杆沿着Z轴放置,如何定义它的位置是一个问题。

Editor Rendering

在编辑器内的体验提升。

渲染不支持的材质

Unity自带一个Hidden/InternalErrorShader的shader,熟悉的紫色Shader,新建一个材质。 然后在drawingSettings中对不支持的ShaderTagId设置overrideMaterial,这些带有不支持的ShaderTag的物体就会经过错误的material渲染成醒目的紫色。

		var drawingSettings = new DrawingSettings(
			legacyShaderTagIds[0], new SortingSettings(camera)
		) {
			overrideMaterial = errorMaterial
		};

Partial Class

一个C#的特性。允许通过partial关键字把一个类分散定义在多个文件内,适合将一些只在Editor内运行的代码分散出来,并加上

partial class CameraRenderer {
#if UNITY_EDITOR
    // some work only valid in Editor
#endif
}

函数也同样可以声明为partial,如果一个声明为partial的代码在编译的时候没有实现(因为实现被宏注释掉了),那么编译器会自动略过对这些函数的调用(The compiler will strip out the invocation of all partial methods that didn't end up with a full declaration.)。

Draw Gizmos

只在Editor里有效,并且Gizmos应该只在物体被选中的时候被绘制,接口是bool UnityEditor.Handles.ShouldRenderGizmos() 直接调API就完事,Gizmos分为两类不过我暂时不知道有啥区别。

	partial void DrawGizmos () {
		if (Handles.ShouldRenderGizmos()) {
			context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
			context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
		}
	}

Drawing Unity UI 绘制UI

https://docs.unity3d.com/Packages/[email protected]/manual/UICanvas.html

UI的绘制可以分为三种情况。

edit-715422187a0c42adb4488350fc0ee4ec-2022-02-08-01-15-33

Overlay的绘制是绘制一个半透明的quad,是屏幕空间的类似于后处理的效果,直接绘制在framebuffer上,其不通过自定义管线。

需要选择一个相机作为Render Camera,然后还会把UI放在透明物体里渲染。

就像真实的物体一样被渲染,可以调节他的transformation之类信息。其单位变成mm,而在屏幕空间其单位为像素pixel

比如HoLolens的UI就是用world mode创建的。

在Unity的编辑器的Scene窗口(Scene窗口会额外创建一个摄像机),UI都会以world mode来绘制。在自定义管线中我们需要判断相机是否为scene创建的相机,并且调用额外的函数以在scene窗口中绘制UI。

	partial void PrepareForSceneWindow () {
		if (camera.cameraType == CameraType.SceneView) {
			ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
		}
	}

多摄像机支持

相机有个Depth参数,默认从-1开始,从小到大渲染。

Camera name and Buffer Name

为了调试方便(在profiler)里看起来更清楚,可以把cmdbuf的名字和camera.name绑定起来,并且通过一个常量SampleName保存。 后续的 buf.BeginSample(name)EndSample都可以调用这个常量而无需内存分配。

    const string SampleName = bufferName;
	partial void PrepareBuffer () {
		buffer.name = SampleName = camera.name;
	}

Layers

可以在inspector里调整camera可以看到哪些层,物体也可以选择其存在与哪些层,可以选择性提出一些物体

Clear RenderTarget

camera的clear属性被保存在camera.clearFlags里。 The CameraClearFlags enum defines four values. From 1 to 4 they are Skybox, Color, Depth, and Nothing.

只要不是nothing就要清理深度,color可以用camera.backgroundColor.linear获取inspector里的颜色。