首页 > 其他分享 >Unity Shader中的Dot点乘解惑篇(转)

Unity Shader中的Dot点乘解惑篇(转)

时间:2024-03-28 19:56:48浏览次数:38  
标签:向量 Shader Snow Unity Dot 方向 夹角 dot 落雪

光照中的点乘应用

漫反色中的核心函数

fixed4 diff = albedo * _LightColor0 * max(0, dot(i.worldNormal, worldLight));

当 worldLight 为float3(0, 1, 0),时,主要分析该函数 dot(float3(0, 1, 0), i.normal);

点乘两个向量,我们知道向量归一化以后向量点乘的值是向量的夹角的Cos值(简单理解)

根据图片我们知道,如果两个向量的夹角为0,也就是向量方向相同,那么cos这个值趋近于1。

这里需要注意的一点就是光源的方向是指指向光源的方向向量,而不是光的方向向量。

我们知道指向光源的方向和法向量的夹角不会大于90度,如果出现大于90度说明光源在平面的背面,我们不需要负光的出现,所以需要“受钳制的光照”,这里有两个函数可以选择

一个是:max(0, dot(float3(0, 1, 0), i.normal)); 这个函数比较简单,取最大值,由于cos的最大值是1,所以产生的值是(0,1)

另一个是:saturate(dot(float3(0, 1, 0), i.normal));这个函数内置的饱和值函数,当然这个函数把结果限制在0和1之间

最后一个是高级函数, UnityStandardBRDF导入文件定义了方便的DotClaped函数(实际是根据显卡做了性能选择和封装,详情见本节链接)

另外说一个关于反色光的计算问题,也就是我们初中必背的一个公式,入射角=反色角,这里shaderlab提供了一个内置函数 reflect

float3 reflectionDir = reflect(-lightDir, i.normal);

这里需要注意的这个函数负的 –lightDir 因为这里的参数需要的是光源的方向,且两个参数需要归一化的,具体的延伸阅读见下图:

边缘提取中额点乘应用

本节效果1 轮廓增强

本节效果2 边缘自发光

效果1分析

获取边缘的核心函数如下,又看到了我们提到的向量点乘

floatnewOpacity = min(1.0, tex.a / abs(dot(viewDirection, normalDirection)));

returnfloat4(col, newOpacity);

这里我们先看下 viewDirection是怎么求得

output.viewDir = normalize(_WorldSpaceCameraPos- mul(modelMatrix, input.vertex).xyz);

根据向量的减法我们知道两个向量相减得到的向量的方向由减数指向被减数,如下图所示:

所以我们 这个viewDir就是世界坐标系中模型指向相机的,和上一节的光照方向是一样的。这样我们在分析下边缘提取的公式。

min(1.0, tex.a / abs(dot(viewDirection, normalDirection)));

1、dot(viewDirection, normalDirection),这个我想大家都明白就是求相机和法向量的余弦值,夹角越小值越趋近1,夹角越大值越趋近于0,也就是值大小与夹角成反比

2、abs(dot(viewDirection, normalDirection)),求绝对值,我们知道当夹角大于90度的时候,cos为负值,也就是摄像头从背面看物体(你以为是透视啊,实际shader有这个能力的)所以这里为什么用的abs而不是上节中的max(有待证明)

3、tex.a / abs(dot(viewDirection, normalDirection)),首先求反,也就是1/x,我们在1中知道dot的值与夹角成反比,再求反的话,负负为正了,该值变成与夹角成正比了,由于又做了绝对值操作,所以求反的值也在(0,1)之间但是趋近于1的时候,说明法线和视线垂直(正交),也就是边缘点。

4、tex.a*这个(0,1)的值,也就是说夹角越大(在边缘)透明度越高,在中心透明度越低,这就自然形成边缘高亮或者轮廓高亮的效果了

5、min(1.0, tex.a / abs(dot(viewDirection, normalDirection)));,由于颜色的值是在(0,1)之间的大于1也没有意义,所以可以用这个min函数限制最大值是1

效果2分析

核心公式

half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));

//赋值自发光颜色信息

o.Emission = _RimColor.rgb * pow (rim, _RimPower);

1、dot (normalize(IN.viewDir), o.Normal)和效果1一样,也就是视线与法线夹角成反比,而我们知道夹角正交(垂直)的地方是边缘,那么1.0 - saturate(dot (normalize(IN.viewDir), o.Normal)); 相当于再次求反,负负为正,那么就变成夹角越大值越大,也就是越是边缘越趋近1,自然实现了自发光效果了。

积雪效果的点乘综合应用--积雪实现

效果图

