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,但结果变量为rotationo.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.mzph.cn/news/752200.shtml

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

相关文章

数据结构之顺序存储-顺序表的基本操作c/c++(创建、初始化、赋值、插入、删除、查询、替换、输出)

学习参考博文&#xff1a;http://t.csdnimg.cn/Qi8DD 学习总结&#xff0c;同时更正原博主在顺序表中插入元素的错误。 数据结构顺序表——基本代码实现&#xff08;使用工具&#xff1a;VS2022&#xff09;&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include <stdi…

uniapp canvas文字和元素居中

文字居中&#xff1a;ctx.textAlign "center"; 元素居中&#xff1a;ctx.arc(screenWidth / 2, 122, 40, 0, 2 * Math.PI); ctx.arc()的x轴为当前屏幕的宽度/2&#xff1b; let screenWidth 540; let screenHeight 960; // 头像 if (photoimg) {ctx.setFillSty…

gitlab cicd问题整理

1、docker设置数据目录&#xff1a; 原数据目录磁盘空间不足&#xff0c;需要更换目录&#xff1a; /etc/docker/daemon.json //写入/etc/docker/daemon.json {"data-root": "/data/docker" } 2、Dockerfile中ADD指令不生效 因为要ADD的文件被.docker…

指南:在各主流操作系统上安装与配置Apache Tomcat

指南&#xff1a;在各主流操作系统上安装与配置Apache Tomcat Apache Tomcat作为一款广受欢迎的开源Java Servlet容器&#xff0c;为用户提供了一个纯Java环境下的Web服务器和Servlet容器。本文将详细介绍如何在不同的操作系统上安装Apache Tomcat&#xff0c;并进行基本的配置…

【计算机网络】什么是http?

​ 目录 前言 1. 什么是HTTP协议&#xff1f; 2. 为什么使用HTTP协议&#xff1f; 3. HTTP协议通信过程 4. 什么是url&#xff1f; 5. HTTP报文 5.1 请求报文 5.2 响应报文 6. HTTP请求方式 7. HTTP头部字段 8. HTTP状态码 9. 连接管理 长连接与短连接 管线化连接…

smartmontools-5.43交叉编译Smartctl

嵌入式系统的sata盘经常故障&#xff0c;需要使用smatctl工具监控和诊断sata故障。 1. 从网上下载开源smartmontools-5.43包。 2. 修改makefile进行交叉编译。 由于软件包中已经包含Makefile.am&#xff0c;Makefile.in。直接运行 automake --add-missing 生成Makefile。 3.…

自动部署SSL证书到阿里云腾讯云CDN

项目地址&#xff1a;https://github.com/yxzlwz/ssl_update 项目简介 目前&#xff0c;自动申请和管理免费SSL证书的项目有很多&#xff0c;如个人正在使用的 acme.sh。然而在申请后&#xff0c;如果我们的需求不仅限于服务器本地的使用&#xff0c;证书的部署也是一件麻烦事…

Gin 框架中实现路由的几种方式介绍

本文将为您详细讲解 Gin 框架中实现路由的几种方式&#xff0c;并给出相应的简单例子。Gin 是一个高性能的 Web 框架&#xff0c;用于构建后端服务。在 Web 应用程序中&#xff0c;路由是一种将客户端请求映射到特定处理程序的方法。以下是几种常见的路由实现方式&#xff1a; …

JavaScript | 检测文档在垂直方向已滚动的像素值用pageYOffset在webstorm上显示弃用了,是否应该继续使用?还是用其他替代?

在学习JavaScript的时候&#xff0c;深入学习时会遇到一些实际案例需要检测文档在垂直方向已滚动的像素值。 例如&#xff0c;当前页面内容很多&#xff0c;我想要滚动鼠标滑轮或者拖拽滚动条来浏览网页下面的内容。这时候一动滚动条&#xff0c;一些绝对固定的盒子却想要随着…

python图形化编程turtle小乌龟

