概念
法线纹理
法线方向的分量[-1, 1]和像素的分量[0,1]存在映射关系,normal = pixel * 2 - 1。
法线变换
在不同变换中使用的顶点变换矩阵Ma,不能直接用于顶点的法线,两个变换矩阵的关系为,新的矩阵为Ma的转置逆矩阵。
切线空间
通常用于法线映射,存储模型顶点的法线,模型的每个顶点,它都有一个属于自己的切线空间 这个切线空间的原点就是该顶点本身,而轴是顶点的法线方向 (n) 轴是顶点的切线方向 (t),可由法线和切线叉积而得,也被称为是副切线 bitangent, b) 或副法线对应的纹理的叫切线空间的法线纹理。
切线空间下的法线纹理看起来几乎全部是浅蓝色的,这种法线纹理其实就是存储了每个点在各自的切线空间中的法线扰动方向,如果一个点的法线方向不变,那么在它的切线空间中 ,新的法线方向就是 z 轴方向,即值为(0, 0, 1),经过映射后存储在纹理中就对应了 RGB(0.5, 0.5, 1) 浅蓝色。
在切线空间下计算光照模型
- 【顶点着色器】中把【视角方向】和【光照方向】从模型空间变换到切线空间中,即我们需要知道从模型空间到切线空间的变换矩阵。
- 在顶点着色器中完成上述两个方向从模型空间变换到切线空间。
- 片元着色器在切线空间下进行光照计算。
遮罩纹理
让一些地方免于修改,比如不是所有地方都用同样的高光反射。使用遮罩纹理的一般流程:
- 通过采样得到遮罩纹理的纹素值。
- 使用其中某个(或某几)通道的值(例如 texel.r 来与某种表面属性进行相乘,这样,当该通道的值为0时,可以保护表面不受该属性的影响。
效果
使用遮罩纹理后,只有部分的应用了高光
Shader
Shader "Example/MaskTexture"
{
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 //控制凹凸程度的,当它为0时,意味着该法线纹理不会对光照产生任何影响。
_SpecularMask ("Specular Mask", 2D) = "white" {}
_SpecularScale ("Specular Scale", Float) = 1.0
_Specular ("Specular", Color) = (1,1,1,1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
//TANGENT义来描述 float4 类型的 tangent 变量,以告诉 Unity 顶点的切线方向填充到 tangent 变量中
//tangent.w 分量来决定切线空间中的第三个坐标轴一副切线的方向性。
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask;
float _SpecularScale;
fixed4 _Specular;
float _Gloss;
float4 _LightColor0;
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// 对顶点的纹理坐标进行变换,最终得到纹理坐标
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
// UnityCG.cginc内置矩阵,模型空间下切线方向,副切线方向和法线方向按行排列来得到的,从模型空间到切线空间的变换矩阵
TANGENT_SPACE_ROTATION;
// 在切线空间下计算光照模型
// 变换光线方向从模型空间到切线空间
// 内置函数ObjSpaceLightDir得到模型空间下的光照方向
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
// 变换视角方向从模型空间到切线空间
// 内置函数ObjSpaceViewDir得到模型空间下的视角方向
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//切线空间下的光照方向
fixed3 tangentLightDir = normalize(i.lightDir);
//切线空间下的视角方向
fixed3 tangentViewDir = normalize(i.viewDir);
//先利用tex2D 对法线纹理 BumpMap 进行采样,用内置函数UnpackNormal得到正确的法线方向
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
//
tangentNormal.xy *= _BumpScale;
//saturate, 返回0~1的函数
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);
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
部分理解
float4 _MainTex_ST
在 Unity 中,我们需要使用【纹理名_ST】的方式来声明某个纹理的属性。其中,ST 是缩放scale和平移translation的缩写。_MainTex_ST可以让我们得到该纹理的缩放和平移(偏移)值 _MainTex_ST.xy 存储的是缩放值,而_MainTex_ST.zw 存储的是偏移值。
SV_Target
是 HLSL 中的个系统语义,它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标 (render target) 中,这里将输出到默认的帧缓存中。
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
高光反射部分使用了Blinn-Phong模型:
参考
《Unity Shader入门精要》冯乐乐