Unity Shader-法线贴图(Normal Map)与视差贴图(Parallax Map)

增强法线与视差贴图
本文介绍如何通过调整法线强度改善游戏画面,并深入探讨视差贴图原理及其实现方法,显著提升视觉效果。

简介


法线贴图技术至今一直是游戏增强画面效果的利器之一。随着移动设备的性能越来越好,曾经的在PC平台的各种高级技术在移动平台也都开始大规模使用了,法线贴图甚至是视差贴图已经开始在移动平台的游戏上使用了。 之前的一篇文章简单探究了一下法线贴图的原理以及在Unity下的实现,本篇就不多介绍了。本篇文章主要研究一下怎样增强法线贴图的效果,法线贴图的进阶版-视差贴图(Parallax Map)。

可调法线强度的shader


我们常用的法线效果,一般是用于增强一些细节,有时候我们想要的法线,实际用起来就会发现法线效果不强,尤其是较远的物体,比如地形,墙壁之类的场景物体,虽然加上了法线,但是由于镜头离得比较远,法线效果表现不出来。而这个时候我们一种方式是去找美术调整法线贴图,但是一方面调整贴图会比较花费美术的时间,一方面万一这张图用了多次,有的地方想要比较明显的法线效果,有的地方想要不明显的法线效果,这就比较蛋疼了。如果我们用灰度图转法线图的时候(Create from GrayScale),有个Bumpiness的选项,可以控制这张图的法线强度。但是很多时候,我们的法线图是美术直接在工具里面做好的,就没法转化了。如果我们能给一个参数,直接在材质里面调整法线的强度就好了。之前纠结了好几个小时,后来突然查到了 这个帖子,豁然开朗。
首先,我们从法线贴图中获取到的是这个采样点对应的法线方向,光滑的部分法线方向都为垂直于表面竖直向上的方向,也就是(0,0,1)的方向,而有凹凸的地方,法线就会有偏移值,换句话说,我们让法线贴图采样出来的方向越偏离(0,0,1)方向,就会越加强法线的效果。我们可以减小法线贴图的b值,但b通道越小,法线效果越强,不过这样不直观,换一种方式就是加强法线贴图的r,g值,给r,g通道乘以一个factor,换句话说可以直接乘以一个(factor,factor,1)向量,就达到了增强与减弱法线效果的目的。
下面附上可以直接调整法线强度的shader代码:

