Learn SRP 02

news2024/12/23 15:44:33

3.Editor Rendering

3.1Drawing Legacy Shaders

因为我们的管线只支持无光照的着色过程,使用其他不同的着色过程的对象是不能被渲染的,他们被标记为不可见。尽管这是正确的,但是它还是隐藏了场景中一些使用错误着色器的对象。所以让我们来渲染它们吧,但是要和无光照分开。

为了能够兼容所有unity默认的着色器我们必须使用着色器标记ID(ShaderTagId),把Always,ForwardBase,PrepassBase,Vertex,VertexLMRGBM,和VertexLM放到一个静态数组里。

	static ShaderTagId[] legacyShaderTagIds = {
		new ShaderTagId("Always"),
		new ShaderTagId("ForwardBase"),
		new ShaderTagId("PrepassBase"),
		new ShaderTagId("Vertex"),
		new ShaderTagId("VertexLMRGBM"),
		new ShaderTagId("VertexLM")
	};

DrawVisibleGeometry函数之后我们使用一个单独的方法绘制所有不支持的着色器。因为它们是错误的着色过程所以无论如何渲染结果都是错误的,所以我们不用关心其他设置。我们可以使用默认的过滤设置FilteringSetting.defaultValue属性。

我们可以多此调用DrawingSettingSetShaderName方法,使用序列和标记作为参数。因为在构造函数中已经加入了第一个Tag,所有我们对数组的操作从第二个开始。

	void DrawUnsupportedShaders()
	{
		var drawingSettings = new DrawingSettings(
			legacyShaderTagIds[0], new SortingSettings(camera)
		);
		for (int i = 1; i < legacyShaderTagIds.Length; i++)
		{
			drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
		}
		var filteringSettings = FilteringSettings.defaultValue;

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

3.2Error Material

为了明确的指明哪些对象使用了不支持的着色器我们用Unity错误的着色器来绘制它们。我们通过Shader.Find函数,使用Hidden/InternalErrorShader作为参数来构造一个全新的材质。我们将这个材质用静态字段缓存,这样我们就不用每帧都创建它了。然后将它赋值给DrawingSettingoverrideMaterial属性。

	static Material errorMaterial;

	…

	void DrawUnsupportedShaders () {
		if (errorMaterial == null) {
			errorMaterial =
				new Material(Shader.Find("Hidden/InternalErrorShader"));
		}
		var drawingSettings = new DrawingSettings(
			legacyShaderTagIds[0], new SortingSettings(camera)
		) {
			overrideMaterial = errorMaterial
		};
		…
	}

显示结果:

现在所有使用其他着色器的都变成粉色了

3.3Partial Class 

绘制无效的对象对开发模式是有用的,但是对于正式版是没有意义的。所以我们把所有的只在编辑器模式有用的代码放在一个分开的局部类文件CameraRenderer中。首先复制原始的CameraRenderer文件,并将其命名为CamreaRenderer.Editor

然后将原始的CameraRenderer转换为局部类,并从中删除ShaderTagId数组,错误的材质,和DrawUnsupportedShaders方法。

什么是局部类?

这是一种拆分类-结构体的方法,把他们放入不同的部分,存储在不同的文件中。这样做的目的是为了更好的组织代码。典型的用例是将自动生成的代码与手动编写的代码分开。就编译器而言,它们都是同一类定义的一部分。他们在Object Management, More Complex Levels教程中有介绍。

清理另一个局部类文件只包含上个文件我们之前删除的那部分。

这个编辑器部分的内容只需要在编辑器中执行,所以我们用条件宏来标记。

但是,现在发布模式运行还是会失败,因为另一个局部类中包含DrawUnsupportedShaders函数的调用,它应该只能存在于编辑器模式。为了解决这个问题,我们需要让这个方法正常执行。为此我们需要在方法前面用partial进行声明,类似于抽象方法声明。我们可以在类定义的任何部分中执行此操作,所以让我们把它放在编辑器部分。完整的方法声明也必须标记为partial。

public  partial class CameraRenderer : MonoBehaviour
{

    partial void DrawUnsupportedShaders();
#if UNITY_EDITOR
    static Material errorMaterial;

    static ShaderTagId[] legacyShaderTagIds = {
        new ShaderTagId("Always"),
        new ShaderTagId("ForwardBase"),
        new ShaderTagId("PrepassBase"),
        new ShaderTagId("Vertex"),
        new ShaderTagId("VertexLMRGBM"),
        new ShaderTagId("VertexLM")
    };

    partial void DrawUnsupportedShaders()
    {
        if (errorMaterial == null)
        {
            errorMaterial =
                new Material(Shader.Find("Hidden/InternalErrorShader"));
        }
        var drawingSettings = new DrawingSettings(
            legacyShaderTagIds[0], new SortingSettings(camera)
        )
        {
            overrideMaterial = errorMaterial
        };
        for (int i = 1; i < legacyShaderTagIds.Length; i++)
        {
            drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
        }
        var filteringSettings = FilteringSettings.defaultValue;

        context.DrawRenderers(
            cullingResults, ref drawingSettings, ref filteringSettings
        );
    }
#endif
}

3.4Drawing Gizmos

现在我们的管线不能绘制辅助图标(Gizmos),无论是场景视图还是游戏视图激活的时候都不会显示。

我们可以检查辅助图标是否应该被绘制通过执行UnityEditor.Handles.ShouldRenderGizmos函数。如果需要显示,我们必须调用context的DrawGizmos方法,并传递camera作为第一个参数,传入第二个参数来确定辅助图标的哪些子集需要被绘制。这里有两个子集,在ImageEffects(屏幕后效)之前和之后。因为我们现在不支持ImageEffects因此两个方法我们都会调用。我们在一个只有Editor模型运行的方法DrawGizmos。

using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;

partial class CameraRenderer {
	
	partial void DrawGizmos ();

	partial void DrawUnsupportedShaders ();

#if UNITY_EDITOR

	…

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

	partial void DrawUnsupportedShaders () { … }

#endif
}

辅助图标将被绘制在所有对象绘制之后。

3.5Drawing Unity UI

另一个需要我们注意的事情是Unity的用户界面系统。例如,我们通过GameObject/UI/Button添加一个按钮来创建一个简单的UI。它将显示在game窗口,但是不会显示在scene窗口。

帧调试器显示UI是通过新增的UGUI.Rendering.RenderOverlays绘制的,并不是通过我们的渲染管线绘制的

这是因为UI的默认绘制模式是ScreenSpace-Overlay

当我们把UI的渲染模式修改成ScreenSpace-Camera模式。使用主摄像机作为RenderCamera的参数,可以使UI的渲染成为透明几何体的一部分。

在渲染scene窗口的时候我们可以通过执行ScriptableRenderContext.EmitWorldGeometryForSceneView函数和camera参数来将UI添加到世界的几何体中。在一个只有编辑器模式允许的新函数PrepareForSceneWindow中执行这些逻辑。当CameracameraType属性是CameraType.SceneView的时候我们渲染这个场景摄像机。

	partial void PrepareForSceneWindow ();

#if UNITY_EDITOR

	…

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

因为我们要把几何体添加到场景中,所有我们必须在Culling剔除之前执行这个函数。

执行完这一步后UI就会被绘制到场景视图中了。

4.Multiple Cameras

在场景中有可能同时存在多个已激活的摄像机,如果是这样我们必须保证他们同时工作。

4.1Two Cameras

每一个摄像机都有一个深度(Depth)值,默认的主摄像机的值为-1。摄像机按深度增加的顺序进行渲染。为了看到这些,复制主摄像机,改它改名为SecondaryCamera,并把深度值设置为0。同时我们给它另一个tag值,因为MainCamera的tag只能被一个摄像机使用。

现在场景被渲染了两次。渲染结果的表现仍然是一样的,因为渲染过程中执行了清楚操作。FrameDebugger向我们展示了这个过程,但是因为相邻的具有相同名字的采样被合并成我们最终得到的一个Render Camera列表。

如果每个摄像机都有自己的范围,就更清楚了。为了使之成为可能,添加一个只在编辑器模式运行的函数PrepareBuffer方法,使得缓冲区的名字和相机的名字一样。

partial void PrepareBuffer ();

#if UNITY_EDITOR

	…
	
	partial void PrepareBuffer () {
		buffer.name = camera.name;
	}

#endif

现在看就清晰很多了

4.2Dealing with Changing Buffer Names

尽管现在FrameDebugger能够分开显示每个相机的采样信息,但是当我们进入运行模式时Unity的控制台将充满警告并告诉我们BeginSampleEndSample的计数必须要匹配(但是这里我没有报错不知道为啥)。因为我们对采用和他们的缓冲区使用了不同的名字,所以匹配混淆了。除此之外,我们每次访问camera的name属性也会造成内存的分配,所以我们不想在创建的时候这样做。

为了解决这两个问题我们添加一个SampleName字符串属性。在编辑器模式我们应该在PrepareBuffer函数中设置它和缓冲区的名字,运行模式它就是RenderCamera类的一个字符串常量值。

#if UNITY_EDITOR

	…

	string SampleName { get; set; }
	
	…
	
	partial void PrepareBuffer () {
		buffer.name = SampleName = camera.name;
	}

#else

	const string SampleName = bufferName;

#endif

然后在开始采样和结束采样的时候都使用SampleName

我们可以看到不同通过查看 profiler-打开 Window/Analgsis/Profiler。切换到Hierarchy模式然后按GC数据进行排序。可以看到有一个GC.Alloc,是96B。这部分就是在编辑器模式下产生的GC,在打包以后这部分GC就会消失。教程里后面摄像机数组的48bit不知道为啥没有出现。

4.3Layers

摄像机可以被配置成只看到某些层级的物体。这是通过调整摄像机的Culling Mask实现的。为了看到这个效果我们把所有使用标准着色器的对象设置为IgnoreRaycast层级 

然后从主摄像机的Culling Mask中排除这个层级。

SecondaryCameraCulling Mask设置为只有IgnoreRaycast

因为SecondaryCamera最后渲染,所以我们只能看到无效的对象。

4.4Clear Flags

我们可以通过设置CameraClearFlags来让第二个摄像机在第一个摄像机的渲染结果之上进行渲染(在之前,渲染第二个摄像机时我们会清除所有缓冲)。

	void Setup () {
		context.SetupCameraProperties(camera);
		CameraClearFlags flags = camera.clearFlags;
		buffer.ClearRenderTarget(true, true, Color.clear);
		buffer.BeginSample(SampleName);
		ExecuteBuffer();
	}

CameraClearFlags枚举定义了Skybox,Color,Depth, andNothing四个值,除了 Nothing,其他情况都需要清理深度缓冲区。观看源代码还有一个SolidColor

我们只需要在标记设置为Color时清除颜色缓冲区,因为在Skybox中,我们必然都会替换之前所有的颜色数据。

如果想要清除为纯色,我们就必须使用摄像机的背景色。但是因为我们在线性空间进行渲染,我们必须把颜色转换为线性空间,所以我们使用camera.backgroundColor.linear。在其他情况下,颜色并不重要,所以我们可以用Color.clear。

SecondaryCamera的清理标记定义如何组合两个摄影机的渲染。在skybox或color的情况下,先前的结果将被完全替换。如果仅清除深度,则辅助摄影机将正常渲染,但不会绘制天空盒,因此以前的结果显示为背景。当什么都没有清除时,深度缓冲区将保留,因此未照亮的对象最终将遮挡无效对象,就像它们是由同一台摄像机绘制的一样。但是,前一个摄像机绘制的透明对象没有深度信息,因此像 skybox之前所做的那样被绘制。

通过调整相机的 Viewport Rect ,也可以将渲染区域缩小到整个渲染目标的一小部分。其余渲染目标保持不受影响。在这种情况下,将使用Hidden / InternalClear着色器进行清除。模板缓冲区用于将渲染限制在视口区域。这里将第二个相机渲染到右半屏幕。

请注意,如果每帧渲染一台以上的摄像机就必须同时进行多次剔除,设置,分类等操作这是很耗的。一种有效的方式是让每一个摄像机都有自己的渲染视角。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1601693.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Golang】并发编程之三大问题:原子性、有序性、可见性

目录 一、前言二、概念理解2.1 有序性2.2 原子性后果1&#xff1a;其它线程会读到中间态结果&#xff1a;后果2&#xff1a;修改结果被覆盖 2.3 可见性1&#xff09;store buffer(FIFO)引起的类似store-load乱序现象2&#xff09;store buffer(非FIFO)引起的类似store-store乱序…

代理模式(结构型模式)

目录 1、概述 2、结构 2.1、角色分类 2.2、类图 3、静态代理 3.1、案例类图 3.2、案例代码 4、JDK 动态代理 4.1、案例代码 4.2、底层原理 4.3、执行流程说明 5、CGLib 动态代理 5.1、案例代码 6、三种代理的对比 6.1、JDK代理和CGLib代理 6.2、动态代理和静态…

【Latex排版小记录】latex设置两端对齐

Latex排版的时候遇到了公式/英文过长超出来的情况 解决办法&#xff1a;在\begin{document}里面增加\begin{sloppypar} \begin{document} \begin{sloppypar}\end{sloppypar} \end{document}

Spring Boot - 利用MDC(Mapped Diagnostic Context)实现轻量级同步/异步日志追踪

文章目录 Pre什么是MDC&#xff08;Mapped Diagnostic Context&#xff09;Slf4j 和 MDC基础工程工程结构POMlogback-spring.xmlapplication.yml同步方式方式一&#xff1a; 拦截器自定义日志拦截器添加拦截器 方式二&#xff1a; 自定义注解 AOP自定义注解 TraceLog切面 测试…

Java实现优先级队列(堆)

前言 在学习完二叉树的相关知识后&#xff0c;我们对数据结构有了更多的认识&#xff0c;本文将介绍到优先级队列(堆&#xff09; 1.优先级队列 1.1概念 前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能…

【机器学习】探究Q-Learning通过学习最优策略来解决AI序列决策问题

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

学会 Python 后可以做什么副业?

近年来&#xff0c;Python凭借其简洁易入门的特点受到越来越多人群的青睐。 当然这不仅仅是针对程序员来说&#xff0c;对于一些学生、职场人士也是如此。 Python为什么会大受欢迎呢&#xff1f;因为Python还被大家称为“胶水语言&#xff0c;它适用于网站、桌面应用开发、[自…

蓝牙耳机哪个牌子好用?力荐五款实力超群机型,快收藏!

​随着蓝牙耳机的普及&#xff0c;越来越多的年轻人开始追求这种无线的便利。市场上品牌众多&#xff0c;款式多样&#xff0c;选择起来确实让人眼花缭乱。我整理了一份蓝牙耳机品牌排行榜前十名&#xff0c;希望能为你提供一些参考&#xff0c;帮助你找到心仪的耳机。 一、如何…

C语言堆区内存管理

一、C语言编译的内存分配 二、堆区空间的分配 1、malloc函数 功能&#xff1a;从堆区分配内存 #include <stdlib.h> void *malloc(unsigned int size)//size 分配内存的字节数2、free函数 功能&#xff1a;释放内存 #include <stdlib.h> void free(void *ptr)…

L2-045 堆宝塔 - java

L2-045 堆宝塔 Java (javac) 时间限制 500 ms 内存限制 256 MB 其他编译器 时间限制 400 ms 内存限制 64 MB 栈限制 8192 KB 题目描述&#xff1a; 堆宝塔游戏是让小朋友根据抓到的彩虹圈的直径大小&#xff0c;按照从大到小的顺序堆起宝塔。但彩虹圈不一定是按照直径的大小顺…

Java NIO,高效操作I/O流的必备技能

Java IO在工作中其实不常用到&#xff0c;更别提NIO了。但NIO却是高效操作I/O流的必备技能&#xff0c;如顶级开源项目Kafka、Netty、RocketMQ等都采用了NIO技术&#xff0c;NIO也是大多数面试官必考的体系知识。虽然骨头有点难啃&#xff0c;但还是要慢慢消耗知识、学以致用哈…

Spring核心容器总结

2.2 核心容器总结 2.2.1 容器相关 BeanFactory是IoC容器的顶层接口&#xff0c;初始化BeanFactory对象时&#xff0c;加载的bean延迟加载 ApplicationContext接口是Spring容器的核心接口&#xff0c;初始化时bean立即加载 ApplicationContext接口提供基础的bean操作相关方法…

CTFHUB-技能树-Web前置技能-文件上传(无验证,JS前端验证,前端验证)

CTFHUB-技能树-Web前置技能-文件上传&#xff08;无验证&#xff0c;JS前端验证&#xff0c;前端验证—.htaccess&#xff09; 文章目录 CTFHUB-技能树-Web前置技能-文件上传&#xff08;无验证&#xff0c;JS前端验证&#xff0c;前端验证—.htaccess&#xff09;文件上传无验…

基于 LSTM 模型的古诗词自动生成算法实现及系统实现

近年来&#xff0c;研究者在利用循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;进行古诗自动生成方面取得了显著的效果。但 RNN 存在梯度问题&#xff0c;导致处理时间跨度较长的序列时 RNN 并不具备长期记忆存储功能。随后&#xff0c;出现的基…

(十一)C++自制植物大战僵尸游戏客户端更新实现

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/cFP3z 更新检查 游戏启动后会下载服务器中的版本号然后与本地版本号进行对比&#xff0c;如果本地版本号小于服务器版本号就会弹出更新提示。让用户选择是否更新客户端。 在弹出的更新对话框中有显示最新版本更新的内容…

全球化背景下的海外社媒营销战略:趋势洞察与策略调整

随着全球化的不断深入&#xff0c;企业在海外市场的竞争愈发激烈。在这一背景下&#xff0c;海外社交媒体平台成为了企业品牌推广和营销的重要渠道。本文Nox聚星将和大家探讨全球化背景下&#xff0c;企业如何利用海外社交媒体平台进行品牌推广和营销&#xff0c;并分析企业如何…

Git分布式版本控制系统——在IDEA中使用Git(一)

一、在IDEA中配置Git 本质上还是使用的本地安装的Git软件&#xff0c;所以需要在IDEA中配置Git 打开IDEA的设置页面&#xff0c;按照下图操作 二、在IDEA中使用Git获取仓库 1、本地初始化仓库 2、从远程仓库克隆 方法一&#xff1a; 方法二&#xff1a; 三、.gitignore文件…

#陶晶驰串口屏使用

1.陶晶驰串口屏输入要连接的wifi信息实现 &#xff08;1&#xff09;选择文本控件 &#xff08;2&#xff09;给文本控件配置输入键盘&#xff0c;id代表用户名&#xff0c;password代表wifi密码&#xff08;注意wifi的频段需要为2.4GHz&#xff09; &#xff08;3&#xff0…

k8s之etcd

1.特点&#xff1a; etcd 是云原生架构中重要的基础组件。有如下特点&#xff1a; 简单&#xff1a;安装配置简单&#xff0c;而且提供了 HTTP API 进行交互&#xff0c;使用也很简单键值对存储&#xff1a;将数据存储在分层组织的目录中&#xff0c;如同在标准文件系统中监…

RAG (Retrieval Augmented Generation) 结合 LlamaIndex、Elasticsearch 和 Mistral

作者&#xff1a;Srikanth Manvi 在这篇文章中&#xff0c;我们将讨论如何使用 RAG 技术&#xff08;检索增强生成&#xff09;和 Elasticsearch 作为向量数据库来实现问答体验。我们将使用 LlamaIndex 和本地运行的 Mistral LLM。 在开始之前&#xff0c;我们将先了解一些术…