UnityShader学习笔记——多种光源

news/2025/2/9 5:04:56 标签: 学习, 笔记

——内容源自唐老狮的shader课程

目录

1.光源类型

2.判断光源类型

2.1.在哪判断

2.2.如何判断

3.光照衰减

3.1.基本概念

3.2.unity中的光照衰减

 3.3.光源空间变换矩阵

 4.点光源衰减计算

5.聚光灯衰减计算

5.1.聚光灯的cookie(灯光遮罩)

5.2.聚光灯衰减计算

6.应用

7.如有疏漏,还请指出


1.光源类型

        平行光:没有位置,没有衰减,只有方向,颜色,强度参与计算

        点光源:五个属性都需要考虑

        聚光灯:五个属性都需要考虑,并且因为他的范围(由Range和Spot Angle共同决定)特殊,所以需要进行更复杂的运算

        面光源:在烘培时使用


2.判断光源类型

2.1.在哪判断

        我们一般在Additional Pass中判断光源类型以分别处理各部分的逻辑,因为不同光源的处理不一样

2.2.如何判断

unity提供了三个宏:

  • _DIRECTIONAL_LIGHT(平行光)
  • _POINT_LIGHT(点光源)
  • _SPOT_LIGHT(聚光灯)
//在CG中使用条件语句来分别处理

#if defined(_DIRECTIONAL_LIGHT)
平行光逻辑
#elif defined(_POINT_LIGHT)
点光源逻辑
#elif defined(_SPOT_LIGHT)
聚光灯逻辑
#else
其他逻辑
#endif

        unity底层会根据该条件编译指令,生成多个 Shader Variants(着色器变体),这些变体共享相同的核心代码,但会根据条件选择执行不同的代码块。

        Shader Variants是在编写Shader时根据不同的配置(如条件编译指令下不同的条件)生成的多个版本的Shader


3.光照衰减

3.1.基本概念

        通常是指渲染过程中考虑光线在传播过程中的减弱效应。

        一般常见的光照衰减计算方式有:线性衰减和平方衰减,后者更符合现实

3.2.unity中的光照衰减

        为了提高性能,我们一般不会直接通过数学公式计算衰减,而是使用一张纹理作为查找表,在片元着色器中计算逐像素光照的衰减。

        为此,unity准备了一个内置的纹理类型的变量 _LightTexture0,unity会在内部计算好相关数据并存储到这个变量中。其上的纹理颜色值,表明了光源空间中不同位置的点对应的衰减值。

        起点(0,0)位置,表明和光源重合的点的衰减值

        终点(1,1)位置,表明光源空间中离光源最远的点的衰减值

        

        一般我们会直接从_LightTexture0纹理中进行纹理采样,利用其中的UNITY_ATTEN_CHANNE宏来获得衰减值所在的分量,即:

tex2D(_LightTexture0,,对应纹理坐标).UNITY_ATTEN_CHANNEL

         要注意的是:如果光源存在cookie(灯光遮罩),那么衰减查找纹理就变成_LightTextureB0

 3.3.光源空间变换矩阵

        由于我们要从光照纹理中取得对应的衰减数据,因此我们需要将顶点坐标从世界空间变换到光源空间,然后再去获取衰减数据,使用如下公式即可:

mul(unity_WorldToLight,世界空间下顶点坐标)


 4.点光源衰减计算

        1.将顶点从世界空间下转换到光源空间,并且只取xyz分量,也就是说lightCoord是一个float3的变量。需要了解的是,unity_WorldToLight计算所得的是一个规范在0~1之间的值,这个值可视为与光源的距离

        2.利用该光源空间的坐标来计算离光源的距离,并利用距离参数,从衰减纹理中采样

fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL

            纹理坐标使用distance ^ 2是为了符合现实。ligthCoord是光源空间下顶点位置


5.聚光灯衰减计算

5.1.聚光灯的cookie(灯光遮罩)

        unity会默认为聚光灯弄个cookie,这玩意主要是用来模拟聚光灯的区域性,就像打手电一样

        此时,_LightTexture0 存储的变为cookie纹理信息,_LightTextureB0 存储的才是光照纹理信息,也就是说衰减值要从这里面取

5.2.聚光灯衰减计算

        1.将顶点从世界空间转换到光源空间,并且这里的lightCoord是一个float4变量,也就是取了w

        2.利用光源空间下的坐标信息与一些数据获取聚光灯的衰减信息,公式为(好长):

fixed atten = (lightCoord.z > 0) *

tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w *