//Bump Map
//by:puppet_master
//2017.2.27
Shader "ApcShader/NormalMapX"
{
	//属性
	Properties{
		_Diffuse("Diffuse", Color) = (1,1,1,1)
		_MainTex("Base 2D", 2D) = "white"{}
		_BumpMap("Bump Map", 2D) = "bump"{}
		_BumpFactor ("Bump Scale", Range(0, 10.0)) = 1.0
	}

	//子着色器	
	SubShader
	{
		Pass
		{
			//定义Tags
			Tags{ "RenderType" = "Opaque" }

			CGPROGRAM
			//引入头文件
			#include "Lighting.cginc"
			//定义Properties中的变量
			fixed4 _Diffuse;
			sampler2D _MainTex;
			//使用了TRANSFROM_TEX宏就需要定义XXX_ST
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float _BumpFactor;

			//定义结构体:vertex shader阶段输出的内容
			struct v2f
			{
				float4 pos : SV_POSITION;
				//转化纹理坐标
				float2 uv : TEXCOORD0;
				//tangent空间的光线方向
				float3 lightDir : TEXCOORD1;
			};

			//定义顶点shader
			v2f vert(appdata_tan v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				//这个宏为我们定义好了模型空间到切线空间的转换矩阵rotation,注意后面有个;
				TANGENT_SPACE_ROTATION;
				//ObjectSpaceLightDir可以把光线方向转化到模型空间,然后通过rotation再转化到切线空间
				o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
				//通过TRANSFORM_TEX宏转化纹理坐标,主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}

			//定义片元shader
			fixed4 frag(v2f i) : SV_Target
			{
				//unity自身的diffuse也是带了环境光,这里我们也增加一下环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.xyz;
				//直接解出切线空间法线
				float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
				//构建一个float3向量,用于与法线相乘得到更改后的法线值
				float3 normalFactor = float3(_BumpFactor, _BumpFactor, 1);
				//将factor与法线相乘并normalize
				float3 normal = normalize(tangentNormal * normalFactor);
				//normalize一下切线空间的光照方向
				float3 tangentLight = normalize(i.lightDir);
				//兰伯特光照
				fixed3 lambert = saturate(dot(normal, tangentLight));
				//最终输出颜色为lambert光强*材质diffuse颜色*光颜色
				fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz + ambient;
				//进行纹理采样
				fixed4 color = tex2D(_MainTex, i.uv);
				return fixed4(diffuse * color.rgb, 1.0);
			}

			//使用vert函数和frag函数
			#pragma vertex vert
			#pragma fragment frag	

			ENDCG
		}

	}
	//前面的Shader失效的话,使用默认的Diffuse
	FallBack "Diffuse"
}

我们给一张diffuse图以及一张法线图,这样我们就可以灵活地直接在材质上调整法线的强弱了。先来一张法线强度为0的情况,等同于不加法线:

强度为1时,等同于直接使用法线:

加强法线强度,设置为2时,法线效果非常更加明显:

虽然我们可以调整法线的强度,但是也不是就可以无休止地调整,当强度调整为10时,效果就有点吓人了....


视差贴图


法线贴图是通过改变要表现凹凸部分的表面的明暗值来让人有一种凹凸的感觉,但是这毕竟只是一种最初级的模拟,如果凹凸不需要特别明显,直接用法线贴图还好:

当我们想再增强法线的强度的时候,极限的情况也只能把凹陷的地方置为全黑,但是法线的强度并没有特别明显的提升:

而且上面的增强法线的shader,当法线效果增强到一定程度之后,就不仅仅会影响有凹凸的部分,还会导致非凹凸部分也被改变颜色,最后变得面目全非。所以这种做法只能算是一个“歪门邪道”(有时候这种歪门邪道还是很有用的)。

下面我们就来看一下正统一点的增强版本的法线效果,也就是视差贴图(Parallax Map)。视差贴图是真正通过改变采样点纹理坐标达到让法线效果看起来更加真实并且可以增强法线效果的目的。简单画了一张示意图解释一下视差贴图的原理:

上面是一个用法线模拟的凹凸表面,红色为我们的视线方向,视线的方向(切线空间的viewDir)是从A指向B,指向相机方向。如果表面真的有凹凸的话,那么视线方向应该和B点相交,但是实际上表面是平的,真实的交点是A点。我们在纹理坐标的层面来看一下,以uv的u轴为例,视线方向xyz转化到切线空间后,正好对应纹理坐标系上的u,v,以及垂直表面的方向。所以,为了让采样点更准确,我们需要调整实际采样时使用的纹理坐标,也就是给一个偏移值,在进行采样MainTex和BumpTex的时候,都加上相应的偏移。那么问题又来了,我们怎么知道这个偏移是多少,怎么控制这个偏移呢?首先,我们的视线方向是一直在改变的,也就是说我们可能从各个方向看这个凸起来的部分,比如我们从右边看(如上图),那么就需要向右边偏移一些;如果从左边看,那么就需要向左偏移一些;如果垂直看表面,那就不需要偏移了。所以,我们正好可以让视线方向转化到切线空间,之后视线方向的x,y轴就是我们uv需要偏移的方向了,这也就是所谓的视差。第二个问题就是我们不知道这个面的高度是多少,最简单的一种方式就是直接给一张高度图,黑色表示不需要凸起,白色表示需要凸起,这样我们进行采样的时候先采这张高度图,就知道这个点对应的高度,也就变相地知道我们需要偏移多少了,凸起越高偏移越多,凸起越低偏移越少(视线方向一致的情况下)。有了这两点,剩下的就再增加一个系数,来控制我们偏移的强度。那么最终我们需要的采样偏移值就等于tanViewDir.xy / tanViewDir.z * height * parallaxScale。这里用tanViewDir.xy / tanViewDir.z,通过切平面垂直的z分量来调整xy方向(uv方向)偏移的大小,也就是说当我们视角越平,uv偏移越大;视角越垂直于表面,uv偏移值越小。

下面附上视差贴图的shader代码:
//ParallaxMap
//by:puppet_master
//2017.3.10
Shader "ApcShader/ParallaxMap"
{
	//属性
	Properties{
		_Diffuse("Diffuse", Color) = (1,1,1,1)
		_MainTex("Base 2D", 2D) = "white"{}
		_BumpMap("Bump Map", 2D) = "bump"{}
		_HeightMap("Height Map", 2D) = "black"{}
		_HeightFactor ("Height Scale", Range(0, 0.1)) = 0.05
	}

	//子着色器	
	SubShader
	{
		Pass
		{
			//定义Tags
			Tags{ "RenderType" = "Opaque" }

			CGPROGRAM
			//引入头文件
			#include "Lighting.cginc"
			//定义Properties中的变量
			fixed4 _Diffuse;
			sampler2D _MainTex;
			//使用了TRANSFROM_TEX宏就需要定义XXX_ST
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float _HeightFactor;
			sampler2D _HeightMap;

			//定义结构体:vertex shader阶段输出的内容
			struct v2f
			{
				float4 pos : SV_POSITION;
				//转化纹理坐标
				float2 uv : TEXCOORD0;
				//tangent空间的光线方向
				float3 lightDir : TEXCOORD1;
				//需要视线方向
				float3 viewDir : TEXCOORD2;
			};

			//计算uv偏移值
			inline float2 CaculateParallaxUV(v2f i)
			{
				//采样heightmap
				float height = tex2D(_HeightMap, i.uv).r;
				//normalize view Dir
				float3 viewDir = normalize(i.viewDir);
				//偏移值 = 切线空间的视线方向.xy(uv空间下的视线方向)* height * 控制系数
				float2 offset = viewDir.xy / viewDir.z * height * _HeightFactor;
				return offset;
			}

			//定义顶点shader
			v2f vert(appdata_tan v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				//这个宏为我们定义好了模型空间到切线空间的转换矩阵rotation,注意后面有个;
				TANGENT_SPACE_ROTATION;
				//ObjectSpaceLightDir可以把光线方向转化到模型空间,然后通过rotation再转化到切线空间
				o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
				//通过TRANSFORM_TEX宏转化纹理坐标,主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				//计算观察方向
				o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));
				return o;
			}

			//定义片元shader
			fixed4 frag(v2f i) : SV_Target
			{
				//unity自身的diffuse也是带了环境光,这里我们也增加一下环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.xyz;
				float2 uvOffset = CaculateParallaxUV(i);
				i.uv += uvOffset;
				//直接解出切线空间法线
				float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
				//normalize一下切线空间的光照方向
				float3 tangentLight = normalize(i.lightDir);
				//兰伯特光照
				fixed3 lambert = saturate(dot(tangentNormal, tangentLight));
				//最终输出颜色为lambert光强*材质diffuse颜色*光颜色
				fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz + ambient;
				//进行纹理采样
				fixed4 color = tex2D(_MainTex, i.uv);
				return fixed4(diffuse * color.rgb, 1.0);
			}

			//使用vert函数和frag函数
			#pragma vertex vert
			#pragma fragment frag	

			ENDCG
		}

	}
		//前面的Shader失效的话,使用默认的Diffuse
		FallBack "Diffuse"
}

