Unity中Shader的光照衰减

news2024/11/17 22:36:21

文章目录

  • 前言
  • 一、衰减原理
    • 1、使用一张黑白渐变贴图用于纹理采样
    • 2、把模型从世界坐标转化为灯光坐标(即以灯光为原点的坐标系)
    • 3、用转化后的模型坐标,对黑白渐变纹理进行纹理采样
    • 4、最后,把采样后的结果与光照模型公式的结果相乘输出
  • 二、光照衰减实现
    • 1、Unity内部已经给我们提供了一张非线性黑白渐变的UV贴图
    • 2、把模型从世界坐标转化到灯光坐标下(使用矩阵相乘实现转化的效果)
    • 3、使用Unity自带的光照衰减贴图进行纹理采样
    • 4、最终效果
    • 测试代码
  • 三、使用Unity自带的方法,实现光源的衰减效果
    • 最终代码


前言

Unity中Shader的光照衰减


一、衰减原理

1、使用一张黑白渐变贴图用于纹理采样

2、把模型从世界坐标转化为灯光坐标(即以灯光为原点的坐标系)

3、用转化后的模型坐标,对黑白渐变纹理进行纹理采样

4、最后,把采样后的结果与光照模型公式的结果相乘输出

在这里插入图片描述


二、光照衰减实现

1、Unity内部已经给我们提供了一张非线性黑白渐变的UV贴图

这张UV贴图名字是固定的:_LightTexture0
注意:需要引入库 AutoLight.cginc

使用模型的uv进行采样,看看这张图大概的样子
fixed atten = tex2D(_LightTexture0,i.uv);
return atten;

把这个Shader的材质球给一个面片就可以看见这张渐变图
在这里插入图片描述

但是,这个测试效果并不是我们需要的
在这里插入图片描述

2、把模型从世界坐标转化到灯光坐标下(使用矩阵相乘实现转化的效果)

1.在 v2f 中定义一个 float3 类型的 TEXCOORD,来存放顶点坐标转化到世界坐标之后坐标信息

float3 worldPos : TEXCOORD2;

2.在顶点着色器中,把模型顶点的本地坐标转化为世界坐标(使用了unity_ObjectToWorld矩阵)

o.worldPos = mul(unity_ObjectToWorld,v.vertex);

3.把模型顶点从世界坐标转化为灯光坐标(使用了unity_WorldToLight矩阵)

//因为转化时使用的是4行的矩阵,所以 要把模型的顶点坐标增加一个w = 1,使坐标转化准确
float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;

在这里,我们输出一下lightCoord的灰度图看一下效果

return lightCoord.x;

请添加图片描述

3、使用Unity自带的光照衰减贴图进行纹理采样

fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord));

注意:这里的纹理采样不能直接使用lightCoord
可以这样理解,我们需要的效果是灯光靠近模型之后
越近,采样越靠uv的左边,灯光越亮(白色)
那么我们就可以使用lightCoord的点积来给光照衰减uv进行纹理采样
不使用模长的原因:向量点积计算量比计算模长计算量小

可以由下图理解,当 a 模长越小,dot(a,a)越小
则在纹理采样时,越靠近纹理的左边(白色)
请添加图片描述

5.最后,使用纹理采样后的结果和光的颜色相乘来模拟光照衰减

fixed4 LightColor = _LightColor0 * atten;

4、最终效果

测试代码

