UnityShader(十六)凹凸映射

news2024/10/7 20:33:35

前言:

纹理的一种常见应用就是凹凸映射(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
        }
  }
}

效果:

 

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

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

相关文章

《论文阅读》E-CORE:情感相关性增强的移情对话生成 EMNLP 2023

《论文阅读》E-CORE:情感相关性增强的移情对话生成 EMNLP 2023 前言摘要模型架构图构建边的构建和初始化节点的初始化图更新情感相关性加强解码损失函数总结前言 亲身阅读感受分享,细节画图解释,再也不用担心看不懂论文啦~ 无抄袭,无复制,纯手工敲击键盘~ 今天为大家带来…

Django验证码(一)

一、介绍 1.1、概述 验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序 可以防止:恶意破解密码、刷票、论坛灌水,有效防止某…

@Valid 和 @Validated 区别和使用方法

1、相关依赖 Valid 依赖包 <dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId> </dependency>Validated的依赖包 <dependency> <groupId>org.springframework</groupId> <…

学习JavaEE的日子 Day27 手撕HashMap底层原理

Day27 1.手撕HashMap底层原理(重点) public class Test01 {public static void main(String[] args) {// Float float1 new Float("0.0f"); // Float float2 new Float("0.0f"); // Float result float1/float2; // System.out.println(result);/…

【论文阅读】DiffSpeaker: Speech-Driven 3D Facial Animation with Diffusion Transformer

DiffSpeaker: 使用扩散Transformer进行语音驱动的3D面部动画 code&#xff1a;GitHub - theEricMa/DiffSpeaker: This is the official repository for DiffSpeaker: Speech-Driven 3D Facial Animation with Diffusion Transformer paper&#xff1a;https://arxiv.org/pdf/…

【SQL】1174. 即时食物配送 II (窗口函数row_number; group by写法;对比;定位错因)

前述 推荐学习&#xff1a; 通俗易懂的学会&#xff1a;SQL窗口函数 题目描述 leetcode题目&#xff1a;1174. 即时食物配送 II 写法一&#xff1a;窗口函数 分组排序&#xff08;以customer_id 分组&#xff0c;按照order_date 排序&#xff09;&#xff0c;窗口函数应用。…

从零自制docker-4-【PID Namespace MOUNT Namespace】

文章目录 PID namespace代码mountnamespace通俗理解代码 PID namespace 每个命名空间都有独立的PID空间&#xff0c;即每个命名空间的进程都由一开始分配。 新建立的进程内部进程ID为1 代码 package main import ("log""os/exec""os""sy…

pyspark基础 -- DataFrame的理解与案例

DataFrame(df)介绍 datafram就是一个内存中的二维表结构&#xff0c;具备表结构的三个基本属性&#xff1a; 行列表结构描述 在结构层面&#xff0c;pyspark中的StructType对象描述了表结构&#xff0c;StructField对象描述了表的一个列信息&#xff1b;在数据层面&#xff…

银行信息系统应用架构导论-引用

一级目录二级目录金融标准和参考文档一、银行企业级应用系统架构规划企业级应用系统架构规划《金融科技发展规划&#xff08;2022-2025年&#xff09;&#xff08;2022年1月中国人民银行印发&#xff09;》 《关于银行业保险业数字化转型的指导意见&#xff08;2022年1月中国银…

【linux】CentOS查看系统信息

一、查看版本号 在CentOS中&#xff0c;可以通过多种方法来查看版本号。以下是几种常用的方法&#xff1a; 使用cat命令查看/etc/centos-release文件&#xff1a; CentOS的版本信息存储在/etc/centos-release文件中。可以使用cat命令来显示该文件的内容&#xff0c;从而获得C…

ThingsBoard初始化数据库Postgres

视频教程&#xff1a; ThingsBoard初始化数据库postgres_哔哩哔哩_bilibilihingsBoard是一个基于Java的开源物联网平台&#xff0c;旨在实现物联网项目的快速开发、管理和扩展。本课程主要从0到1带你熟悉ThingsBoard&#xff0c;学习优秀的物联网变成思维与思想&#xff0c;主…

Nginx离线安装(保姆级教程)

1、下载与安装gcc-c环境 获取rpm包的方式很多&#xff0c;在这里推荐使用yum工具获取&#xff0c;因为手动从官网下载&#xff0c;手动执行rpm -Uvh *.rpm --nodeps --force命令进行安装&#xff0c;可能会缺少某个依赖&#xff0c;我们也不确定到底需要哪些依赖。 因此需要准…

游戏引擎中网络游戏的基础

一、前言 网络游戏所面临的挑战&#xff1a; 一致性&#xff1a;如何在所有的主机内都保持一样的表现可靠性&#xff1a;网络传输有可能出现丢包安全性&#xff1a;反作弊&#xff0c;反信息泄漏。多样性&#xff1a;不同设备之间链接&#xff0c;比如手机&#xff0c;ipad&a…

第九节:Vben Admin实战-系统管理之角色管理实现-上

系列文章目录 第一节:Vben Admin介绍和初次运行 第二节:Vben Admin 登录逻辑梳理和对接后端准备 第三节:Vben Admin登录对接后端login接口 第四节:Vben Admin登录对接后端getUserInfo接口 第五节:Vben Admin权限-前端控制方式 第六节:Vben Admin权限-后端控制方式 第七节…

RUST egui体验

egui官方提供了web版的demo&#xff0c;效果还是很不错的&#xff0c;就是用的时候有点一头雾水&#xff0c;没有找到明确的指导怎么把这些组件插入到自己的application或者web。花了一天时间撸了一遍流程&#xff0c;记录一下&#xff0c;说不定以后能用到呢 >_< efram…

蓝桥杯并查集|路径压缩|合并优化|按秩合并|合根植物(C++)

并查集 并查集是大量的树&#xff08;单个节点也算是树&#xff09;经过合并生成一系列家族森林的过程。 可以合并可以查询的集合的一种算法 可以查询哪个元素属于哪个集合 每个集合也就是每棵树都是由根节点确定&#xff0c;也可以理解为每个家族的族长就是根节点。 元素集合…

【Java基础知识总结 | 第三篇】深入理解分析ArrayList源码

文章目录 3.深入理解分析ArrayList源码3.1ArrayList简介3.2ArrayLisy和Vector的区别&#xff1f;3.3ArrayList核心源码解读3.3.1ArrayList存储机制&#xff08;1&#xff09;构造函数&#xff08;2&#xff09;add()方法&#xff08;3&#xff09;新增元素大体流程 3.3.2ArrayL…

R:简易的Circos图

library(grid) library(circlize) library(RColorBrewer) library(ComplexHeatmap) setwd("C:/Users/fordata/Downloads/Circos") # 创建颜色调色板 coul <- colorRampPalette(brewer.pal(9, "Set3"))(12) # 读取基因组数据 genome <- read.table(ci…

【启动npm run serve 奇怪的报错】

报错如下&#xff1a; INFO Starting development server... utils.js:587Uncaught TypeError [ERR_INVALID_ARG_VALUE]: The argument path must be a string or Uint8Array without null bytes. Received E:\\#\u0000#idea-workspace\\wonderful-search\\wonderful-search-v…

julia语言中的决策树

决策树&#xff08;Decision Tree&#xff09;是一种基本的分类与回归方法&#xff0c;它呈现出一种树形结构&#xff0c;可以直观地展示决策的过程和结果。在决策树中&#xff0c;每个内部节点表示一个属性上的判断条件&#xff0c;每个分支代表一个可能的属性值&#xff0c;每…