Unity-Shader详解-其二

前向渲染和延迟渲染

前向渲染和延迟渲染总的来说是我们的两种主要的渲染方式。

我们在Unity的Project Settings中的Graphic界面能够找到渲染队列的设定:

我们也可以在Main Camera这里进行设置:

那这里我们首先介绍一下两种渲染(Forward Renderring\Deferred Renderring)的基本概念:

渲染就像去餐馆吃饭,每一桌就是一个需要渲染的对象,我们在吃饭前要先指指点点(渲染的设置),前向渲染就是比较朴素的餐馆运行模式:针对每一桌客人,我们都执行:客人下单,上菜,执行完一桌之后我们才去咨询下一桌,这样的话问题:作为餐馆的服务人员,我们来回跑了太多次(调用GPU进行渲染的指令),且这样计算的总开销时间较长(下一桌客人一定要等到上一桌客人的菜上完才能点菜);延迟渲染就是我们先总的收集所有客人的点餐情况,然后根据总的要求进行上菜,这样最大的改善就是我们大大减少了咨询客人点菜情况的时间,且减小了整体的计算时长。

两种渲染方式在多光源场景下的性能差异尤为明显:前向渲染的过程中我们要分别每个渲染对象单独地计算完光照效果之后再计算下一个对象,但是对于延迟渲染来说,整个场景的所有物体只用计算一次光照,本身光照计算就几乎是开销最大的部分,这样可以相当大一部分节省性能。

但是显然延迟渲染并不是十全十美的,你能统计全餐馆的下单情况的前提是你得有足够大的一个记事本,在计算机里这个记事本叫做:G-BUFFER,我们会把各个对象的诸多信息放入这个缓冲区,然后后续再在缓冲区中统一计算光照。因此,从这个角度来说,我们可以认为延迟渲染是前向渲染的以空间换时间的一种方式。

我们现在展开来说:

通俗地说,针对场景里的多光源,前向渲染会给光源分为三个优先级:最亮的一档就会采取逐像素渲染,而最不重要的一档我们会直接使用球谐函数来生成一个结果避免复杂运算,中间的一档(逐顶点渲染)则不会超过四个。其中每个光源并不是一定处以某个优先级,而是一个两种优先级的混合叠加态。

上述说的光源的优先级和限制的逐像素渲染光源数都是可以修改的:

这是前向渲染处理多光源的方法,那么对于延迟渲染来说呢?

延迟渲染是不支持抗锯齿的,这是一个非常致命的问题,同时还不支持半透明:

 

因此,根据不同的场景来选择不同的渲染策略才是最重要的。 

阴影实现

还是那句话,现在并没有完美的生成阴影的算法,大多数都是有些缺陷的。

我们直接上代码来介绍吧:

