「UnityShader笔记」06. 基于法线贴图实现凹凸纹理效果
作者:互联网
Part1.代码逐段解析
Properties
{
_Color("Colot 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
}
在属性块,我们需要定义颜色值,主纹理,法线纹理,凹凸系数(用于控制凹凸的程度),高光颜色,高光聚集程度
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT; //注意是切向量是四维的,还有一维表示计算副切线坐标轴的正负方向
float4 texcoord : TEXCOORD0;
};
在顶点着色器的输入结构体中,我们获取了顶点位置,顶点法线,顶点切线和纹理uv坐标
注意顶点切线是float4类型的,w维度代表了副切线的朝向
struct v2f{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 lightDir : TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
在从顶点着色器到片元着色器的通信结构体中,我们定义了裁剪空间的坐标,纹理采样坐标,切线空间的光照方向,切线空间的观察方向
注意纹理采样坐标实际上包含了颜色纹理与法线纹理的采样坐标,刚刚好占用4个分量
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); //将顶点位置变换到裁剪空间
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; //计算在纹理上的采样点,存储在uv的前两个分量中
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw; //计算在法线纹理上的采样点,存储在uv的后两个分量中
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w; //计算副切向量,记住要乘上tangent的w分量来赋予方向性
float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal); //将代表x轴的切线向量,表示y轴的副切线向量,表示z轴的法线方向按行排列,得到从切线空间到模型空间的变换矩阵
o.lightDir = mul(rotation, normalize(ObjSpaceLightDir(v.vertex))).xyz; //先获取顶点处的光照向量,然后左乘变换矩阵,将光照方向变换到切线空间
o.viewDir = mul(rotation, normalize(ObjSpaceViewDir(v.vertex))).xyz; //先获取顶点处的观察方向,然后左乘变换矩阵,将观察方向变换到切线空间
return o;
}
在顶点着色器中,我们主要的工作是将顶点变换到裁剪空间,完成纹理尺度、偏移变换,构建切线空间坐标系,并计算从模型空间到切线空间的变换矩阵,并用此矩阵获取切线空间下的光照方向和观察方向
在计算副切向量时,需要乘上切向量的w分量,这里记录了副切线量朝向的信息
计算出副切线向量后,将代表x轴的切线向量,表示y轴的副切线向量,表示z轴的法线方向按行排列,得到从切线空间到模型空间的变换矩阵,之所以是按行排列,是因为这三个向量按列排列实际上是从切线空间变换到模型空间的矩阵,并且注意到这个矩阵实际上是一个正交矩阵,正交矩阵的逆矩阵等于其转置(刚好是按行排列),而这个逆矩阵显然就是从模型空间变换到切线空间的矩阵
得到变换矩阵后,剩下的工作就十分简单了,我们只需要将变换矩阵左乘以单位化后的光照方向、观察方向,就可以得到切线空间的光照向量和观察向量了
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 = UnpackNormal(packedNormal);//对法线纹理进行采样后,将x,y反变换到(-1,1)的范围(由unity内置函数自动完成)
tangentNormal.xy *= _BumpScale; //乘以凹凸系数
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy))); //计算出z坐标轴的数值
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); //按Blinn-phong模型计算高光项
return fixed4(ambient + diffuse + specular, 1.0); //返回最终颜色值
}
在片元着色器中,我们需要先将顶点着色器中计算的光照向量和观察向量单位化,然后对法线贴图进行采样
注意到法线信息在法线贴图中保存时,其x、y取值被映射到了(0,1)范围,所以我们需要一次反映射操作,使得其映射回正常的(-1,1)范围,unity也为我们准备了实现该功能的内置函数UnpackNormal(),可以直接调用
采样完成后,我们还需要乘以预先定义的凹凸系数,来控制表面凹凸的程度
在得到纹理法线最终的x,y值后,我们需要手动计算z值,由于该法向量为单位向量,我们直接按照公式z = √(1 - (x^2 + y^2)) 计算即可
这样我们就得到了可用于光照计算的表面法线,接下来按照光照模型直接进行光照计算即可
Par2.完整代码
Shader "Chapter7/bumpTex_Tangent"
{
Properties
{
_Color("Colot 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;
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;
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
o.lightDir = mul(rotation, normalize(ObjSpaceLightDir(v.vertex))).xyz;
o.viewDir = mul(rotation, normalize(ObjSpaceViewDir(v.vertex))).xyz;
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 = 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 "Diffuse"
}
Part3.效果图
不使用凹凸贴图的效果
当凹凸系数 = -1时的效果
当凹凸系数 = 1时的效果
标签:贴图,normalize,06,切线,fixed3,UnityShader,xy,tangentNormal,向量 来源: https://www.cnblogs.com/MuTsuKi/p/16339428.html