UnityShader入门——法线贴图
高度映射,模型空间下的法线映射,切线空间下的法线映射

贴图纹理和法线之间的转换
知识:向量的分量就是 向量的点表示图中,按照逗号进行分割数字
高度映射
使用像素的灰度值来表示该位置的深度
缺点是需要通过一系列的计算来变换为可以使用的法线角度、不利于美术的处理
模型空间下的法线映射
坐标系的原点是模型坐标系的原点
颜色很多,每个点的向量都有可能不同,最后呈现的结果就是花里胡哨的。
UV点和模型的每一个顶点都是一一对应的,不方便移动改变,有些高级的特效或者粒子特效需要移动UV贴图来实现。
制作简单
一般在3S贴图中可能遇见
优点
实现简单,更加直观。计算量少。在纹理的边角部分,缝隙较少,可提供平滑的边界。是因为所有法线都是在同一坐标空间中,可以在边角处通过插值进行平滑变换。
切线空间下的法线映射
叉乘:
向量叉乘是对两个三维空间中的向量进行的一种二元运算,其结果是一个新的向量,这个向量垂直于由原始两个向量所定义的平面。
对于两个三维向量a(x1, y1, z1)和b(x2, y2, z2),它们的叉乘c = a × b可以表示为:
c = (y1z2 - y2z1, z1x2 - z2x1, x1y2 - x2y1)
请自行百关于叉乘的详细信息
优点
自由度高。切线空间下的法线纹理是相对法线纹理,即便在一个不同的网格上也可以得到一个合理的结果。可进行UV动画。可以通过移动一个纹理的UV坐标来实现一个凹凸移动的效果可重用法线纹理。可压缩。切线空间下,Z轴方向总是正方向,可以只存储XY轴,推导得到Z轴。
切线空间中,原来在模型空间下的法线坐标的法线是(0,0,1),这个值转换到通过 切线到颜色的转换后结果是 (0.5,0.5,1),在视觉上表现出来浅蓝色。最终结果大面积呈现为淡蓝色,说明大部分新法线还是原来的法线。
实现方式
在顶点着色器中进入到每个顶点的切线坐标系下计算出真实的法线,再交给片元着色器进行染色
性能比较好,顶点着色器的计算要比片元着色器少
在片元着色器将所有法线转换到世界坐标下,然后再去进行计算
进入切线空间
旋转矩阵的求解
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| Shader "Unlit/01" { Properties { _MainTex ("Texture", 2D) = "white" {} _Blutom("Blutom",2D) = "white" {} _Diffse("Diff",color) = (1,1,1,1) _Specular("Specular",color) = (1,1,1,1) _Gloss("Gloss",Range(1,256)) = 20 _NormalStrength("NormalStrength",float) =10 } SubShader { Tags { "RenderType"="Opaque" } LOD 100
Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
#include "UnityCG.cginc" #include "UnityLightingCommon.cginc "
struct v2f { float4 vertex : SV_POSITION; float2 uv: TEXCOORD0; float3 lightDir: TEXCOORD1; float3 viewDir : TEXCOORD2; float2 normalUv : TEXCOORD3; };
sampler2D _MainTex; float4 _MainTex_ST;
sampler2D _Blutom; float4 _Blutom_ST;
float _NormalStrength;
float4 _Diffse; float4 _Specular; int _Gloss;
v2f vert (appdata_tan v) { v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz; o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex); o.normalUv =TRANSFORM_TEX(v.texcoord,_Blutom); return o; }
fixed4 frag (v2f i) : SV_Target {
float3 environmentLightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
float3 viewDir = normalize(i.viewDir);
float3 lightDir = normalize(i.lightDir);
float4 packedNormal = tex2D(_Blutom,i.normalUv);
float3 tangentNormal = UnpackNormal(packedNormal); tangentNormal *= NormalStrength; float3 albedo = tex2D(_MainTex,i.uv).rbg; float3 diffue = albedo * _LightColor0.rgb * _Diffse.rgb * (dot(tangentNormal,lightDir)* 0.5 + 0.5);
float3 halfView = normalize(viewDir + lightDir); float3 phong = _LightColor0.rgb * _Specular * pow(max(0,dot(tangentNormal,halfView)),_Gloss); float3 resColor = diffue + phong;
float4 col = float4(resColor,1); return col; } ENDCG } } }
|
提取到世界空间
旋转矩阵的大致翻转过程和进入切线空间的一致
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
| Shader "Unlit/02" { Properties { _MainTex ("Texture", 2D) = "white" {} _Blutom("Blutom",2D) = "white" {} _Diffse("Diff",color) = (1,1,1,1) _Specular("Specular",color) = (1,1,1,1) _Gloss("Gloss",Range(1,256)) = 20 _NormalStrength("NormalStrength",float) =10 } SubShader { Tags { "RenderType"="Opaque" } LOD 100
Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
#include "UnityCG.cginc" #include "UnityLightingCommon.cginc "
struct v2f { float4 vertex : SV_POSITION; float4 uv: TEXCOORD0; float4 Ttiw1 : TEXCOORD1; float4 Ttiw2 : TEXCOORD2; float4 Ttiw3 : TEXCOORD3; };
sampler2D _MainTex; float4 _MainTex_ST;
sampler2D _Blutom; float4 _Blutom_ST;
float _NormalStrength;
float4 _Diffse; float4 _Specular; int _Gloss;
v2f vert (appdata_tan v) { v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
TANGENT_SPACE_ROTATION;
float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; float3 worldNormal = UnityObjectToWorldNormal(v.normal); float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); float3 subTangent = cross(worldNormal,worldTangent)* v.tangent.w;
o.Ttiw1 = float4(worldTangent.x,subTangent.x,worldNormal.x,worldPos.x); o.Ttiw2 = float4(worldTangent.y,subTangent.y,worldNormal.y,worldPos.y); o.Ttiw3 = float4(worldTangent.z,subTangent.z,worldNormal.z,worldPos.z);
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex); o.uv.wz =TRANSFORM_TEX(v.texcoord,_Blutom); return o; }
fixed4 frag (v2f i) : SV_Target { float3 environmentLightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
float3 worldPos = float3(i.Ttiw1.w,i.Ttiw2.w,i.Ttiw3.w);
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
float4 packedNormal = tex2D(_Blutom,i.uv.wz);
float3 tangentNormal = UnpackNormal(packedNormal); tangentNormal *= _NormalStrength; float3 worldiNormal = normalize(float3(dot(i.Ttiw1.xyz,tangentNormal), dot(i.Ttiw2.xyz,tangentNormal), dot(i.Ttiw3.xyz,tangentNormal)) ); float3 albedo = tex2D(_MainTex,i.uv.xy).rbg; float3 diffue = albedo * _LightColor0.rgb * _Diffse.rgb * (dot(worldiNormal,lightDir)* 0.5 + 0.5);
float3 halfView = normalize(viewDir + lightDir); float3 phong = _LightColor0.rgb * _Specular * pow(max(0,dot(worldiNormal,halfView)),_Gloss); float3 resColor = diffue + phong;
float4 col = float4(resColor,1); return col; } ENDCG } } }
|
法线贴图设置
Unity对于导入的图片素材可以进行一个分类,这里展示Unity中对于将图片自动转换为高度映射的法线贴图的选项框