Shader "MyShader/P1_5_5"
{
    Properties
    {
        //光照系数
        _DiffuseIntensity("Diffuse Intensity",float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //在应用程序阶段传入到顶点着色器中,时加入顶点法向量信息
                half3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义一个3维向量,用于接受世界坐标顶点法向量信息
                half3 worldNormal:TEXCOORD1;
                
            };

            half _DiffuseIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                //把顶点法线本地坐标转化为世界坐标
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //Lambert光照模型的结果
                //Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L))
                //使用 Unity 封装的参数 获取环境光色
                float Ambient = unity_AmbientSky;

                //在属性面板定义一个 可调节的参数 用来作为光照系数,调节效果的强弱
                half Kd = _DiffuseIntensity;

                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;

                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);

                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;

                //使用Lambert公式计算出光照
                //fixed4 Diffuse = Ambient + (Kd * LightColor * dot(N,L));
                //因为 当 顶点法线 与 反射点指向光源的向量 垂直 或成钝角时,光照效果就该忽略不计
                //所以,这里使用 max(a,b)函数来限制 点积的结果范围
                fixed4 Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L));
                
                return Diffuse;
            }
            ENDCG
        }
        Pass
        {
            Tags{"LightMode"="ForwardAdd"}
            Blend One One
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            //加入Unity自带的宏,用于区分不同的光照
            //只声明我们需要的变体
            //#pragma multi_compile POINT SPOT
            
            #pragma multi_compile_fwdadd
            //剔除我们不需要的变体
            #pragma skip_variants DIRECTIONAL POINT_COOKIE DIRECTIONAL_COOKIE
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            //使用光照衰减贴图,需要引入 AutoLight.cginc 库
            #include "AutoLight.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //在应用程序阶段传入到顶点着色器中,时加入顶点法向量信息
                half3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义一个3维向量,用于接受世界坐标顶点法向量信息
                half3 worldNormal:TEXCOORD1;
                //定义一个三维向量,用于存放模型顶点 从本地坐标 转化为 世界坐标
                float3 worldPos : TEXCOORD2;
            };

            half _DiffuseIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                //把顶点法线本地坐标转化为世界坐标
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                //把模型顶点从本地坐标转化为世界坐标
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                /*#if POINT
                return fixed4(0,1,0,1);
                #elif SPOT
                return 0;
                #endif*/

                //把模型顶点从世界坐标转化为灯光坐标
                //unity_WorldToLight
                //从世界空间转换到灯光空间下,等同于旧版的_LightMatrix0
                //因为转化时使用的是4行的矩阵,所以 要把模型的顶点坐标增加一个w = 1,使坐标转化准确
                float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                //return lightCoord.x;
                //使用Unity自带的光照衰减贴图进行纹理采样
                fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord));
                
                
                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0 * atten;
                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);
                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;
                //因为计算点光源时不需要考虑环境光,所以在Lambert光照模型中删除环境光的影响
                fixed4 Diffuse = LightColor * max(0,dot(N,L));
                
                return Diffuse;
                
            }
            ENDCG
        }
    }
}

效果:
请添加图片描述


三、使用Unity自带的方法,实现光源的衰减效果

自己写光照衰减 和 使用Unity自带的方法 实现光照衰减需要根据情况而定

在这里插入图片描述
destName:out用于存放衰减值得变量
input:用于控制阴影的变量(目前用不上,传入0)
worldPos:模型的世界坐标

UNITY_LIGHT_ATTENUATION(atten,0,i.worldPos)

最终代码

Shader "MyShader/P1_5_5"
{
    Properties
    {
        //光照系数
        _DiffuseIntensity("Diffuse Intensity",float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //在应用程序阶段传入到顶点着色器中,时加入顶点法向量信息
                half3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义一个3维向量,用于接受世界坐标顶点法向量信息
                half3 worldNormal:TEXCOORD1;
                
            };

            half _DiffuseIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                //把顶点法线本地坐标转化为世界坐标
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //Lambert光照模型的结果
                //Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L))
                //使用 Unity 封装的参数 获取环境光色
                float Ambient = unity_AmbientSky;

                //在属性面板定义一个 可调节的参数 用来作为光照系数,调节效果的强弱
                half Kd = _DiffuseIntensity;

                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;

                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);

                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;

                //使用Lambert公式计算出光照
                //fixed4 Diffuse = Ambient + (Kd * LightColor * dot(N,L));
                //因为 当 顶点法线 与 反射点指向光源的向量 垂直 或成钝角时,光照效果就该忽略不计
                //所以,这里使用 max(a,b)函数来限制 点积的结果范围
                fixed4 Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L));
                
                return Diffuse;
            }
            ENDCG
        }
        Pass
        {
            Tags{"LightMode"="ForwardAdd"}
            Blend One One
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            //加入Unity自带的宏,用于区分不同的光照
            //只声明我们需要的变体
            //#pragma multi_compile POINT SPOT
            
            #pragma multi_compile_fwdadd
            //剔除我们不需要的变体
            #pragma skip_variants DIRECTIONAL POINT_COOKIE DIRECTIONAL_COOKIE
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            //使用光照衰减贴图,需要引入 AutoLight.cginc 库
            #include "AutoLight.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //在应用程序阶段传入到顶点着色器中,时加入顶点法向量信息
                half3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义一个3维向量,用于接受世界坐标顶点法向量信息
                half3 worldNormal:TEXCOORD1;
                //定义一个三维向量,用于存放模型顶点 从本地坐标 转化为 世界坐标
                float3 worldPos : TEXCOORD2;
            };

            half _DiffuseIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                //把顶点法线本地坐标转化为世界坐标
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                //把模型顶点从本地坐标转化为世界坐标
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                /*#if POINT
                return fixed4(0,1,0,1);
                #elif SPOT
                return 0;
                #endif*/

                //把模型顶点从世界坐标转化为灯光坐标
                //unity_WorldToLight
                //从世界空间转换到灯光空间下,等同于旧版的_LightMatrix0
                //因为转化时使用的是4行的矩阵,所以 要把模型的顶点坐标增加一个w = 1,使坐标转化准确
                //float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                //return lightCoord.x;
                //使用Unity自带的光照衰减贴图进行纹理采样
                //fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord));
                

                //使用Unity自带的方法实现光照衰减
                UNITY_LIGHT_ATTENUATION(atten,0,i.worldPos)
                
                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0 * atten;
                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);
                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;
                //因为计算点光源时不需要考虑环境光,所以在Lambert光照模型中删除环境光的影响
                fixed4 Diffuse = LightColor * max(0,dot(N,L));
                
                return Diffuse;
                
            }
            ENDCG
        }
    }
}

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

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

