UnityShader(十六)凹凸映射

前言:

纹理的一种常见应用就是凹凸映射(bump mapping)。凹凸映射目的就是用一张纹理图来修改模型表面的法线,让模型看起来更加细节,这种方法不会改变模型原本的顶点位置(也就是不会修改模型的形状),只是让模型看起来凹凸不平而已。

有两种主要的方法可以用来进行凹凸映射:

1.用一张高度纹理(height map)来模拟表面位移,然后得到修改后的法线值,也被称为高度映射(height mapping)

2.用一张法线纹理(normal map)来直接存储表面法线,被称为法线映射(normal mapping)

高度纹理

高度纹理图中存储的是表面位移的强度值,用于表示模型表面局部的海拔高度。颜色越浅表示模型表面越向外凸起,颜色越深表明该位置越向里凹(白色为1,黑色为0,因此高度图看起来是一张黑白图)。

这种方法的优点是我们能够直观的知道模型表面的凹凸情况,缺点是计算会更加复杂。

法线纹理:

法线纹理存储的是表面法线的方向。由于法线的分量范围在[-1,1]之间,而像素的分量范围在[0,1]之间,因此需要做一个映射,即:

piexl=\frac{normal+1}{2}

这就要求我们直在Shader中采样法线纹理进行纹理采样后,还需要对结构进行一次反映射的过程,用来得到原先的法线方向。反映射的过程实际上就是上面映射函数的你函数,即:

normal=pixel*2-1 

但是,由于方向是相对于坐标空间来说的,不同空间坐标系的方向也不尽相同。对于模型顶点自带的法线,它们是定义在模型空间中的,也被称为模型空间的法线纹理(object-space normal map)。但在实际制作中,往往会采取模型顶点的切线空间(tangent space)存储法线。对于每一个模型顶点,它们都有一个属于自己的切线方向t,切线t和法线n的叉积得到的就是副切线方向(binormal,b)或副法线

这种纹理被称为切线空间的法线纹理(tangent-space normal map)

两者对比:

使用模型空间存储法线:

1.实现简单,更加直观,甚至不需要模型原始的法线和切线等信息,也就是说计算更少。但如果想要得到效果比较好的法线映射,由于模型的切线一般和UV坐标相同,因此需要纹理映射需要连续。

2.在纹理坐标的缝合处和尖锐的边角部分,可见的突变(缝隙)较少,即可以提供平滑的边界。这是因为模型空间下的法线纹理存储的是同一坐标系下的法线信息,因此在边界处通过插值得到的法线可以平滑变换,而切线空间下的法线纹理中的发现信息是依据纹理坐标的方向得到的结果,可能会在边缘处或者尖锐部分造成更多可见的缝合迹象。

使用切线空间存储法线:

1.自由度很高,模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它时的那个模型,而应用到其它模型上效果就完全错误。而切线空间下的法线纹理记录的是相对法线信息,这意味着即便把纹理应用到一个完全不同的网格上,也可以得到一个合理的效果。

2.可进行UV动画:比如我们可以移动一个纹理的UV坐标来实现一个凹凸移动的效果,但使用模型空间下的法线纹理则会得到完全错误的效果。

3.可以重用法线纹理。

4.可以压缩。

对比之下,切线空间下的法线在很多情况下都优于模型空间下的法线。

实践

我们需要计算光照模型中统一各个方向矢量所在的坐标空间。由于法线纹理中存储的法线是切线空间下的方向,因此我们可以有两种思考方向:

1.在切线空间下计算光照,需要把光照方向、视角方向都转换到切线空间下

2.在世界空间下计算光照,需要把采样到的法线转换到世界空间下,再和世界空间下的光照方向、视角方向计算

从效率上来说第一组往往优于第二种,因为我们可以在顶点着色器中完成对光线方向和视角方向的变换,而第二种需要对法线先进行采样,只能在片元着色器中计算。

但从通用性来讲,第二种优于第一种,因为有时候我们需要在世界空间下进行一些计算(比如在使用Cubemap进行环境映射时,我们需要把法线方向变换到世界空间下)

代码实操:

切线空间下:

