开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,[点击查看活动详情]
前言
在之前的文章中,介绍了一张纹理图片是如何被贴到模型上的,其中纹理的作用是替代物体的漫反射颜色,而纹理的另一种常见应用是凹凸映射。
什么是凹凸映射
通常一个模型的面数会直接影响其渲染时间和效率,因此选用低面数的模型要比高面数的模型在渲染时效率高,但是低面数的模型会失去很多细节,比如光影效果。那么有没有什么办法既可以照顾到渲染效率又不损失很多细节呢?答案是:凹凸映射技术。而本篇要介绍的法线贴图就属于凹凸映射技术的一种,其可以不改变低模的空间结构,但是又可以使低模在一定程度上保留高模的光影效果。
法线贴图
法线贴图中存储的是物体表面的法线信息,法线的坐标信息取决于我们是基于哪个坐标空间构建的,例如世界空间下还是模型空间下,不过一般我们经常用的是切线空间下的法线贴图,其具有自由度高、可以进行UV动画,可重用,可压缩等优点。需要说明的是对其采样得到的rgb通道的数值是模型的法向量转换到切空间后的坐标经过单位化,再做一个0-1的映射,然后乘以255得出的颜色值。(下图右侧为切线空间下的法线纹理)
光影计算
上面提到我们经常使用的是切线空间下的法线贴图,所以采样得到的也就是切线空间下的法线信息,要拿来做光影计算就要求我们将光照模型中需要的其他信息:例如光方向、视角方向、反射方向、顶点坐标信息都变换到同一坐标系下,例如可以将法线信息变换到世界空间下,在和世界空间下的其他向量参与运算。也可以将其他向量变换到切线空间下再与法线进行运算。
在Unity中的具体实现(转换到世界空间下进行计算)
- 1、在Project面板右键创建一个Unlit Shader,将其命名为NormalMap。
- 2、选中第一步创建的NormalMap,右键创建一个Material。
- 3、在Hierarchy面板创建一个Sphere,并将第2步创建的Material拖拽赋值到Sphere上。
- 4、使用脚本IDE打开第一步创建的Shader,添加代码如下:
Shader "Unlit/NormalMap"
{
Properties {
_Color ("基础色", color) = (1.0, 1.0, 1.0, 1.0)
_MainTex("纹理贴图", 2D) = "white" {}
_NormalMap("法线贴图", 2D) = "bump" {}
_SpecularPower ("高光次幂", Range(0, 10)) = 5
_SpecularColor ("高光颜色", color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader {
Tags {
"RenderType"="Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
float3 _Color;
float _SpecularPower;
float3 _SpecularColor;
sampler2D _MainTex;float4 _MainTex_ST;
sampler2D _NormalMap; float4 _NormalMap_ST;
struct appdata {
float4 vertex : POSITION;
float4 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 posWS : TEXCOORD0;
float2 uv : TEXCOORD1;
float3 nDirWS : TEXCOORD2;
float4 tDirWS : TEXCOORD3;
};
v2f vert (appdata v) {
v2f o;
o.uv = v.uv;
o.pos = UnityObjectToClipPos( v.vertex );
o.posWS = mul(unity_ObjectToWorld, v.vertex);
o.nDirWS = normalize(UnityObjectToWorldNormal(v.normal));
o.tDirWS = float4(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz, v.tangent.w);
return o;
}
float4 frag(v2f i) : COLOR {
float3 nDirTS = UnpackNormal(tex2D(_NormalMap, TRANSFORM_TEX(i.uv, _NormalMap)));
float3 nDir = normalize(i.nDirWS);
float3 tDir = normalize(i.tDirWS - dot(i.tDirWS, nDir) * nDir);
float3 bDir = cross(nDir, tDir) * i.tDirWS.w;
float3x3 TBN = float3x3(tDir, bDir, nDir);
float3 nDirWS = normalize(mul(nDirTS, TBN));
float3 lDirWS = _WorldSpaceLightPos0.xyz;
float3 vrDirWS = reflect(-normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz), nDirWS);
float lambert = max(0.0, dot(nDirWS, lDirWS));
float phong = pow(max(0.0, dot(vrDirWS, lDirWS)), _SpecularPower);
float3 diffuse = tex2D(_MainTex,TRANSFORM_TEX(i.uv, _MainTex)).rgb * _Color.rgb * lambert;
float3 specular = _SpecularColor * phong;
return float4(diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
- 5、将准备好的漫反射贴图和法线贴图拖拽赋值到材质面板。
- 6、查看效果。
最后
需要注意的是法线贴图并没有改变模型的顶点位置,而是一种视觉效果,在模型的边缘处就可以看出来。OK,本篇就到这里。