【Unity Shader】(6)凹凸映射 实现材质的 法线贴图 和 高度图(Normal Map And Height Map)

在Unity Shader中用法线贴图和高度图来实现凹凸映射


1、凹凸映射概念

凹凸映射,在不改变顶点位置的前提下,修改模型表面的法线方向,为模型提供更多的细节。

2、凹凸映射的 2 种方法

  • 高度纹理(Height Map)

使用一张高度纹理来模拟表面位移(Displacement),然后得到一个修改后的法线值。此方法也叫做高度映射(Height Mapping)
颜色越浅,越向外凸;颜色越深,越向内凹。能明确表面的凹凸信息,缺点是计算复杂。

  • 法线纹理(Normal Map)

使用一张法线纹理来直接存储表面法线。此方法也叫做法线映射(Normal Mapping)。用于存储表面的法线向量,法线向量的取值范围为 [ -1 , 1] 。但是像素分量的取值范围是 [ 0 , 1],因此需要进行以下两个映射:

【Unity Shader】(6)凹凸映射 实现材质的 法线贴图 和 高度图(Normal Map And Height Map)_第1张图片

  • 贴图属性

  • UnpackNormal是unity内置的函数(在后面⑥定义片元着色器中会出现)。当我们把贴图纹理设置为Normal map类型时,该函数可以得到正确的法线方向。
    不仅如此,Unity还可以根据不同平台来调整Normal的细节,使用UnpackNormal函数针对不同压缩格式对法线纹理进行正确采样。(目前存在DXT1、DXT5、DXT5nm的格式,DXT3以及被弃用——Tech-Artists)
    可以在UnityCG.cginc之中找到UnpackNormal的具体定义:

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
    return packednormal.xyz * 2 - 1;
#else
    return UnpackNormalmapRGorAG(packednormal);
#endif
}

代码中可以看到我们可爱的Unity并不能识别除了DXT5nm以外的贴图格式,因此翻阅官方文档然后直接Ctrl-F搜索DXT得到如下结果:

【Unity Shader】(6)凹凸映射 实现材质的 法线贴图 和 高度图(Normal Map And Height Map)_第2张图片
说明在unity之中可以有2个选择,包括XYZ和DXT5nm,且更改法线编码时,最好直接在Unity的项目设置中,而不是在CG Shader代码中,否则解码成本会增加。

  • 根据下图可以看到,如果我们把类型设置为Normal map,将会出现Create from Grayscale的选项。这个选项的主要作用是可以将我们的先前讲到的 高度图 转换为 法线贴图,方便我们进行法线处理。
    不仅如此,Unity给予了我们两种滤波器:
    ①Sharp——使用Sobel滤波来生成法线
    ②Smooth



3、法线纹理的坐标空间

  • 模型空间的法线纹理(Object-Space Normal Map)

对于先前的纹理映射和纹理贴图之中,我们可以直到,要在CG语言中实现相应效果,不可或缺的是纹理的坐标空间(是ObjectSpace还是WorldSpace?)。而对于法线纹理的坐标空间来说,有个直接点的想法是——将修改后的模型空间中的表面法线存储在一张纹理中。(这样一来就可以结合前面“纹理贴图”一样实现凹凸效果。代码部分会加以证明。)这种纹理被称为——模型空间的法线纹理(Object-Space Normal Map)。

  • 模型顶点的切线空间(Tangent Space)

然而,实际上我们真正使用的是——模型顶点的切线空间(Tangent Space)。具体解释如下:
对于模型的每一个顶点,都有属于自己的切线空间,这个切线空间的原点就是顶点本身。
Z轴是顶点的法线方向 n
X轴是顶点的切线方向 t
Y轴是由顶点的切线和法线叉积而得

【Unity Shader】(6)凹凸映射 实现材质的 法线贴图 和 高度图(Normal Map And Height Map)_第3张图片



代码开始

在切线空间下计算

① 在Properties属性下添加Bump法线纹理的属性,以及用于控制凹凸程度的属性

