一、延迟着色和前向渲染

很可惜的是没有什么前置,OpenGL 本是要写一篇延迟着色的笔记的,但是怎么看这都不属于OpenGL基础的范畴

先考虑最简单的情况:只有最多一个光源,这个时候当然就按照正常渲染流程就好了

→ 稍微复杂点:有多个光源,但是数量比较少(夜晚有路灯的街道),很容易想到在着色中 for 循环所有光源再叠加颜色的方法,确实之前 OpenGL 的 demo 就是这样做的,又或者是多个 PASS,一个 PASS 对应一个光照,并且按照混合的设置进行叠加,这些都没有太大的问题。不管如何,像这样在场景中根据所有光源照亮一个物体,之后再渲染下一个物体的方法都叫做前向渲染(Forward Rendering)

→ 终极情况:场景中有大量的光源(演出现场、舞台),上面的渲染方法在这种情况下就会束手无策,因为单从它的时间复杂度来讲就是平方级别的(,其中 n 为光源数量,m 为物体数量),这个时候延迟渲染(Deferred Rendering)就为了解决上述问题而诞生了,它大幅度地改变了我们之前渲染物体的方式

延迟渲染的过程:

  1. 离线!还是离线,先渲染场景一次以获取物体的各种几何信息(位置、颜色、法向量等),并储存在一系列叫做G缓冲(G-buffer)的纹理中,这个阶段也叫做几何处理阶段(Geometry Pass)
  2. 紧接着再第二个阶段使用G缓冲内的纹理数据:渲染一个屏幕大小的方形,并使用G缓冲中的几何数据对每一个片段计算场景的光照,这个阶段也叫做光照处理阶段(Lighting Pass)

这样总体时间复杂度就不再是平方级别,而更多取决于片段数量(,其中 n 为光源数量,p 为片段数量,一般都为分辨率大小)这一定值

二、Unity中的渲染路径

小明和小红同时来到的银行,小明在长期996的努(bo)力(xue)下,存了不少钱,因此她本次去银行的目的就是:储蓄,这样在银行柜员的指引下,小明成功办理了一张储蓄卡;而打工是不可能打工的小红则需要一些资金做自己奶茶店的开店准备,因此信誉良好的他本次去银行的目的就是:借款,这样在银行柜员的指引下,小明成功办理了一张信用卡

同样去银行,目的不同,银行就会提供不同的服务,对于存款和借款,整体的流程和操作都是不一样的,如果你没有提出你的要求,柜台的服务员也就无法直接给予你帮助

指明 Unity 渲染路径其实正是同样的道理,在 Unity 里,渲染路径(Rendering Path)决定了光照是如何运用到 UnityShader 中的,就像我们向银行提出我们的要求一样,我们也要为每个 Pass 指定它使用的渲染路径,这样 Unity 才能知道如何给予我们正确的光源和处理后的光照信息

Unity支持的渲染路径主要有三种:

  • 前向渲染路径:对应第一节的前向渲染
  • 延迟渲染路径:对应第一节的延迟着色
  • 顶点照明渲染路径(VertexLitRenderingPath):前向渲染路径的一个子集,不支持逐像素光照,已基本废弃

关于渲染路径的设置:

Edit → ProjectSettings,或者针对不同的摄像机使用不同的渲染路径

三、LightMode标签

完成的上一节的渲染路径设置后,就可以在每个 Pass 中使用标签来指定该 Pass 的渲染路径了,这是通过设置 Pass 的 LightMode 标签实现的。不同类型的渲染路径可能会包含多种标签设置,如果该 Pass 对应的渲染路径和 Unity 设置的不同,该 Pass 就不会被渲染

LightMode 标签支持的路径渲染设置有:

  • Always:无论 Unity 设置的哪种渲染路径,该 Pass 总会被渲染,不会计算任何光照
  • ForwardBase:前向渲染,基本 Pass,会计算环境光、最重要的平行光、逐顶点/SH 光源和 Lightmaps
  • ForwardAdd:前向渲染,额外 Pass:每个 Pass 对应一个光源,可以计算额外的逐像素光源
  • Deferred:延迟渲染,渲染G缓冲
  • ShadowCaster:用于把物体的深度信息渲染到 ShadowMap 中
  • PrepassBase:Unity5版本之前的延迟渲染,会渲染法线和高光反射的指数部分,基本已废弃
  • PrepassFinal:Unity5版本之前的延迟渲染,和 PrepassFinal 组合,通过合并纹理光照等属性来渲染得到最终的颜色,基本已废弃
  • Vertex\VertexLMRGBM\VertexLM:定点照明渲染,基本已废弃