Shader "Shader入门/凹凸映射/NormalMapTangentSpace"
{
  Properties
  {
    _MainTex("MainTex",2D)="white"
    _NormalTex("Normal",2D)="bump"
    _NormalScale("NormalScale",float)=1.0
    _Specular("Specular",float)=1.0

  }
  SubShader
  {
        Pass
        {
            Tags{"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _NormalTex;
            float4 _NormalTex_ST;
            float _NormalScale;
            float _Specular;

            struct vertexInput
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 texcoord : TEXCOORD;
            };
            
            struct vertexOutput
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 lightDir : TEXCOORD1;
                float3 viewDir : TEXCOORD2;
            };

            vertexOutput vert(vertexInput v)
            {
                vertexOutput o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                o.uv.zw = v.texcoord.xy*_NormalTex_ST.xy+_NormalTex_ST.zw;

                float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w;
                float3x3 TBN = float3x3(v.tangent.xyz,binormal,v.normal);
                //TANGENT_SPACE_ROTATION     内置宏,同样实现TBN,但结果变量为rotation

                o.lightDir = mul(TBN,ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(TBN,ObjSpaceViewDir(v.vertex)).xyz;

                return o;
            }

            fixed4 frag(vertexOutput i):SV_TARGET
            {
                half3 tangentLightDir = normalize(i.lightDir);
                half3 tangentViewDir = normalize(i.viewDir);

                fixed4 packedNormal = tex2D(_NormalTex,i.uv.zw);
                fixed3 tangentNormal = UnpackNormal(packedNormal);
                tangentNormal.xy *= _NormalScale;
                tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
                tangentNormal = normalize(tangentNormal);

                fixed3 albedo = tex2D(_MainTex,i.uv).rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
                fixed3 diffuse = _LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir));
                fixed3 halfDir = normalize(tangentViewDir+tangentNormal);
                fixed3 specular = _LightColor0.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Specular);

                fixed3 final_color = diffuse+ambient+specular;
                return fixed4(final_color,1.0);
            }
            
            ENDCG
        }
  }
}

效果:

世界空间下:

代码:

Shader "Shader入门/凹凸纹理/NormalMapWorldSpace"
{
  Properties
  {
    _MainTex("MainTex",2D)="white"
    _NormalTex("Normal",2D)="bump"
    _NormalScale("NormalScale",float)=1.0
    _Specular("Specular",float)=1.0

  }
  SubShader
  {
        Pass
        {
            Tags{"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _NormalTex;
            float4 _NormalTex_ST;
            float _NormalScale;
            float _Specular;

            struct vertexInput
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 texcoord : TEXCOORD;
            };
            
            struct vertexOutput
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float4 TtoW0 : TEXCOORD1;
                float4 TtoW1 : TEXCOORD2;
                float4 TtoW2 : TEXCOORD3;

            };

            vertexOutput vert(vertexInput v)
            {
                vertexOutput o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                o.uv.zw = v.texcoord.xy*_NormalTex_ST.xy+_NormalTex_ST.zw;

                float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                fixed3 worldTargent = UnityObjectToWorldDir(v.tangent);
                fixed3 worldBinormal = cross(worldNormal,worldTargent)*v.tangent.w;

                o.TtoW0 = float4(worldTargent.x,worldBinormal.x,worldNormal.x,worldPos.x);
                o.TtoW1 = float4(worldTargent.y,worldBinormal.y,worldNormal.y,worldPos.y);
                o.TtoW2 = float4(worldTargent.z,worldBinormal.z,worldNormal.z,worldPos.z);

                return o;
            }

            fixed4 frag(vertexOutput i):SV_TARGET
            {
                float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);

                half3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
                half3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                fixed4 packedNormal = tex2D(_NormalTex,i.uv.zw);
                fixed3 normal = UnpackNormal(packedNormal);
                normal.xy *= _NormalScale;
                normal.z = sqrt(1.0-saturate(dot(normal.xy,normal.xy)));
                normal = normalize(half3(dot(i.TtoW0.xyz,normal),dot(i.TtoW1.xyz,normal),dot(i.TtoW2.xyz,normal)));

                fixed3 albedo = tex2D(_MainTex,i.uv).rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
                fixed3 diffuse = _LightColor0.rgb*albedo*max(0,dot(normal,lightDir));
                fixed3 halfDir = normalize(lightDir+viewDir);
                fixed3 specular = _LightColor0.rgb*pow(max(0,dot(normal,halfDir)),_Specular);

                fixed3 final_color = diffuse+ambient+specular;
                return fixed4(final_color,1.0);
            }
            
            ENDCG
        }
  }
}

效果:

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《硬件历险》之Mac抢救出现问题的时间机器硬盘中的数据

本文虽然使用“抢救”一词,但是运气比较好,远没有达到访问和修改底层的信息来抢救的地步。如果你是需要通过访问和修改底层信息来抢救数据,建议阅读刘伟的《数据恢复技术深度揭秘(第二版)》或者寻找专业人士的帮助。 《…

卷积篇 | YOLOv8改进之C2f模块融合SCConv | 即插即用的空间和通道维度重构卷积

前言:Hello大家好,我是小哥谈。SCConv是一种用于减少特征冗余的卷积神经网络模块。相对于其他流行的SOTA方法,SCConv可以以更低的计算成本获得更高的准确率。它通过在空间和通道维度上进行重构,从而减少了特征图中的冗余信息。这种…

留学生课设|R语言|研究方法课设

目录 INSTRUCTIONS Question 1. Understanding Quantitative Research Question 2. Inputting data into Jamovi and creating variables (using the dataset) Question 3. Outliers Question 4. Tests for mean difference Question 5. Correlation Analysis INSTRUCTIO…

深度学习 精选笔记(13.1)卷积神经网络-LeNet模型