Properties{
	_Color("Base Color",Color) = (1,1,1,1)
	_MainTex("Main Tex",2D) = "white" {}
	_BumpMap("Normal Map", 2D) = "bump"{}
	_BumpScale ("Bump Scale",Float) = 1.0
	_Specular("Specular",Color) = (1,1,1,1)
	_Gloss("Gloss",Range(8.0,256)) = 20
}

_BumpMap,默认值可以使用“Bump”——Unity内置法线纹理。
_BumpScale为0时,意味着该法线纹理不会起作用。

② 定义Tags

SubShader{
	Pass{
		Tags{"LightMode"="ForwardBase"}
  • 声明顶点着色器和片元着色器 和 光照包含

#pragma vertex vert
#pragma pragment prag
#include "Lighting.cginc"

③ 为Properties中的属性定义类型

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;

根据上面的代码,可以看到,_BumpMap和普通纹理类似,同样也声明了一个_ST变量,这也正说明我们前面的理解是正确的——这样一来就可以结合前面“纹理贴图”一样实现凹凸效果。

④ 定义我们的a2v和v2f结构体

struct a2v {
	float4 vertex : POSITION;
	float3 normal : NORMAL;
	float4 tangent : TANGENT;
	float4 texcoord : TEXCOORD0;
};

struct v2f {
	float4 pos : SV_POSITION;
	float4 uv : TEXCOORD0;
	float3 lightDir : TEXCOORD1;
	float3 viewDir : TEXCOORD2;
};

这里的TANGENT就是我们所说的切线。Unity会像传送POSITION一样,把TANGENT所包含的每个顶点的切线方向填充到tangent之中,但是需要注意的是,和法线方向的normal不同,tangent的类型是float4,而非float3.是因为其额外多了一个tangent.w分量来决定切线空间的第三个坐标轴——副切线的方向性。

⑤ 定义顶点着色器

v2f vert(a2v v) {
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);

	o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
	o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

	fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
	fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
	fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

	float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);

	o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex));
	o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex));

	return o;
}

⑥ 定义片元着色器

fixed4 frag(v2f i) : SV_Target {
	fixed3 tangentLightDir = normalize(i.lightDir);
	fixed3 tangentViewDir = normalize(i.viewDir);
	
	fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
	fixed3 tangentNormal;
	
	tangentNormal = UnpackNormal(packedNormal);
	tangentNormal.xy *= _BumpScale;
	tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
	
	fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
	
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
	
	fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
	
	fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
	
	return fixed4(ambient + diffuse + specular, 1.0);
}

⑦ 整体代码:

Shader "LeonShader/Shader_7_2_HeightMap"{
	Properties{
		_Color("Base Color",Color) = (1,1,1,1)
		_MainTex("Main Tex",2D) = "white" {}
		_BumpMap("Normal Map", 2D) = "bump"{}
		_BumpScale("Bump Scale",Float) = 1.0
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256)) = 20
	}
	SubShader{
		Pass {
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float4 _BumpMap_ST;
			float _BumpScale;
			fixed4 _Specular;
			float _Gloss;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};

			struct v2f {
				float4 pos : SV_POSITION;
				float4 uv : TEXCOORD0;
				float3 lightDir: TEXCOORD1;
				float3 viewDir : TEXCOORD2;
			};


			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
				fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
				fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

				float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);

				o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex));
				o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex));

				return o;
			}

			fixed4 frag(v2f i) : SV_Target {
				fixed3 tangentLightDir = normalize(i.lightDir);
				fixed3 tangentViewDir = normalize(i.viewDir);

				fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
				fixed3 tangentNormal;

				tangentNormal = UnpackNormal(packedNormal);
				tangentNormal.xy *= _BumpScale;
				tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

				fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);

				return fixed4(ambient + diffuse + specular, 1.0);
			}

			ENDCG
		}
	}
	FallBack "Specular"
}

⑧效果图

【Unity Shader】(6)凹凸映射 实现材质的 法线贴图 和 高度图(Normal Map And Height Map)_第4张图片



在世界空间下计算

你可能感兴趣的:(Shader,TA,untiy,着色器,技术美术,Unity,Shader,TA,Unity)