三种光照处理方式,性能从低到高,效果从好到差:

  • 逐像素光照(Per-Pixel)
  • 逐顶点光照(Per-Vertex)
  • 球谐函数(Spherical Harmonics, SH)处理:及其高效的光照处理方式,但一般只应用漫反射

Unity 决定对一个光源使用哪种处理方式取决于光源的类型(平行光/点光源…)和它的渲染模式,在这里进行设置:

判断规则如下:

  • 场景中最亮的平行光一定是逐像素处理的
  • 渲染模式被设置为 Not Important 的光源会按照逐顶点或者 SH 处理
  • 渲染模式被设置为 Important 的光源会按照逐像素处理
  • 根据规则1-3,如果最终得到的逐像素光源数量小于 ProjectSettings → Quality 中的逐像素光源数量,那么会有更多的光源以逐像素的方式进行渲染

四、Unity中的前向渲染

完成了上一节的光照设置后,就可以来关心 Pass 本身了

前向渲染有两种 Pass:Base Pass 和 Addtional Pass,其渲染设置及常规光照计算如下:

前向渲染的两种Pass,此图来源于《UnityShader入门精要》

关于里面的重点:

  • 对于前向渲染来说,如果只定义了一个 Base Pass 和一个 Additional Pass,那么 Base Pass 会仅执行一次,而 Additional Pass 可能会被执行多次,即每个逐像素光照都会执行一次 Additional Pass
  • 关于Bland One One,是为了将每个帧缓冲中的光照结果进行叠加,也因此,向自发光和环境光这种只需要被计算一次的光照都要放在 Bass Pass 中
  • Base Pass 中渲染的平行光默认是支待阴影的,而 Additional Pass 中渲染的光源在默认情况下是没有阴影效果的(即便我们在它的 Light 组件中设置了有阴影的 Shadow Type),但我们可以在 Additional Pass 中使用 #pragma multi_compile_fwdadd_fullshadows 代替 #pragma multi_compile_fwdadd 编译指令,为点光源和聚光灯开启阴影效果,但这需要 Unity 在内部使用更多的 Shader 变种

回到最初,曾说过我们需要为每个 Pass 指定它使用的渲染路径,这样 Unity 才能知道如何给予我们正确的光源和处理后的光照信息,那么对于前向渲染 Unity 给了我们哪些“服务”呢?

前向渲染可以使用的内置光照变量和函数
名称 类型 描述
_LightColor0 float4 该 Pass 处理的逐像素光源的颜色
_WorldSpaceLightPos0 float4 _WorldSpaceLightPos0.xyz 是该 Pass 处理的逐像素光源的位置,_WorldSpaceLightPos0.w 表示当前光照是否是平行光(是0否1)

unity_WorldToLight

float4×4 从世界空间到光源空间的变换矩阵,可以用于采样聚光灯影和光强衰减(attenuation)纹理

_LightTexture0

sampler2D 点光源光照衰减纹理,用于获取光照的衰减值,对于聚光灯,其存储的不再是基于距离的衰减纹理,而是一张基于张角范围的衰减纹理
_LightTextureB0 sampler2D 聚光灯光照衰减纹理,用于获取光照的衰减值
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0 float4 仅用于 Base Pass。 前4个非重要的点光源在世界空间中的位置
unity_4LightAtten0 float4 仅用于 Base Pass。前4个非重要的点光源的衰减
unity_LightColor[] half4[4]

仅用于 Base Pass。前4个非重要的点光源的颜色

float3 WorldSpaceLightDir(float4 v)   输入模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。内部实现使用了 UnityWorldSpaceLightDir 函数,没有被归一化
float3 UnityWorldSpaceLightDir(float4 v)   输入世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向,没有被归一化
float3 ObjSpaceLightDir(float4 v)   输入模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向,没有被归一化
float3 Shade4PointLights(...)   计算4个点光源的光照,它的参数是已经打包进矢量的光照数据

五、关于光源处理方式、BassPass 和 AddPass 调用的验证

下面是一个拥有5个点光源,1个平行光的场景,场景中间有一个立方体,它的着色器算上了点光源:

着色器代码:

// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'Shader "Jaihk662/PointLight1"
{Properties{_DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)_SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)_MainTex ("MainTex", 2D) = "white" {}_Gloss ("Gloss", Range(8.0, 256)) = 20}SubShader{LOD 200//Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}//如果场景中包含了多个平行光,Unity会选择最亮的那个传递给BasePass进行逐像素处理,其它平行光按照逐顶点或在AddPass中按逐像素处理(没有平行光默认全黑)PASS {Tags { "LightMode" = "ForwardBase" }CGPROGRAM#pragma vertex vert#pragma multi_compile_fwdbase#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"fixed4 _DiffuseColor;fixed4 _SpecularColor;sampler2D _MainTex;float4 _MainTex_ST;float _Gloss;struct _2vert{float4 vertex: POSITION;float3 normal: NORMAL;float4 texcoord: TEXCOORD0;};struct vert2frag {float4 pos: SV_POSITION;float3 wPos: TEXCOORD0;float3 wNormal: TEXCOORD1;float2 uv: TEXCOORD2;};vert2frag vert(_2vert v) {vert2frag o;o.pos = UnityObjectToClipPos(v.vertex);o.wNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;}fixed4 frag(vert2frag i): SV_Target {fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos));fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));fixed3 albedo = tex2D(_MainTex, i.uv) * _DiffuseColor.rgb;fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(i.wNormal, wLightDir));fixed3 reflectDir = normalize(reflect(-wLightDir, i.wNormal));fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);return fixed4(ambient + diffuse + specular, 1.0); } ENDCG}PASS{Tags { "LightMode" = "ForwardAdd" }Blend One OneCGPROGRAM#pragma vertex vert#pragma multi_compile_fwdadd#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"      //别忘了包含fixed4 _DiffuseColor;fixed4 _SpecularColor;sampler2D _MainTex;float4 _MainTex_ST;float _Gloss;struct _2vert{float4 vertex: POSITION;float3 normal: NORMAL;float4 texcoord: TEXCOORD0;};struct vert2frag {float4 pos: SV_POSITION;float3 wPos: TEXCOORD0;float3 wNormal: TEXCOORD1;float2 uv: TEXCOORD2;};vert2frag vert(_2vert v) {vert2frag o;o.pos = UnityObjectToClipPos(v.vertex);o.wNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;}fixed4 frag(vert2frag i): SV_Target {fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos));#ifdef USING_DIRECTIONAL_LIGHTfixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));#elsefixed3 wLightDir = normalize(_WorldSpaceLightPos0.xyz - i.wPos.xyz);#endiffixed3 albedo = tex2D(_MainTex, i.uv) * _DiffuseColor.rgb;fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(i.wNormal, wLightDir));fixed3 reflectDir = normalize(reflect(-wLightDir, i.wNormal));fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);#ifdef USING_DIRECTIONAL_LIGHTfixed atten = 1.0;#elsefloat3 lightCoord = mul(unity_WorldToLight, float4(i.wPos, 1)).xyz;fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;#endifreturn fixed4((diffuse + specular) * atten, 1.0); } ENDCG}}FallBack "Specular"
}

由于仅在 AddPass 的片段着色器中计算的点光,并且没有 SH 和逐顶点的相关计算,因此这些光源都应是逐像素处理的,否则将不会表现于物体上

图片中的例子5个点光源都做了逐像素处理,这是因为它们的 RenderMode 都设置为了 Important:

也可以通过 FrameDebugger 工具查看当前的绘制过程:

如果这时将所有光源的 RenderMode 都设置为 Auto,那么就只能看到3个光源了:

而3正是上面 ProjectSettings → Quality 中最大逐像素光源数量,具体哪3个光源会被 Unity 当作 Important 逐像素处理,取决于光照的属性(位置、方向、颜色x强度、衰减)

当然了,手动设置光源 RenderMode 为 Not Important,或者物体脱离了点光源的有效照射范围,Unity 也不会为这个物体调用 Pass 来处理这个光源