文章目录&#xff1a; 一&#xff1a;导入包&#xff08;常用的&#xff09; 二&#xff1a;布局 1.设置世界坐标系 2.窗体 3.画布屏幕screen 三&#xff1a;线条画笔海龟 1.运动 2.样式 3.外观 4.其他 四&#xff1a;颜色 五&#xff1a;文字 六&#xff1a;图…

【Kubernetes】k8s删除master节点后重新加入集群

目录 前言一、思路二、实战1.安装etcdctl指令2.重置旧节点的k8s3.旧节点的的 etcd 从 etcd 集群删除4.在 master03 上&#xff0c;创建存放证书目录5.把其他控制节点的证书拷贝到 master01 上6.把 master03 加入到集群7.验证 master03 是否加入到 k8s 集群&#xff0c;检查业务…

Unity触发器的使用

1.首先建立两个静态精灵&#xff08;并给其中一个物体添加"jj"标签&#xff09; 2.添加触发器 3.给其中一个物体添加刚体组件&#xff08;如果这里是静态的碰撞的时候将不会触发效果&#xff0c;如果另一个物体有刚体可以将它移除&#xff0c;或者将它的刚体属性设置…

c++pair的用法

pair简单来说就是可以存储两种类型数据的一个类&#xff0c;其内部是使用模板实现的&#xff0c;所以可以指定其内部的类型。 pair在#include <utility> pair的构造 pair<int, string> p1({ 1,"张三" });pair<int, string> p2;pair<int, str…

文件的基础

一、文件 什么是文件 文件流&#xff1a; 一、1、文件的相关操作 创建文件的三种方式&#xff1a; public class FileCreate {public static void main(String[] args) {}//方式1 new File(String pathname)Testpublic void create01() {String filePath "e:\\news1.…

Qt源码分析: QEventLoop实现原理

QEventLoop屏蔽了底层消息循环实现细节&#xff0c;向上提供了与平台无关的消息/事件循环。 本文拟对Windows系统下QEventLoop的实现原理予以分析。 注1&#xff1a;限于研究水平&#xff0c;分析难免不当&#xff0c;欢迎批评指正。 注2&#xff1a;文章内容会不定期更新。 …

C语言-memcpy(不重复地址拷贝 模拟实现)

memcpy&#xff08;不重复地址拷贝&#xff09; 语法格式 在C语言中&#xff0c;memcpy 是一个标准库函数&#xff0c;用于在内存之间复制数据。它的原型定义在 <string.h> 头文件中。memcpy 的语法格式如下&#xff1a; c void *memcpy(void *destination, const voi…

健身·健康行业Web3新尝试:MATCHI

随着区块链技术进入主流&#xff0c;web3 运动已经开始彻底改变互联网&#xff0c;改写从游戏到金融再到艺术的行业规则。现在&#xff0c;MATCHI的使命是颠覆健身行业。 MATCHI是全球首个基于Web3的在线舞蹈健身游戏和全球首个Web3舞蹈游戏的发起者&#xff0c;注册于新加坡&a…

K8s 集群高可用master节点ETCD挂掉如何恢复?

写在前面 很常见的集群运维场景&#xff0c;整理分享博文内容为 K8s 集群高可用 master 节点故障如何恢复的过程理解不足小伙伴帮忙指正 不必太纠结于当下&#xff0c;也不必太忧虑未来&#xff0c;当你经历过一些事情的时候&#xff0c;眼前的风景已经和从前不一样了。——村上…

【JVM】(内存区域划分 为什么要划分 具体如何分 类加载机制 类加载基本流程 双亲委派模型 类加载器 垃圾回收机制(GC))

文章目录 内存区域划分为什么要划分具体如何分 类加载机制类加载基本流程双亲委派模型类加载器 垃圾回收机制&#xff08;GC&#xff09; 内存区域划分 为什么要划分 JVM启动的时候会申请到一整个很大的内存区域,JVM是一个应用程序,要从操作系统这里申请内存,JVM就需要根据,把…

【Leetcode-73.矩阵置零】

题目&#xff1a; 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]示例 2&…