UnityShader(十六)凹凸映射

2024-03-18 11:12

本文主要是介绍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}}
}

效果:

 

这篇关于UnityShader(十六)凹凸映射的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/822157

相关文章

docker 重启容器且修改服务映射端口

要重启 Docker 容器并修改服务的映射端口,可以按照以下步骤进行操作: 1. 停止当前运行的容器 如果你想重新配置端口,通常需要先停止当前运行的容器。你可以使用以下命令停止容器: docker stop <container_name_or_id> 2. 删除现有容器 为了修改端口映射,你需要删除旧的容器并重新创建一个新的容器。首先运行以下命令删除停止的容器: docker rm

maven项目中程序运行编译的时候出现:编码GBK的不可映射字符

由于JDK是国际版的,我们在用javac.exe编译时,编译程序首先会获得我们操作系统默认采用的编码格式(也即在编译java程序时,若我们不指定源程序文件的编码格式,JDK首先获得操作系统的file.encoding参数(它保存的就是操作系统默认的编码格式,如WIN2k,它的值为GBK),然后JDK就把我们的java源程序从file.encoding编码格式转化为JAVA内部默认的UNICODE格

ManyToMany双向外键关联(基于注解)的映射案例(简单版)

学生和老师就是多对多的关系,一个学生有多个学生,一个老师也有多个学生,这里的多对多映射采用中间表连接的映射策略,建立中间表的映射策略,建立中间表分别引入俩边的主键作为外键。通过中间表映射俩个表之间的关系。 下面就以学生类和老师类为例介绍多对多的映射关系的实例 Students类 package mtm_bfk;import java.io.Serializable;import java.

Flink实战案例(十六):Flink 异步IO 简介

1 Aysnc I/O是啥? Async I/O 是阿里巴巴贡献给社区的一个呼声非常高的特性,于1.2版本引入。  主要目的:是为了解决与外部系统交互时网络延迟成为了系统瓶颈的问题。  场景: 流计算系统中经常需要与外部系统进行交互,比如需要查询外部数据库以关联上用户的额外信息。通常,我们的实现方式是向数据库发送用户a的查询请求(例如在MapFunction中),然后等待结果返回,在这

【硬刚ES】ES基础(十六)基于词项和基于全文的搜索

本文是对《【硬刚大数据之学习路线篇】从零到大数据专家的学习指南(全面升级版)》的ES部分补充。 DELETE productsPUT products{"settings": {"number_of_shards": 1}}POST /products/_bulk{ "index": { "_id": 1 }}{ "productID" : "XHDK-

CSS学习6--背景图片、颜色、位置、附着、简写、透明、缩放、多背景、凹凸文字、导航栏例子

CSS背景 一、背景颜色和图片二、背景位置三、背景附着四、背景简写五、背景透明六、背景缩放七、多背景八、凹凸文字九、导航栏例子 一、背景颜色和图片 background-color: pink; 背景颜色backgroundoimage: url(##.jpg); 背景图片background-repeat: 平铺 repeat-x横向平铺,repeat-y纵向平铺; 平铺不到

Mybatis查询到多条记录但是映射到list集合只有一条或者条数不对

如果你出现了这个问题。 *. 原因:mybatis是基于当前实体对象的id进行映射的 *.所以在查询字段中加上查询id即可。 搞定!!!!

【高等数学学习记录】映射

【高等数学&学习记录】映射 从事测绘工作多年,深刻感受到基础知识的重要及自身在这方面的短板。 为此,打算重温测绘工作所需基础知识。练好基本功,为测绘工作赋能。 1 知识点 1.1 映射 映射 设 X X X、 Y Y Y是非空集合,若存在法则 f f f,使 X X X中每个元素 x x x,在 Y Y Y中有唯一确定的元素 y y y与之对应,则称 f f f为从 X X X到

NAT技术-将多个内部网络设备映射到一个公共IP地址

问题: 今天上课的时候老师让我们在VMware填同一个子网ip 192.168.196.0,然后给我们的linux镜像都是同一个压缩包,结果我们的静态ip地址都是同一个。 192.168.196.0下面有256个ip地址,范围是192.168.196.0到192.168.196.255。我们几十个人的ip地址怎么会是同一个 linux镜像的ip地址被老师写死了, 1.难道内网的ip可以随便定

使用Mybatis Generator插件生成纯净代码,自定义扩展注释和注释,xml映射文件,不用修改源码!!!

先看效果: (基于Mybatis-generator-core-1.4.0) 生成模型(实体): 根据MySQL库中的tb_prod_info表字段及注释自动生成. package com.ezsyn.cloudstudy.product.dao.entity;import io.swagger.annotations.ApiModelProperty;import lombok.Gette