UnityShader学习笔记——多种光源

news2025/4/21 14:40:32

——内容源自唐老狮的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.coloradmin.cn/o/2294666.html

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

相关文章

电脑右下角小喇叭没反应怎么回事,快速解决方案

当电脑右下角的小喇叭(音量图标)没有反应时,可以尝试以下快速解决方案: 一、基础检查与操作 检查键盘音量键: 按下键盘上的音量增加或减少键,或尝试Fn音量键(部分笔记本需组合键)&a…

Mysql基于binlog主从同步配置

主配置: 修改配置文件:/etc/my.cnf 添加server-id1 重启MySQL服务:systemctl restart mysqld 创建用户并授权: mysql> create user rep192.168.79.% identified with mysql_native_password by 123456; Query OK, 0 rows aff…

Docker Desktop安装到其他盘

Docker Desktop 默认安装到c盘,占用空间太大了,想给安装到其他盘,网上找了半天的都不对 正确安装命令: start /w "" "Docker Desktop Installer.exe" install --installation-dirF:\docker命令执行成功&am…

NetCore Consul动态伸缩+Ocelot 网关 缓存 自定义缓存 + 限流、熔断、超时 等服务治理

网关 OcelotGeteway 网关 Ocelot配置文件 {//单地址多实例负载均衡Consul 实现动态伸缩"Routes": [{// 上游 》》 接受的请求//上游请求方法,可以设置特定的 HTTP 方法列表或设置空列表以允许其中任何方法"UpstreamHttpMethod": [ "Get", &quo…

ubuntu 本地部署deepseek r1 蒸馏模型

本文中的文件路径或网络代理需要根据自身环境自行删改 一、交互式chat页面 1.1 open-webui 交互窗口部署:基于docker安装,且支持联网搜索 Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台,旨在完全离线操作。它支持各种 LLM…

go语言中的反射

为什么会引入反射 有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射。 空接口可…

JUC学习笔记02

文章目录 JUC笔记2练习题:手写线程池代码解释:AdvancedThreadPool 类:WorkerThread 内部类:AdvancedThreadPoolExample 类: 线程池的思考CPU密集型IO密集型 练习题:手写自动重试机练习题:手写定…

【论文翻译】DeepSeek-V3论文翻译——DeepSeek-V3 Technical Report——第一部分:引言与模型架构

论文原文链接:DeepSeek-V3/DeepSeek_V3.pdf at main deepseek-ai/DeepSeek-V3 GitHub 特别声明,本文不做任何商业用途,仅作为个人学习相关论文的翻译记录。本文对原文内容直译,一切以论文原文内容为准,对原文作者表示…

C++(进阶五)--STL--用一颗红黑树封装map和set

目录 1.红黑树源码(简略版) 2.模板参数的控制 3.红黑树的结点 4.迭代器的实现 正向迭代器 反向迭代器 5.set的模拟实现 6.map的模拟实现 7.封装完成后的代码 RBTree.h mymap.h myset.h 1.红黑树源码(简略版) 下面我们…

飞算JavaAI :AI + 时代下的行业趋势引领者与推动者

在科技飞速发展的当下,AI 时代正以前所未有的速度重塑着各个行业的格局,而软件开发领域更是这场变革的前沿阵地。在众多创新力量之中,飞算JavaAI 脱颖而出,宛如一颗璀璨的新星,凭借其独树一帜的特性与强大功能&#x…

【重新认识C语言----结构体篇】

目录 -----------------------------------------begin------------------------------------- 引言 1. 结构体的基本概念 1.1 为什么需要结构体? 1.2 结构体的定义 2. 结构体变量的声明与初始化 2.1 声明结构体变量 2.2 初始化结构体变量 3. 结构体成员的访…

一种解决SoC总线功能验证完备性的技术

1. 前言 通过总线将各个IP通过总线连接起来的SoC芯片是未来的大趋势,也是缩短芯片开发周期,抢先进入市场的常用方法。如何确保各个IP是否正确连接到总线上,而且各IP的地址空间分配是否正确,是一件很棘手的事情。本文提出了一种新…

【Linux系统】线程:线程库 / 线程栈 / 线程库源码阅读学习

一、线程库 1、线程库介绍:命名与设计 命名:线程库通常根据其实现目的和平台特性进行命名。例如,POSIX标准定义了Pthreads(POSIX Threads),这是一个广泛使用的线程库规范,适用于多种操作系统。此…

深度剖析 Redis:缓存穿透、击穿与雪崩问题及实战解决方案

一、缓存基本使用逻辑 在应用程序中,为了提高数据访问效率,常常会使用缓存。一般的缓存使用逻辑是:根据 key 去 Redis 查询是否有数据,如果命中就直接返回缓存中的数据;如果缓存不存在,则查询数据库&#…

如何使用el-table的多选框

对el-table再次封装,使得功能更加强大! 本人在使用el-table时,因为用到分页,导致上一页勾选的数据在再次返回时,没有选中,故在原有el-table组件的基础之上再次进行了封装。 1.首先让某些不需要勾选的列表进…

【工具变量】上市公司企业渐进式创新程度及渐进式创新锁定数据(1991-2023年)

测算方式: 参考顶刊《经济研究》孙雅慧(2024)老师的做法,用当期创新和往期创新的内容重叠度作为衡量渐进式创新程度的合理指标。通过搜集海量专利摘要,测算当前专利申请和既有专利的内容相似度,反映企业在…

LM Studio 部署本地大语言模型

一、下载安装 1.搜索:lm studio LM Studio - Discover, download, and run local LLMs 2.下载 3.安装 4.更改成中文 二、下载模型(软件内下载) 1.选择使用代理,否则无法下载 2.更改模型下载目录 默认下载位置 C:\Users\用户名\.lmstudio\models 3.搜…

嵌入式工程师面试经验分享与案例解析

嵌入式工程师岗位受到众多求职者的关注。面试流程严格,技术要求全面,涵盖C/C编程、数据结构与算法、操作系统、嵌入式系统开发、硬件驱动等多个方向。本文将结合真实案例,深入剖析嵌入式工程师的面试流程、常见问题及应对策略,帮助…

英特尔至强服务器CPU销量创14年新低,AMD取得进展

过去几年是英特尔56年历史上最艰难的时期之一。该公司在晶圆代工、消费级处理器和服务器芯片等各个领域都面临困境。随着英特尔重组其晶圆代工业务,新的分析显示其服务器业务的现状和未来前景不容乐观。 英特尔最近发布的10-K文件显示:“数据中心和人工…

判断您的Mac当前使用的是Zsh还是Bash:echo $SHELL、echo $0

要判断您的Mac当前使用的是Zsh还是Bash,可以使用以下方法: 查看默认Shell: 打开“终端”应用程序,然后输入以下命令: echo $SHELL这将显示当前默认使用的Shell。例如,如果输出是/bin/zsh,则说明您使用的是Z…