学习参考: 动手学深度学习2.0Deep-Learning-with-TensorFlow-bookpytorchlightning ①如有冒犯、请联系侵删。 ②已写完的笔记文章会不定时一直修订修改(删、改、增),以达到集多方教程的精华于一文的目的。 ③非常推荐上面(学习参考&#x…

SAR ADC教程系列5——FFT频谱泄露以及相干采样

频谱泄露的出现以及如何规避? 为什么要相干采样? 1.分析ADC输出信号的频谱工具:DFT(Discrete Fourier Transform) 重点:DFT相邻频谱频率间隔为fs/N 如何规避频谱泄露? 对于DFT,它对于接收到的信…

Qt文件读写

做一个简单的文件读写,我们把一个结构体内的数据写入到二进制文件中,并重新读取解析。代码结构如下: 项目名称随便起就好了。main.cpp是主函数;DataHandler实现文件的写与读,还要模拟过程;Definition.h放置…

机器学习-绪论

机器学习致力于研究如何通过计算的手段、利用经验来改善系统自身的性能。在计算机系统中,“经验”通常以“数据”的形式存在,因此,机器学习所研究的主要内容,是关于在计算机上从数据中产生“模型”的算法,即“学习算法…

NPM 仓库的超集 JSR 来了!

引言 今天在 Deno 博客中看到了一篇文章,介绍了一个叫 JSR 的包管理注册中心,简单尝试了一下觉得还不错,本文将结合原文章和个人体验对 JSR 进行一个详细的介绍。 在现如今的前端开发中,包管理注册中心 (如 npmjs.com) 扮演着至…

Git——本地使用详解

目录 Git1、开始版本控制1.1、初始化Repository1.2、使目录脱离Git控制 2、把文件交给Git管控2.1、创建文件后交给Git2.2、git add之后再次修改文件2.3、git add "--all"与"."参数区别2.4、把暂存区的内容提交到存储库里存档 3、工作区、暂存区与存储库3.1…

Epuck2机器人固件更新及IP查询

文章目录 前言一、下载固件更新软件包:二、查询机器人在局域网下的IP 前言 前面进行了多机器人编队仿真包括集中式和分布式,最近打算在实物机器人上跑一跑之前的编队算法。但由于Epuck2机器人长时间没使用,故对其进行固件的更新,…

软考高级:软件架构本质和作用概念和例题

作者:明明如月学长, CSDN 博客专家,大厂高级 Java 工程师,《性能优化方法论》作者、《解锁大厂思维:剖析《阿里巴巴Java开发手册》》、《再学经典:《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

【PyTorch】进阶学习:一文详细介绍 torch.save() 的应用场景、实战代码示例

【PyTorch】进阶学习:一文详细介绍 torch.save() 的应用场景、实战代码示例 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程…

Vue组件封装方案对比——v-if方式与内置component方式

近期在准备搭建一个通用组件库,而公司现有的各个系统也已有自己的组件库只是没抽离出来,但是目前有两套不同的组件封装方案,所以对于方案的选择比较困惑,于是对两种方式进行了对比,结合网上找到的一些开源组件库进行分…

wireshark解析https数据包

Debian11环境: 在linux环境下抓取访问某个https的网址时抓取的数据包都是加密的,导致无法跟踪到数据包流,现在尝试将抓取的https包进行解密。 1、解密https数据包需要设置SSLKEYLOGFILE变量,推荐写入配置文件中。 echo "exp…

Mysql的行级锁

MySQL 中锁定粒度最小的一种锁,是 针对索引字段加的锁 ,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。行级锁和存…

Ps:文字工具

工具箱里的文字工具组中包含了四种工具: 横排文字工具 Horizontal Type Tool 直排文字工具 Vertical Type Tool 横排文字蒙版工具 Horizontal Type Mask Tool 直排文字蒙版工具 Vertical Type Mask Tool 快捷键:T 横排文字蒙版工具和直排文字蒙版工具…

C++第六弹---类与对象(三)

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】 目录 1、类的6个默认成员函数 2、构造函数 2.1、概念 2.2、特性 3、析构函数 3.1、概念 3.2、特性 3.3、调用顺序 总结 1、类的6个默认成员函数…

力扣hot100:33. 搜索旋转排序数组(二分的理解)

33.搜索旋转排序数组 ​ 这是一个非常有趣的问题,如果不要求使用O(logn)应该没人会想到吧。。 方法一: 极致的分类讨论。旋转排序数组,无非就是右边的增区间的数小于左边的增区间的数,然后依次排序。因此我们只需要分三类讨论即可…

【测试开发学习历程】MySQL数据类型 + MySQL表创建与操作

前言: 半夜梦到自己没有写今天的博客,结果惊醒起来看一看。 得,真的没写。QWQ 可谓垂死病中惊坐起了。 看看发博的时间6:16,而不是什么整点的,就知道我4点就起来了,不是定时发布&#xff01…

知识积累(五):Transformer 家族的学习笔记

文章目录 1. RNN1.1 缺点 2. Transformer2.1 组成2.2 Encoder2.2.1 Input Embedding(嵌入层)2.2.2 位置编码2.2.3 多头注意力2.2.4 Add & Norm 2.3 Decoder2.3.1 概览2.3.2 Masked multi-head attention 2.4 Transformer 模型的训练和推理2.4.1 训练…