相关文章

PBA.客户需求分析管理

1 需求的三个层次: Requirement/Wants/Pains 大部分人认为,产品满足不了客户需要,是因为客户告知的需求是错误的,这听起来有一些道理,却没有任何意义。不同角色对于需求的理解是不一样的。在客户的需求和厂家的需求之间必然有一定…

分布式文件服务器——Windows环境MinIO的三种部署模式

上节简单聊到MinIO:分布式文件存储服务——初识MinIO-CSDN博客,但没具化,本节开始展开在Windows环境下 MinIO的三种部署模式:单机单节点、单机纠删码、集群模式。 部署的几种模式简要概括 所谓单机单节点模式:即MinI…

什么是谐波?谐波的危害

一、什么是谐波? “谐波”一词起源于声学。有关谐波的数学分析在18世纪和19世纪已经奠定了良好的基础。傅里叶等人提出的谐波分析方法至今仍被广泛应用。电力系统的谐波问题早在20世纪20年代和30年代就引起了人们的注意。当时在德国,由于使用静止汞弧变流…

PCL源码分析:直通滤波

文章目录 一、简介二、源码分析三、小结参考资料一、简介 让我们从一个最简单的功能开始慢慢重新认识PCL~~,虽然这个功能很简单,但是已可以从中管中窥豹来更加深入了解PCL的内部结构。 二、源码分析 在真正看PCL的源代码之前,我们先简单的看一下直通滤波这个类的类关系: 这…

自适应前照灯系统控制器AFS

自适应前照灯控制系统(Adaptive Front-lighting System,简称AFS)是一种智能灯光调节系统。通过感知驾驶员操作、车辆行驶状态、路面变化以及天气环境等信息,AFS 自动控制前照灯实时进行上下、左右照明角度的调整,为驾驶…

新的“HTTP/2 Rapid Reset”0day攻击打破了DDoS记录