tex2D(_LightTextureB0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL

        第一行:判断物体是不是在聚光灯的背面,如果在背面返回值是0,即不受聚光灯影响

        第二行:后半部分就是把xy变到01区间内,xy/w能变到-0.5~0.5之间,加个0.5就正好,然后对遮罩纹理采样获取遮罩衰减值(就是那个w)。为什么要这么变换可以画个图根据相似三角形推导一下

        第三行:正常取出基于距离的光照衰减值,需要了解的是,dot不会计算w


6.应用

        1.为BasePass添加编译指令 #pragma multi_compile_fwdbase

           为AdditionalPass添加编译指令 #pragma multi_compile_fwdadd

           这些编译指令会帮我们编译对应Pass中所有变体,并且附加通道的编译指令还可以确保在附加渲染通道中能访问到正确的光照变量

        2.在附加通道中加入混合命令 Blend One One,即线性简单效果

        3.根据条件判断语句,基于不同的光照类型计算光的方向和衰减值。方向是因为点光源和聚光灯需要用光源位置减去顶点位置,平行光不用

        4.计算最终颜色,附加通道中的最后颜色不需要再加环境光,只需要将满反射颜色和高光反射颜色相加然后乘以衰减值即可

 //这是附加通道

       Pass
        {
            Tags { "LightMode" = "ForwardAdd" }

            Blend One One

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdadd
            
            #include "Lighting.cginc"
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            fixed4 _DiffuseColor;
            fixed4 _SpecularColor;
            float _Gloss;

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 wNormal : NORMAL;
                float3 wPos : TEXCOORD0;
            };

            v2f vert(appdata_full v)
            {
                v2f data;
                data.pos = UnityObjectToClipPos(v.vertex);
                data.wNormal = UnityObjectToWorldNormal(v.normal);

                float3 wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                data.wPos = wPos;

                return data;
            }
            
            fixed4 frag(v2f f) : SV_TARGET
            {
                float3 wNormal = normalize(f.wNormal);
                float3 wLightDir = float3(0, 0, 0);

                #if defined(_DIRECTIONAL_LIGHT)

                wLightDir = normalize(_WorldSpaceLightPos0);

                #else
                // 点光源和聚光灯的灯光方向需要用 位置 减去 顶点位置
                wLightDir = normalize(_WorldSpaceLightPos0.xyz - f.wPos);

                #endif

                float3 wViewDir = normalize(_WorldSpaceCameraPos - f.wPos).xyz;

                float3 halfAngle = normalize(wLightDir + wViewDir);

                fixed3 lambertColor = _LightColor0.rgb * _DiffuseColor.rgb * max(0, dot(wNormal, wLightDir));
                fixed3 blinn_PhongColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfAngle)), _Gloss);

                //这个条件判断不止一种写法,还有别的写法
                #if defined(_DIRECTIONAL_LIGHT)
                    float atten = 1;
                #else
                    #if defined(_POINT_LIGHT)
                        //注意由世界坐标向空间左边转换时,将wPos作为四维向量转换并只取xyz(因为w没用)
                        float3 lightCoord = mul(unity_WorldToLight, float4(f.wPos, 1)).xyz;
                        float atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL;
                    #elif defined(_SPOT_LIGHT)
                        //这里w就有用了
                        float4 lightCoord = mul(unity_WorldToLight, float4(f.wPos, 1));
                        //1.判断能否照到;  2.映射到最大平面;  3.距离的平方采样;  
                        float atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
                    #else
                        float atten = 1;
                    #endif
                #endif
                //附加渲染通道中不需要加 环境光颜色,原因是再基础通道中就已经计算了
                //衰减值 乘以 兰伯特光照和布林芳高光的颜色之和
                fixed3 finalColor = (lambertColor + blinn_PhongColor) * atten;

                return fixed4(finalColor, 1);
            }

            ENDCG

        }
多种光源

7.如有疏漏,还请指出


http://www.niftyadmin.cn/n/5845519.html

相关文章

MySql数据库SQL编写规范注意事项

MySQL数据库SQL编写规范对于提高代码可读性、增强代码维护性、优化查询性能、减少错误发生、促进标准化和团队协作以及提升开发效率等方面都具有重要意义。因此,在开发过程中应严格遵守SQL编写规范,以确保代码的质量和效率。 以下是 MySQL 数据库 SQL 编…

golang命令大全12--命令速查表

至此,本系列博文已将golang的各种应用场景的命令都介绍了一遍,通过熟练使用这些命令,开发者可以更高效地开发、测试和维护Go项目,同时也能够更好地理解和学习Go语言的特性和最佳实践。因此,掌握Go命令行工具是成为一名…

第十节-Sourcetree图形化使用git

一、Sourcetree和Git介绍 1.1 Git 简介 Git 是一个分布式版本控制系统(Version Control System, VCS),由 Linus Torvalds 于 2005 年创建,最初是为了管理 Linux 内核开发而设计的。Git 的主要功能是跟踪文件的变更,帮…

微服务 day01 注册与发现 Nacos OpenFeign

目录 1.认识微服务: 单体架构: 微服务架构: 2.服务注册和发现 1.注册中心: 2.服务注册: 3.服务发现: 发现并调用服务: 方法1: 方法2: 方法3:OpenFeign OpenFeig…

快速优雅解决webview_flutter不能Safari调试的问题

这个问题,网上一搜,又是让你去检索WKWebView,找到FWFWebViewHostApi.m文件,然后再改 iOS 的代码, 加一行 self.inspectable YES; 我们开发Flutter项目,尽量还是不要去改插件里的代码,好了不费…

工厂模式+枚举类的json序列化+redisson的使用

目录 这里分享以下工厂模式反射IoC容器多态的妙用 场景引入 环境准备 代码实现 1.设置枚举类来规定有哪些学习方式 2.设置作业的实体对象 3.获取学习方式的接口 4.进行学习的动作,有出题和搜题 5.使用小猿搜题这种学习方式进行的两种学习动作学习&#xff…

音频进阶学习十二——Z变换一(Z变换、收敛域、性质与定理)

文章目录 前言一、Z变换1.Z变换的作用2.Z变换公式3.Z的状态表示1&#xff09; r 1 r1 r12&#xff09; 0 < r < 1 0<r<1 0<r<13&#xff09; r > 1 r>1 r>1 4.关于Z的解释 二、收敛域1.收敛域的定义2.收敛域的表示方式3.ROC的分析1&#xff09;当 …

Tengine配置负载均衡加健康检查

Tengine是淘宝开发的nginx&#xff0c;默认就自带健康检查模块&#xff0c;不过需要手动指定编译安装 https://blog.51cto.com/tchuairen/2287789 1、下载Tengine 官网及下载地址&#xff1a;https://tengine.taobao.org/ 2、解压并安装 # yum install pcre pcre-devel open…