UnityShader15:前向渲染相关推荐

  1. 图片渲染延迟_前向渲染与延迟渲染

    如果您开发过3D游戏,那么您可能会在现代图形引擎的研究中遇到术语"前向渲染"和"延迟渲染". 而且,通常,您必须选择一种在游戏中使用.但是它们是什么,它们有什么 ...

  2. 前向渲染路径细节 Forward Rendering Path Details

    正向渲染路径细节 Forward Rendering Path Details Forward Rendering path renders each object in one or more pa ...

  3. isomorphic-style-loader在前后端渲染样式同构中的应用与源码分析

    前言 在笔者的上一篇文章(基于react的前后端渲染实例讲解)中,对react下前后端渲染的流程进行了一个介绍.对前后端渲染的相关概念和原理不太了解的可以移步那篇文章.同时留下了一个引子:如何优雅地实 ...

  4. 前向渲染(Forward Rendering)和延迟渲染(Deferred Rendering)

    前向渲染(Forward Rendering) 它的实现最贴合我们的思维逻辑,当我们渲染模型时,只需要关心画模型然后直接处理光照,让它自己去做深度测试,最后深度测试过的都显示在屏幕上. 1.对要渲染的 ...

  5. 【技术美术图形部分】关于前向渲染和延迟渲染

    学习参考 [技术美术百人计划]图形 3.4 延迟渲染管线介绍 <Unity Shader 入门精要> 1 Unity的渲染路径 关于渲染路径,我在图形渲染管线1.0中就提过了,但只是初步的 ...

  6. mouseenter 延迟_前向渲染与延迟渲染

    如果您开发过3D游戏,那么您可能会在现代图形引擎的研究中遇到术语"前向渲染"和"延迟渲染". 而且,通常,您必须选择一种在游戏中使用.但是它们是什么,它们有什么 ...

  7. 前向渲染和延迟渲染基本概念

    前向渲染和延迟渲染基本概念 前言 这一篇是对之前欠的东西,延迟渲染这个东西具体有很多很多值得钻研的东西,这里只是对概念做一个简单的笔记.如果后续再需要深入了解的话,会再另作笔记. 前向渲染 前向渲染是 ...

  8. 后端渲染 java_前后端渲染 - 空一座旧城,守一个旧人 - 博客园

    前后端渲染之争 1.引言 十年前,几乎所有网站都使用 ASP.Java.PHP 这类做后端渲染,但后来随着 jQuery.Angular.React.Vue 等 JS 框架的崛起,开始转向了前端渲染. ...

  9. 【Unity渲染】前向渲染和延迟渲染的区别及切换

    前向渲染和延迟渲染通道的区别,主要在对于光源的处理上. Unity默认是前向渲染通道,如果光源特别多,可以使用延迟渲染. 前向渲染 使用前向渲染路径时,被照亮的对象将在单独的通道中进行渲染.根据场景中 ...

最新文章

  1. 第一次胜过MobileNet的二值神经网络,-1与+1的三年艰苦跋涉
  2. Zencart的首页php 301,Zencart 做了301重定向后不能登陆网站后台的解决方案
  3. C++版二叉树非递归遍历
  4. 【转载】网络流和最小费用流
  5. 35数据结构与算法分析之---最短路径
  6. 40岁老码农面试京东被淘汰,HR感叹:40岁混不到高管,都会被清退
  7. python怎么重复程序_利用Python程序完成ABAQUS中的一些重复性操作
  8. 运放 - 输出阻抗(Open loop output resistance)Ro
  9. 并行是什么意思?与并发的区别是什么?
  10. 路由器与交换机知识总
  11. 机器学习:特征选择之RFormula(SparkMLlib中的RFormula)
  12. 专利与论文-1:为什么要写专利?专利有什么好处?
  13. 炼数成金--支持向量机 笔记
  14. xbox one驱动下载_如何从手机将游戏下载到Xbox One
  15. 常见鸟的种类及特点_鸟的分类
  16. TVS二极管和稳压二极管应用有什么不同点
  17. oracle在线重定义(一)
  18. MyBatis联合主键结果集与SQL查询结果不一致的问题
  19. Spark使用RDD实现分组topN(八种方法)
  20. Unity学习——音效系统+音频过滤器+音频混响区+音频管理器

热门文章

  1. vb还是python强大-VB已死,Python当立 | 忆云竹
  2. micropython和python区别-MicroPython与Python速度对比
  3. python编程入门教学-史上最全Python编程基础入门教程
  4. python3.6安装步骤-手动安装python3.6的操作过程详解
  5. 语音识别开放平台调研以及主要技术
  6. npm 报错cb.apply is not a function
  7. 服务器资源评估文档,服务器存储资源评估
  8. sqlplus必须要安装oracle吗,不安装oracle客户端使用sqlplus
  9. cad坐标提取插件_如何快速地将CAD里的坐标提取到Excel中?最简单的方法你知道吗?...
  10. Bootstrap Table API 中文版 说明文档