导语 最近,一种名为“HTTP/2 Rapid Reset”的DDoS(分布式拒绝服务)攻击技术成为了热门话题,该技术自8月份以来被积极利用作为零日漏洞,打破了以往的攻击记录。亚马逊网络服务(Amazon Web Services&#xff…

pytoch M2芯片测试

今天才发现我的新片是M2芯片,而不是M1芯片,有点尴尬 参考网址 https://www.oldcai.com/ai/pytorch-train-MNIST-with-gpu-on-mac/ 测试结果如下 M2_cpu.py # https://www.oldcai.com/ai/pytorch-train-MNIST-with-gpu-on-mac/ import torch from tor…

WebRTC 系列(四、多人通话,H5、Android、iOS)

WebRTC 系列(三、点对点通话,H5、Android、iOS) 上一篇博客中,我们已经实现了点对点通话,即一对一通话,这一次就接着实现多人通话。多人通话的实现方式呢也有好几种方案,这里我简单介绍两种方案…

Linux开启SSH

Linux开启SSH 1.虚拟机确定连通性 如果是虚拟机的话则需要进行确定和宿主主机之间能正常联通(不能联通还远程个啥) 获取到虚拟机的IP 参考文章:Linux获取本机IP地址使用宿主机ping一下虚拟机的IP查看是否联通 2.安装SSH服务端 安装工具来使得能够通过SSH进行连接 命令 sudo a…

【推荐系统】推荐系统(RS)与大模型(LLM)的结合

【推荐系统】推荐系统(RS)与大模型(LLM)的结合 文章目录 【推荐系统】推荐系统(RS)与大模型(LLM)的结合1. 主流的推荐方法2. 大模型(LLM)可能作用的地方 1. 主…

Spring源码解析——ApplicationContext容器refresh过程

正文 在之前的博文中我们一直以BeanFactory接口以及它的默认实现类XmlBeanFactory为例进行分析,但是Spring中还提供了另一个接口ApplicationContext,用于扩展BeanFactory中现有的功能。 ApplicationContext和BeanFactory两者都是用于加载Bean的&#x…

graphviz 绘制单链表

dot 代码 digraph LinkedList {rankdirLR; // 设置布局方向为从左到右(左侧到右侧)node [fontname"Arial", shaperecord, stylefilled, color"#ffffff", fillcolor"#0077be", fontsize12, width1.5, height0.5];edge [fo…

汉诺塔问题:递归

经典汉诺塔问题 汉诺塔问题是经典的可以用递归解决的问题。 汉诺塔(Hanoi)游戏规则如下:在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如下图)。游戏的目标:把A杆上的金盘全部移到C杆上&a…

系统架构师备考倒计时25天(每日知识点)

面向对象设计原则 单一职责原则:设计目的单一的类开放-封闭原则:对扩展开放,对修改封闭李氏(Liskov)替换原则:子类可以替换父类依赖倒置原则:要依赖于抽象,而不是具体实现;针对接口编程&#x…

【Redis】Redis持久化深度解析

原创不易,注重版权。转载请注明原作者和原文链接 文章目录 Redis持久化介绍RDB原理Fork函数与写时复制关于写时复制的思考 RDB相关配置 AOF原理AOF持久化配置AOF文件解读AOF文件修复AOF重写AOF缓冲区与AOF重写缓存区AOF缓冲区可以替代AOF重写缓冲区吗AOF相关配置写后…

机器学习之自训练协同训练

前言 监督学习往往需要大量的标注数据, 而标注数据的成本比较高 . 因此 , 利用大量的无标注数据来提高监督学习的效果有着十分重要的意义. 这种利用少量标注数据和大量无标注数据进行学习的方式称为 半监督学习 ( Semi…

Java 序列化和反序列化为什么要实现 Serializable 接口?

序列化和反序列化 序列化:把对象转换为字节序列的过程称为对象的序列化. 反序列化:把字节序列恢复为对象的过程称为对象的反序列化. 什么时候需要用到序列化和反序列化呢或者对象序列化的两种用途… : (1) 对象持久化:把对象的…

NSSCTF做题(8)

[SWPUCTF 2022 新生赛]js_sign 看到了js代码 有一个base64编码,解密 最后发现这是一个加密方式 去掉空格之后得到了flag NSSCTF{youfindflagbytapcode} [MoeCTF 2022]baby_file 提示说有一个秘密看看你能不能找到 输入?filesecret 出现报错 输入php伪协议读取i…

Simulink仿真之离散系统

最近,为了完成课程作业,需要用到Simulink验证数字控制器的合理性。题目如下所示。 其实这道题在胡寿松老师的《自动控制原理(第七版)》的364页有答案。 这里给出数字控制器的脉冲传递函数为 ​​​​​​​ ​​​​​​​…

【22】c++设计模式——>外观模式

外观模式定义 为复杂系统提供一个简化接口&#xff0c;它通过创建一个高层接口(外观)&#xff0c;将多个子系统的复杂操作封装起来&#xff0c;以便客户端更容易使用。 简单实现 #include<iostream>// 子系统类 class SubsystemA { public:void operationA() {std::co…