Shader "Chapter3/chapter3_3_shadow"
{Properties{// 定义主颜色属性,可在材质面板中调整_MainColor ("Main Color", Color) = (1, 1, 1, 1)}SubShader{// -------- 基础Pass:处理主要光源的投影 --------Pass{// 标签定义,用于指定此Pass的光照模式为"ForwardBase"Tags{"LightMode" = "ForwardBase"}CGPROGRAM// 顶点着色器和片段着色器入口#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase // 多重编译,支持不同光照模型#include "UnityCG.cginc"      // 包含Unity常用工具函数和宏#include "Lighting.cginc"    // 包含Unity光照计算函数#include "AutoLight.cginc"   // 包含Unity自动化光照相关代码// 定义顶点到片段的数据结构struct v2f{float4 pos : SV_POSITION;   // 裁剪空间的顶点位置float3 normal : TEXCOORD0; // 法线,用于光照计算float4 vertex : TEXCOORD1; // 模型空间的顶点位置SHADOW_COORDS(2)           // 使用Unity预定义宏存储阴影坐标};fixed4 _MainColor; // 主颜色变量// 顶点着色器v2f vert (appdata_base v){v2f o;o.pos = UnityObjectToClipPos(v.vertex); // 转换顶点到裁剪空间o.normal = v.normal;                   // 传递法线信息o.vertex = v.vertex;                   // 传递顶点位置TRANSFER_SHADOW(o)                     // 计算阴影坐标并存储return o;}// 片段着色器fixed4 frag (v2f i) : SV_Target{// 计算法线方向float3 n = UnityObjectToWorldNormal(i.normal);n = normalize(n);// 计算世界空间中的光源方向float3 l = WorldSpaceLightDir(i.vertex);l = normalize(l);// 将顶点从模型空间转换到世界空间float4 worldPos = mul(unity_ObjectToWorld, i.vertex);// Lambert光照模型:计算法线与光线夹角的点积fixed ndotl = saturate(dot(n, l));fixed4 color = _LightColor0 * _MainColor * ndotl;// 叠加4个点光源的光照color.rgb += Shade4PointLights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, // 点光源位置unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,       // 点光源颜色unity_4LightAtten0, worldPos.rgb, n                     // 衰减和位置) * _MainColor;// 叠加环境光照color += unity_AmbientSky;// 使用Unity宏计算阴影衰减系数UNITY_LIGHT_ATTENUATION(shadowmask, i, worldPos.rgb)// 将阴影系数与颜色相乘,应用阴影效果color.rgb *= shadowmask;return color; // 返回最终颜色}ENDCG}// -------- 额外的Pass:处理其他逐像素灯光的投影 --------Pass{// 标签定义,此Pass用于附加光源,模式为"ForwardAdd"Tags{"LightMode" = "ForwardAdd"}// 混合模式:相加混合Blend One OneCGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdadd_fullshadows // 支持多重编译,包含完整阴影#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"// 定义顶点到片段的数据结构,与基础Pass一致struct v2f{float4 pos : SV_POSITION;float3 normal : TEXCOORD0;float4 vertex : TEXCOORD1;SHADOW_COORDS(2)};fixed4 _MainColor;// 顶点着色器v2f vert (appdata_base v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.normal = v.normal;o.vertex = v.vertex;TRANSFER_SHADOW(o)return o;}// 片段着色器fixed4 frag (v2f i) : SV_Target{// 计算法线和光照方向float3 n = UnityObjectToWorldNormal(i.normal);n = normalize(n);float3 l = WorldSpaceLightDir(i.vertex);l = normalize(l);// 转换顶点到世界空间float4 worldPos = mul(unity_ObjectToWorld, i.vertex);// Lambert光照计算fixed ndotl = saturate(dot(n, l));fixed4 color = _LightColor0 * _MainColor * ndotl;// 叠加点光源的光照color.rgb += Shade4PointLights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,unity_LightColor[0].rgb, unity_LightColor[1].rgb,unity_LightColor[2].rgb, unity_LightColor[3].rgb,unity_4LightAtten0, worldPos.rgb, n) * _MainColor;// 使用阴影宏计算阴影系数UNITY_LIGHT_ATTENUATION(shadowmask, i, worldPos.rgb)// 应用阴影到颜色color.rgb *= shadowmask;return color; // 返回最终颜色}ENDCG}}// 回退着色器,定义为DiffuseFallBack "Diffuse"
}

我想我需要首先介绍两个Tags中的LightMode:ForwardBase和ForwardAdd。

在代码中我们用:

            // 混合模式:相加混合Blend One One

的混合模式把这两种前向渲染的结果进行结合就可以得到一个完整的生成阴影的方法。

现在我们来看具体代码:

        // -------- 基础Pass:处理主要光源的投影 --------Pass{// 标签定义,用于指定此Pass的光照模式为"ForwardBase"Tags{"LightMode" = "ForwardBase"}CGPROGRAM// 顶点着色器和片段着色器入口#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase // 多重编译,支持不同光照模型#include "UnityCG.cginc"      // 包含Unity常用工具函数和宏#include "Lighting.cginc"    // 包含Unity光照计算函数#include "AutoLight.cginc"   // 包含Unity自动化光照相关代码// 定义顶点到片段的数据结构struct v2f{float4 pos : SV_POSITION;   // 裁剪空间的顶点位置float3 normal : TEXCOORD0; // 法线,用于光照计算float4 vertex : TEXCOORD1; // 模型空间的顶点位置SHADOW_COORDS(2)           // 使用Unity预定义宏存储阴影坐标};fixed4 _MainColor; // 主颜色变量// 顶点着色器v2f vert (appdata_base v){v2f o;o.pos = UnityObjectToClipPos(v.vertex); // 转换顶点到裁剪空间o.normal = v.normal;                   // 传递法线信息o.vertex = v.vertex;                   // 传递顶点位置TRANSFER_SHADOW(o)                     // 计算阴影坐标并存储return o;}// 片段着色器fixed4 frag (v2f i) : SV_Target{// 计算法线方向float3 n = UnityObjectToWorldNormal(i.normal);n = normalize(n);// 计算世界空间中的光源方向float3 l = WorldSpaceLightDir(i.vertex);l = normalize(l);// 将顶点从模型空间转换到世界空间float4 worldPos = mul(unity_ObjectToWorld, i.vertex);// Lambert光照模型:计算法线与光线夹角的点积fixed ndotl = saturate(dot(n, l));fixed4 color = _LightColor0 * _MainColor * ndotl;// 叠加4个点光源的光照color.rgb += Shade4PointLights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, // 点光源位置unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,       // 点光源颜色unity_4LightAtten0, worldPos.rgb, n                     // 衰减和位置) * _MainColor;// 叠加环境光照color += unity_AmbientSky;// 使用Unity宏计算阴影衰减系数UNITY_LIGHT_ATTENUATION(shadowmask, i, worldPos.rgb)// 将阴影系数与颜色相乘,应用阴影效果color.rgb *= shadowmask;return color; // 返回最终颜色}ENDCG}

我们来说新东西:

#pragma multi_compile_fwdbase // 多重编译,支持不同光照模型

这句指令就是允许我们的前向渲染根据场景中的光照动态地调整一些涉及着色器参数的关键字选择。

ForwardBase中主要分成三个部分:v2f,vert和frag。

v2f中我们可以看到熟悉的顶点坐标(裁剪空间和模型空间),法线和一个阴影的预定义宏,其中:

                float4 vertex : TEXCOORD1; // 模型空间的顶点位置SHADOW_COORDS(2)           // 使用Unity预定义宏存储阴影坐标

我们法线模型空间的顶点位置居然使用了TEXCOORD1来存储,这不是纹理坐标的语义吗?是的这确实是,但是其实也没人规定你不可以用,只要合乎语法即可(但是有一种语义不可以用,是的就是我们之前提到过的系统值语义:这种语义存储的内容是被规定好的特殊阶段的特殊数据,如果不符合则整个渲染流程报错),阴影的预定义宏则是提前为将来生成的阴影坐标分配好了存储的坐标索引(2)。

TRANSFER_SHADOW(o)                     // 计算阴影坐标并存储

这一步就是计算阴影坐标的方法,Unity的着色器语言为我们封装成了一个函数。

                // 将顶点从模型空间转换到世界空间float4 worldPos = mul(unity_ObjectToWorld, i.vertex);// Lambert光照模型:计算法线与光线夹角的点积fixed ndotl = saturate(dot(n, l));fixed4 color = _LightColor0 * _MainColor * ndotl;// 叠加4个点光源的光照color.rgb += Shade4PointLights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, // 点光源位置unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,       // 点光源颜色unity_4LightAtten0, worldPos.rgb, n                     // 衰减和位置) * _MainColor;// 叠加环境光照color += unity_AmbientSky;// 使用Unity宏计算阴影衰减系数UNITY_LIGHT_ATTENUATION(shadowmask, i, worldPos.rgb)// 将阴影系数与颜色相乘,应用阴影效果color.rgb *= shadowmask;return color; // 返回最终颜色

这里为什么我们要采取兰伯特模型而不是更精准的冯模型呢?

 我们根据兰伯特模型的公式计算出基本的漫反射光照颜色值之后,再叠加四个点光源的效果,这里我们用了Unity内置的Shade4PointLights函数:

 综上所述,现在我们的漫反射光照颜色值是兰伯特光照模型和点光源效果的总和,我们再添加一个unity自带的:

最后再乘以一个Unity自带的阴影衰落因子就得到了ForwardBase输出的结果。

然后是我们的ForwardAdd部分:
 

// -------- 额外的Pass:处理其他逐像素灯光的投影 --------
Pass
{// 标签定义,此Pass用于附加光源,模式为"ForwardAdd"Tags{"LightMode" = "ForwardAdd"}// 混合模式:相加混合Blend One OneCGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdadd_fullshadows // 支持多重编译,包含完整阴影#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"// 定义顶点到片段的数据结构,与基础Pass一致struct v2f{float4 pos : SV_POSITION;float3 normal : TEXCOORD0;float4 vertex : TEXCOORD1;SHADOW_COORDS(2)};fixed4 _MainColor;// 顶点着色器v2f vert (appdata_base v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.normal = v.normal;o.vertex = v.vertex;TRANSFER_SHADOW(o)return o;}// 片段着色器fixed4 frag (v2f i) : SV_Target{// 计算法线和光照方向float3 n = UnityObjectToWorldNormal(i.normal);n = normalize(n);float3 l = WorldSpaceLightDir(i.vertex);l = normalize(l);// 转换顶点到世界空间float4 worldPos = mul(unity_ObjectToWorld, i.vertex);// Lambert光照计算fixed ndotl = saturate(dot(n, l));fixed4 color = _LightColor0 * _MainColor * ndotl;// 叠加点光源的光照color.rgb += Shade4PointLights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,unity_LightColor[0].rgb, unity_LightColor[1].rgb,unity_LightColor[2].rgb, unity_LightColor[3].rgb,unity_4LightAtten0, worldPos.rgb, n) * _MainColor;// 使用阴影宏计算阴影系数UNITY_LIGHT_ATTENUATION(shadowmask, i, worldPos.rgb)// 应用阴影到颜色color.rgb *= shadowmask;return color; // 返回最终颜色}ENDCG
}

我们首先可以看到Blend One One,这是一种混合模式:

除此之外的ForwardAdd的代码几乎与ForwardBase的部分一模一样,我们都只采用兰伯特光照模型即可(其实具体的渲染模式和内部的光照模型并没有直接挂钩,不同的渲染模式主要是负责的职能不同而光照模型则是定义光照计算的方式不同)。

大体效果如图:

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

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

相关文章

C++ 中 std::tuple 使用详解

C 中 std::tuple 使用详解 基本概念 std::tuple 是 C11 引入的模板类&#xff0c;用于打包任意数量、任意类型的值在一起。可看作是类型安全的变长结构体。 #include <tuple>std::tuple<int, std::string, double> t(42, "hello", 3.14);创建 tuple 的…

WebRTC基于网页的视频会议,手写WebRTC流程(html)

WebRTC是web real-time communication网页及时通信的缩写&#xff0c;通过javascript就可以实现网页会话&#xff0c;基于浏览器开发出来多媒体应用&#xff0c; 以下是手写的WEBRTC调用本地摄像头的html代码&#xff0c;直接用浏览器打开&#xff0c;就可以使用 <!DOCTYPE…

MyBatis 官方子项目详细说明及表格总结

MyBatis 官方子项目详细说明及表格总结 1. 核心子项目说明 1.1 mybatis-3 GitHub 链接&#xff1a;https://github.com/mybatis/mybatis-3功能&#xff1a; MyBatis 核心框架的源码&#xff0c;提供 SQL 映射、动态 SQL、缓存、事务管理等核心功能。主要功能&#xff1a; 支持…

【虚幻C++笔记】碰撞检测

目录 碰撞检测参数详情示例用法 碰撞检测 显示名称中文名称CSphere Trace By Channel按通道进行球体追踪UKismetSystemLibrary::SphereTraceSingleSphere Trace By Profile按描述文件进行球体追踪UKismetSystemLibrary::SphereTraceSingleByProfileSphere Trace For Objects针…

推论阶梯——AI与思维模型【81】

一、定义 推论阶梯思维模型是一种用于分析和理解人们如何从观察到的事实,经过一系列的假设、推理和判断,最终得出结论的思维过程的理论框架。它将这个过程比喻为一个阶梯,每一步都建立在前一步的基础上,逐渐形成一个完整的推论。这个模型帮助我们意识到在思考和决策过程中…

小刚说C语言刷题——1109加密四位数

1.题目描述 某军事单位用 4位整数来传递信息&#xff0c;传递之前要求先对这个 4 位数进行加密。加密的方式是每一位都先加上 5然后对 10取余数&#xff0c;再将得到的新数颠倒过来。 例如&#xff1a;原数是 1379 &#xff0c;那么每位加 55对 10 取余数的结果为 6824 &…

云服务器和独立服务器的区别在哪

在当今数字化的时代&#xff0c;服务器成为了支撑各种业务和应用的重要基石。而在服务器的领域中&#xff0c;云服务器和独立服务器是两个备受关注的选项。那么&#xff0c;它们到底有何区别呢&#xff1f; 首先&#xff0c;让我们来聊聊成本。云服务器通常采用按需付费的模式…

【前端】【业务场景】【面试】在前端开发中,如何优化 SVG(可缩放矢量图形)的性能,特别是在处理复杂图形和动画时

SVG 性能优化&#xff1a;循序渐进 4 步法 目标&#xff1a;先减负 → 再复用 → 后加速 → 最后按场景微调 ① 精简—把包袱先丢掉 删除无用元素 隐藏/被遮挡的 <path>、未引用的 <defs> 里渐变、滤镜。 合并路径 同填充色或描边的路径 ⇒ SVGO / SVGOMG「Mer…

MySQL长事务的隐患:深入剖析与解决方案

MySQL长事务的隐患&#xff1a;深入剖析与解决方案 一、什么是长事务&#xff1f; 在数据库系统中&#xff0c;长事务(Long Transaction)通常指执行时间超过预期或系统设定阈值的事务。对于MySQL而言&#xff0c;虽然没有严格的时间定义&#xff0c;但一般认为执行时间超过数…

华为AR1200 telnet设置

华为路由配置TELNET登 &#x1f4fa; 启动TELNET服务 在华为路由器上启动TELNET服务&#xff0c;执行以下命令&#xff1a; telnet server enable &#x1f511; 配置AAA认证 进入AAA认证配置&#xff0c;创建一个路由器登录帐号admin123&#xff0c;并设置密码为huawei123&…

【Token系列】01 | Token不是词:GPT如何切分语言的最小单元

文章目录 01 | Token不是词&#xff1a;GPT如何切分语言的最小单元&#xff1f;一、什么是 Token&#xff1f;二、Token 是怎么来的&#xff1f;——BPE算法原理BPE核心步骤&#xff1a; 三、为什么不直接用词或字符&#xff1f;四、Token切分的实际影响五、中文Token的特殊性六…

如何快速高效学习Python?

如何快速高效学习Python&#xff1f; How to Fastly and Effectively Learn Python Programming? By JacksonML 1. Python年轻吗&#xff1f; Python自1991年诞生到现在&#xff0c;已经经历了三十四年或者更长时间了。毕竟&#xff0c;Python之父 – 吉多范罗苏姆先生(Gu…

NAT穿透

NAT是 Net Address Traslation的缩写&#xff0c;即网络地址转换 NAT部署在网络出口的位置。位于内网和公网之间&#xff0c;是连接内挖个主机和公网的桥梁&#xff0c;双向流量都必须经过NAT&#xff0c;装有NAT软件的路由器叫NAT路由器&#xff0c;NAT路由器拥有公网Ip NAT解…

搜索引擎的高级语法

文章目录 精确搜索&#xff1a;双引号站内搜索&#xff1a;site通配符搜索&#xff1a;*减号缩小范围&#xff1a;-文档搜索&#xff1a;filetypeURL搜索&#xff1a; inurl标题搜索&#xff1a;intitle正文搜索&#xff1a;intext参考链接 精确搜索&#xff1a;双引号 “ ” …

RAG vs 微调:大模型知识更新的最优解之争

一、技术本质&#xff1a;知识注入的两条路径 在大模型应用落地的实践中&#xff0c;RAG&#xff08;检索增强生成&#xff09;与微调&#xff08;Fine-tuning&#xff09;已成为知识更新的两大核心技术路径。二者的本质差异在于是否对模型参数进行修改&#xff1a; 维度RAG微…

解释器体系结构风格-笔记

解释器&#xff08;Interpreter&#xff09;是一种软件设计模式或体系结构风格&#xff0c;主要用于为语言&#xff08;或表达式&#xff09;定义其语法、语义&#xff0c;并通过解释器来解析和执行语言中的表达式。解释器体系结构风格广泛应用于编程语言、脚本语言、规则引擎、…

浏览器f12可以搜索接口的入参 和返回内容

浏览器f12可以搜索接口的入参 和返回内容

vue3+element-push 实现input框粘贴图片或文本,图片上传。

vue3element-push 实现input框粘贴图片或文本&#xff0c;图片上传。 <el-inputstyle"height: 100px; width: 100%"paste.capture.prevent"pasting"v-model"textMsg"placeholder"请输入"/>// 展示上传的列表--可不要<divsty…

高效使用DeepSeek对“情境+ 对象 +问题“型课题进行开题!

目录 思路"情境 对象 问题"型 课题选题的类型有哪些呢&#xff1f;这要从课题题目的构成说起。通过对历年来国家社会科学基金立项项目进行分析&#xff0c;小编发现&#xff0c;课题选题类型非常丰富&#xff0c;但一般是围绕限定词、研究对象和研究问题进行不同的组…

cursor改Goland操作习惯

步骤1&#xff1a;设置主题 步骤2&#xff1a;安装最新go插件 步骤3&#xff1a;安装最新go版本 需要使用最新版本go1.24.1,设置玩环境变量&#xff0c;需要关闭cursor进程再打开 步骤4&#xff1a;安装go相关工具 Command Shift P安装完成后需要把go版本设置回自己项目合…