上代码

  1. void surf (Input IN, inout SurfaceOutput o) {

  2. //该像素的真实颜色值

  3. half4 c = tex2D(_MainTex, IN.uv_MainTex);

  4. //从凹凸贴图中得到该像素的法向量

  5. o.Normal = UnpackNormal (tex2D (_Bump, IN.uv_Bump));

  6. //得到世界坐标系下的真正法向量(而非凹凸贴图产生的法向量)和雪落

  7. //下相反方向的点乘结果,即两者余弦值,并和_Snow(积雪程度)比较

  8. if(dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz)>lerp(1,-1,_Snow))

  9. //此处我们可以看出_Snow参数只是一个插值项,当上述夹角余弦值大于

  10. //lerp(1,-1,_Snow)=1-2*_Snow时,即表示此处积雪覆盖,所以此值越大,

  11. //积雪程度程度越大。此时给覆盖积雪的区域填充雪的颜色

  12. o.Albedo = _SnowColor.rgb;

  13. else

  14. //否则使用物体原先颜色,表示未覆盖积雪

  15. o.Albedo = c.rgb;

  16. o.Alpha = 1;

  17. }

效果很美,代码注释很好,还记的本文的背景嘛,每个shader都有它的灵魂和核心公式,这篇翻译的也不错,但是美中不足,核心公式可读性差了些。我们来分析下,首先落雪的原理是什么,现实中能积雪的是什么地方呢?是一个平台要有面积才能承接上积雪,当然面的方向要与下雪的方向垂直,很少见到墙面上能积雪的一般都是屋顶是这样吧。结合前两节的关于小芝麻Dot的学习,我们知道通过Dot我们可以求得面法线与指定方向的夹角,从而求得面与方向的夹角和边缘位置,这里我们可以把落雪的方向当成光源的方向实际上和漫反色一个道理,“落雪方向的反方向和面法向量夹角越小,说明落雪方向与面垂直,积雪越多“,下面我们看下shader的核心公式,我们分解分析下

if(dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz)>lerp(1,-1,_Snow))

1、dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) 翻看_SnowDirection知道是传入变量也确实是落雪反方向(模型指向落雪方向),那么这个dot就是求得法线与该方向的cos值,角度越小值越大,也就是面越垂直值越小,下文中的”落雪方向“都指的是实际落雪的反方向

2、lerp(1,-1,_Snow)是什么1,-1的插值函数,恶补下公式

float lerp(float a, float b, float w) { return a + w*(b-a);

}

也就是注释给的1-2*_Snow

//此处我们可以看出_Snow参数只是一个插值项,当上述夹角余弦值大于

//lerp(1,-1,_Snow)=1-2*_Snow时,即表示此处积雪覆盖,所以此值越大,

//积雪程度程度越大。此时给覆盖积雪的区域填充雪的颜色

看到这个计算式子,其实是一个斜率为负值的直线方程,也就是该值与_Snow参数 成反比,值越大结果值越小(读到这里估计很多人一头雾水了)

3、dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz)>lerp(1,-1,_Snow)

按照注释大家理解下这个公式,感觉如何,我第一次度的时候觉得挺绕的,后来仔细想想,问题出在原作者的编码风格上,这里实际上代码规范做的不好,没有遵循单一原则。下面我来分解下

A、不等式左边,dot部分,法线与”落雪方向“夹角越小值越大,符合我们分析的原理,面与”落雪方向“垂直值越大,很好理解,这里与两个向量的夹角成反比,但是面与指定方向成正比,也就是面和落雪方向夹角越大,值越大,正是我们需要的;

B、不等式右边,lerp部分,是什么?不是什么插值计算,最后求得,是一个阈值是一个常量。

那不等式整体的意思是什么呢,当法线与”落雪方向“夹角小与一个值是 就会积雪,反过来好理解当面与落雪方向夹角大于一个值就落雪,好理解吧。

那B中的lerp部分中是什么呢,其实是另一件事情,它是求这个阈值的,当参数越大返回值越小,而阈值越小则允许落雪的角度差越大就是就会落更多的雪,其实也是一个求反,以符合人的惯性思维_Snow越大代表雪越多,恰好不等式右边越小雪量越大,是不是分开更好理解,一行代码只做一件事情。

4、不完美的地方

根据源码分析,我们发现dot没有做负值判断,也就是当发现视线和”落雪方向“大于90度时,会出现负值,实际上这种情况根本就是多余的,物体的内面根本不会出现积雪,至于插值 lerp(1,-1,_Snow)的下降速度采用1,0也应该是没有问题的。

积雪效果的点乘综合应用--积雪厚度变形

上代码

  1. void vert (inout appdata_full v) {

  2. //将_SnowDirection转化到模型的局部坐标系下

  3. float4 sn = mul(UNITY_MATRIX_IT_MV, _SnowDirection);

  4. if(dot(v.normal, sn.xyz) >= lerp(1,-1, (_Snow*2)/3))

  5. {

  6. v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;

  7. }

  8. }

这里我引下原文的原理说明,很简单了

首先我们传给vert函数一个参数appdata_full v,参数的类型为appdata_full(Unity内置类型),该类型包含了纹理坐标,法向量,顶点位置,以及切线信息。如果你还需要使用其他的数据类型,你可以使用自定义的输入结构体作为pixel函数的第二个参数传递额外的信息 — 目前我们不需要这样做。