对于视差贴图,除了法线图之外,我们增加了一个HeightMap的贴图,其实只有一个通道,也可以直接放在NormapMap的A通道中,贴图中黑色表示平坦,白色表示凸起。如下图所示:

视差贴图效果如下:

在近距离的时候,法线图是经不起细看的,但是视差贴图效果却仍然很好,而且能看到凸起的细节:

俗话说得好,没有对比就没有伤害,我们对比一下没有法线,普通法线以及视差贴图的效果:


不过视差贴图也不是可以无限扩大凹凸程度的,因为毕竟视差贴图也只是近似的模拟,将采样点向真正的采样点靠近。还有一些更加高级的视差贴图,比如POM,以及带自阴影的视差贴图。不过这些贴图技术基本不可能在现在的移动设备上跑,因为需要逐像素计算一些迭代的计算,暂时没有再往深研究,最后附上一些关于视差贴图的参考链接(推荐第一个的那篇翻译文章,非常详细的讲解了各种视差贴图):


原文地址:http://blog.youkuaiyun.com/puppet_master/article/details/57125379

<think>我们正在讨论的是bump贴图(凹凸贴图)和法线贴图在计算机图形学中的区别以及各自的应用场景。根据提供的引用,我们可以总结如下:1.**原理差异**:-**Bump贴图**:使用灰度图像来表示表面的凹凸信息。其中,亮色区域(如白色)表示凸起,暗色区域(如黑色)表示凹陷。它通过改变表面法线在渲染时的方向来模拟光照效果,从而产生凹凸感,但实际上并没有改变模型的几何形状[^1][^2]。- **法线贴图**:使用RGB图像来存储每个像素点的法线方向(即表面的朝向)。其中,RGB值分别对应法线向量的X、Y、Z分量(通常映射到[0,1]范围)。法线贴图同样不改变几何形状,但它提供了更精确的法线扰动信息,能够产生更逼真的凹凸效果[^1][^3][^4]。2.**技术实现**:- Bump贴图通常通过计算灰度图的梯度(高度变化率)来扰动法线方向[^2][^4]。-法线贴图直接提供扰动后的法线向量,因此计算效率更高,且能表现更复杂的细节(如方向性光照变化)[^3][^4]。 3. **视觉效果**:- Bump贴图适用于模拟简单的表面纹理(如粗糙的石头、木材纹理),但边缘轮廓仍保持平滑(因为几何未变)[^1][^2]。-法线贴图能表现更细致的凹凸(如皮肤皱纹、金属划痕),且在视角变化时保持更好的立体感[^3][^4]。然而,当视线表面平行时,两者都会暴露“表面实际平坦”的缺陷[^2]。 4. **应用场景**:-**Bump贴图**:适合早期硬件或移动平台(计算量小),或用于不需要高精度细节的场合(如远景物体)[^2]。-**法线贴图**:广泛用于现代游戏和影视渲染,尤其当需要近景细节(如角色模型、武器表面)时[^3][^4]。 5. **进阶技术**:-为弥补法线贴图在边缘轮廓的不足,衍生出**置换贴图(Displacement Map)**——它真正改变顶点位置(需细分曲面支持),但计算成本高[^1]。-**视差贴图ParallaxMap)**通过偏移纹理坐标模拟深度,改善视角平行时的穿帮问题[^2]。###数学表达在光照计算中,两种贴图的核心都是修改法线向量$\vec{N}$: -Bump贴图通过高度场$h(u,v)$计算扰动后的法线:$$\vec{N}_{new}= \vec{N} +\left( \frac{\partial h}{\partialu},\frac{\partialh}{\partial v},0\right)$$ -法线贴图直接存储$\vec{N}_{new}$的切线空间坐标(通常需从RGB转换:$\vec{N} =2\cdot\text{RGB} -1$)[^4]。###总结对比表|**特性**|**Bump贴图**|**法线贴图**| |------------------|--------------------------|--------------------------| |**数据形式**|灰度图(高度信息)| RGB图(法线向量)|| **计算开销**|较低(需计算梯度)|中等(直接读取法线)|| **细节精度**|中等(适合宏观凹凸)|高(适合微观细节)|| **边缘轮廓处理** |无改善(边缘平滑)|无改善(边缘平滑)|| **典型应用**|简单纹理、移动端渲染|高细节模型、PC/主机游戏|[^1]:置换贴图(Displacement map),凹凸贴图(Bumpmap)法线贴图(Normal map)的区别。 [^2]: UnityShader浮雕凹凸贴图BumpMap法线贴图NormalMap的原理及其区别。[^3]:在三维计算机图形学中,法线贴图(英語:Normalmapping)是一种模拟凹凸处光照效果的技术。[^4]:法线映射:凹凸贴图的核心思想是通过对表面法线的扰动来模拟凹凸效果。 §§相关问题§§1.为什么法线贴图通常呈现蓝紫色?2.在实时渲染中,如何选择使用bump贴图还是法线贴图?3.置换贴图法线贴图在性能开销上有何差异?</think>### Bump贴图法线贴图的区别及应用场景(计算机图形学) #### **核心原理差异** 1. **数据表示方式** - **Bump贴图**: 使用**单通道灰度图像**存储高度信息。 - 白色/亮色 → 表面凸起 - 黑色/暗色 → 表面凹陷 通过灰度梯度计算法线扰动(数学表达:$\Delta N \propto \nabla h(u,v)$,其中$h$为高度场)[^1][^4]。 - **法线贴图**: 使用**三通道RGB图像**存储法线向量。 - RGB值 → 切线空间下的法线方向$(x,y,z)$ - 转换公式:$\vec{N}_{\text{new}} = 2 \times \text{RGB} - 1$[^3][^4]。 2. **计算过程** - **Bump贴图**: 需实时计算高度场的梯度(偏导数)来扰动原始法线,计算开销较高[^2][^4]。 ```glsl // GLSL伪代码示例 float height = texture(bumpMap, uv).r; vec2 gradient = vec2(dFdx(height), dFdy(height)); vec3 newNormal = normalize(N + gradient.x * T + gradient.y * B); ``` - **法线贴图**: 直接读取存储的法线向量,仅需向量归一化操作,效率更高[^3][^4]。 3. **视觉局限性** - 两者均**不改变几何体轮廓**(例如边缘仍保持平滑)[^1][^2]。 - 当视角表面平行时,凹凸假象易穿帮(如图)[^2]: ![视角穿帮示意图](https://example.com/normal-map-limitation.png) *图示:法线贴图在平视角度下表面实际平坦[^2]* --- #### **应用场景对比** | **特性** | Bump贴图 | 法线贴图 | |------------------|------------------------------|------------------------------| | **细节精度** | 低(依赖灰度梯度近似) | 高(精确存储法线方向)[^3] | | **硬件要求** | 低(适合移动端/旧硬件) | 需支持切线空间计算[^2] | | **资源开销** | 纹理小(单通道) | 纹理大(三通道) | | **典型用途** | 石材纹理、布褶模拟(远景) | 角色皮肤、金属划痕(近景)[^3] | | **生成方式** | 可由高度图转换 | 需烘焙高模或算法生成[^1] | --- #### **技术演进关系** 1. **Bump贴图法线贴图** - 法线贴图是bump贴图的升级,直接存储预计算的法线向量,避免实时梯度计算[^1][^3]。 - 法线贴图能表现**方向性细节**(如非垂直凹凸),而bump贴图仅支持垂直高度变化[^4]。 2. **进阶技术** - **置换贴图 (Displacement Map)**: 真正修改顶点位置,解决轮廓穿帮问题(但需细分曲面支持)[^1]。 - **视差贴图 (Parallax Map)**: 通过UV偏移模拟深度,改善平视角度的视觉缺陷[^2]。 --- #### **性能优化** - **实时渲染首选法线贴图**: 现代引擎(如Unity/Unreal)默认使用法线贴图,因其在同等细节下性能更优[^2][^3]。 - **混合方案**: - 远景物体:Bump贴图(节省带宽) - 近景物体:法线贴图 + 视差/置换贴图[^2] > 引用总结: > Bump贴图通过灰度扰动法线,法线贴图直接存储法线向量,两者均属"视觉欺骗"技术。法线贴图因精度和效率优势成为主流,但受限于几何轮廓不变性[^1][^2][^3][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值