[unity shader] 法线纹理 - maggie03230/maggie03230.github.io GitHub Wiki
标准shader 法线贴图使用方法 https://docs.unity3d.com/Manual/StandardShaderMaterialParameterNormalMap.html
简介&原理:
凹凸映射有两种方法,其一为用高度纹理(height map),模拟表面位移,得到修改后的法线值;其二为使用法线纹理(normal map),直接存储表面法线。
法线纹理 一条法线是一个三维向量,一个三维向量由x, y, z等3个分量组成,在法线贴图中,把(x, y, z)当作RGB3个颜色的值存储,并将其每个分量映射到[-1, 1]。例如,对于x, y, z各有8位的纹理,(0, 0.5, 1)表示法向量(-1, 0, 1)。
公式: pixel = (normal + 1) / 2
=> normal = pixel * 2 -1
计算方法有二:
一:在切线空间下进行光照计算,需将光照、视角切换到切线空间。
二:在世界空间下进行光照计算,需把采样得到的法线方向变换到世界空间下,再和世界空间下的光照和视角方向进行计算。
法一效率优,法二因为是在世界空间,通用性强。
Shader过程及解释
注:代码来自《Unity Shader入门精要》第7章 部分注解为笔者注,若有错误那一定是笔者的错hhh
输入参数:
Properties {
//叠加颜色
_Color ("Color Tint", 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
}
着色输出结构体:v2f
保存切线空间到世界空间的变换矩阵
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
};
根据顶点信息得到变换矩阵
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//把_MainTex和_BumpMap放到一个插值寄存器去
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; //xy分量保存_MainTex的纹理坐标
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw; //zw分量保存_BumpMap的纹理坐标
float3 worldPos = mul(_Object2World, v.vertex).xyz; //顶点的世界坐标
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); //世界坐标系下的法向量
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); //世界坐标系下的切线
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
//世界坐标下的次法线(跟法线和切线垂直的那个,因为有两个,定义为tangent.w方向)
// 由于插值寄存器只能存储float4大小变量,所以把4个fixed3转换成了3个float4
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
根据顶点信息逐像素计算此像素光照后颜色
此处原理为:在没有法线纹理情况下,光线本是看上去是按照原法线方向A反射,有了以后则在每个像素点叠加法线得到法线B,使用B计算颜色,这些颜色的变化使得原本的平面看上去像有弯曲
在计算完法向量之后关于漫反射等的计算均使用新法向量即可
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w); //把定点坐标变回来
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos)); //世界坐标系下的光照方向的单位向量
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); //世界坐标系下的视角方向的单位向量
// 在_BumpMap中根据uv获取到纹理
// 用Unity自带方法UnpackNormal(即:*2-1)获得已转换好的法向量
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump.xy *= _BumpScale; //bump的xy放大
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy))); //bump的z放大
// 将bump从切线空间转换到世界空间
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
//主材质 * 表面颜色
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
//漫反射
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
//下面计算高光
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
完整代码:
Shader "Custom/NormalMapFromBook" {
Properties {
//叠加颜色
_Color ("Color Tint", 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;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
};
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//把_MainTex和_BumpMap放到一个插值寄存器去
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; //xy分量保存_MainTex的纹理坐标
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw; //zw分量保存_BumpMap的纹理坐标
float3 worldPos = mul(_Object2World, v.vertex).xyz; //顶点的世界坐标
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); //世界坐标系下的法向量
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); //世界坐标系下的切线
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; //世界坐标下的次法线
// Compute the matrix that transform directions from tangent space to world space
// Put the world position in w component for optimization
// 由于插值寄存器只能存储float4大小变量,所以把4个fixed3转换成了3个float4
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get the position in world space
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w); //把定点坐标变回来
// Compute the light and view dir in world space
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos)); //世界坐标系下的光照方向的单位向量
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); //世界坐标系下的视角方向的单位向量
// Get the normal in tangent space
// 在_BumpMap中根据uv获取到纹理
// 用Unity自带方法UnpackNormal(即:*2-1)获得已转换好的法向量
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump.xy *= _BumpScale; //bump的xy放大
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy))); //bump的z放大
// Transform the narmal from tangent space to world space
// 将bump从切线空间转换到世界空间
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
//主材质 * 表面颜色
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
//漫反射
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
//下面计算高光
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}