_SnowDirection使用的是世界坐标系,但是我们需要的其实是模型局部坐标系下的_SnowDirection。所以我们需要先将_SnowDirection转化到模型的局部坐标系下。而我们只需要将_SnowDirection乘以Unity内置矩阵 – UNITY_MATRIX_IT_MV(IT表示Inverse Transpose逆转置矩阵,MV表示 ModelView矩阵,该矩阵表示是ModelView的逆转置矩阵)。

现在我们得到了该顶点的法向量(vert函数应该是对每个vertex调用一次,相对于surf函数对每个pixel调用一次)。我们仍然像上面做积雪效果时那样,将转换坐标系空间后的雪落下相反方向和模型局部坐标系下的法向量进行点乘,得到的结果仍然和一个插值比较。不过此时插值项不再是_Snow,而是_Snow*2/3,这表示只有那些更接近雪落下方向的区域才会增加雪的厚度,更符合自然现象。

而这些通过测试的区域,沿着(sn.xyz+v.normal)方向进行加厚,也就是将其顶点沿此方向伸展一定距离。注意到增厚的程度取决于_SnowDepth和_Snow,而增厚的方向是由物体法向和雪落的方向综合作用的,这也符合自然现象。

原理用一句话概括,就是通过dot与阈值判断,加大面顶点的高度,以获得加厚的效果,注意这里应用的向量的加法。

标签:向量,Shader,Snow,Unity,Dot,方向,夹角,dot,落雪
From: https://www.cnblogs.com/gamesky/p/18102488

相关文章

  • 【Unity】调整Player Settings的Resolution设置无效
    【背景】Build时修改了PlayerSettings下的Resolution设置,但是再次Building时仍然不生效。【分析】明显是沿用了之前的分辨率设定,所以盲猜解决办法是Build相关的缓存文件,或者修改打包名称。【解决】实测修改版本号无效,必须修改productName才会使Resolution设置生效。......
  • Unity网络通信系统设计.md
    Unity网络通信系统设计Buffer报文BufferEntity类作为报文基类的作用包括:封装数据:BufferEntity类可以用来封装网络通信中的数据,方便在网络传输中进行处理和管理。提供数据缓冲区:BufferEntity类通常会包含一个数据缓冲区,用来存储待发送或接收的数据,以便进行网络通信......
  • C# 异步与 Unity 协程(实例讲解)
    C#异步编程实例:假设我们有一个需要从Web获取数据的简单应用。我们可以使用C#的异步编程模型来避免UI线程被HTTP请求阻塞1usingSystem.Net.Http;2usingSystem.Threading.Tasks;34publicclassAsyncExample5{6publicasyncTask<string>FetchDataFromWebAsync(st......
  • 【Unity】TextMeshPro富文本
    启用富文本在Unity里,如果需要使用富文本,首先需要开启RichText如果不开启RichText,就会在UI上显示富文本代码1.粗体<b>Game</b>Over2.斜体<i>Game</i>Over3.下划线<u>Game</u>Over4.删除线<s>Game</s>Over5.指定颜色删除线<scolor=#ff8000>Game&......
  • Unity在旋转时出现万向节锁的解决方案
    关于万向节锁在Unity官方文档中有这样的描述:欧拉角在变换坐标中,Unity使用矢量属性Transform.eulerAngles X、Y和Z显示旋转。与法线矢量不同,这些值实际上表示绕X、Y和Z轴旋转的角度(以度为单位)。欧拉角旋转围绕三个轴执行三个单独的旋转。Unity依次围绕z轴、x轴和y......
  • Unity中如何实现草的LOD
    1)Unity中如何实现草的LOD2)用ComputeShader处理图像数据后在安卓机上不能正常显示渲染纹理3)关于进游戏程序集加载的问题4)预制件编辑模式一直在触发自动保存这是第379篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地掌握和......
  • 启动应用程序出现dot3hc.dll找不到问题解决
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个dot3hc.dll文件(挑选合适的版本文件)把它放......
  • unity+单例
    usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassModuleManage:MonoBehaviour{privatestaticModuleManage_instance;publicstaticModuleManageInstance{get{if(_ins......
  • 使用Github托管Unity项目
    ​准备工作在本机生成ssh密钥ssh-keygen-trsa-C"你的邮箱地址"点击回车后会出现生成的密钥路径,我们直接打开密钥复制下来。github官网添加我们的本机密钥进入Github官网,点击设置,选择SSHandGPGkeys点击newSSHkey,将我们刚才在本机生成的ssh密钥放入key中,并起......
  • Deepin-DotnetSdk安装
    Linux-DotnetSdk安装1.1密钥下载sudowget-Uvhhttps://packages.microsoft.com/config/debian/10/packages-microsoft-prod1.2执行密钥双击:packages-microsoft-prod.deb更新:sudoapt-getupdate1.3安装SDKsudoapt-getinstalldotnet-sdk-8.0sudoapt-getinstalldotn......