1. 紋理映射
每一張紋理可以看作擁有一個屬于自己的2D坐標空間,其橫軸用U表示,縱軸用V表示,因此也稱為UV坐標空間。
UV空間的坐標范圍為[0,0]到[1,1],在Unity中,UV空間也是從左下到右上,即紋理的左下角對應的UV坐標為[0,0],紋理右上角對應的UV坐標為[1,1]
在美術導出模型資源時,會通過UV展開將模型每個頂點對應的UV坐標存儲到頂點信息中。渲染時通過頂點(或片元)的UV坐標映射到紋理上的某一點,這樣就可以通過采樣每一個頂點(或片元)對應的紋理上的顏色將紋理顯示出來。
2. 紋理基礎設置
以Unity 2021為例,一張紋理的Inspector面板大致如下
其中紅框部分是本節主要關注的部分
-
Generate Mip Maps
- 勾選后會提前進行濾波,生成不同采樣等級的Mip Map紋理
- 渲染時根據屏幕像素對應的紋理范圍從對應等級的Mip Map紋理中進行采樣
- 由于生成了新的紋理,因此會占用更多的內存空間
-
Wrap Mode
UV坐標空間自身的范圍雖然只有0到1,但實際采樣時,頂點映射的UV坐標可能超過這個范圍,WrapMode屬性決定了采樣的UV坐標超過[0, 1]范圍時的處理方式- Repeat:重復采樣,即超過范圍時舍棄整數部分只按小數部分采樣,比如(1.1, 1.2)實際會按照(0.1, 0.2)采樣
- Clamp:超過[0, 1]的部分延用相應邊緣的值(0或者1)
-
Filter Mode
決定紋理采樣的濾波方式- Point:就近點采樣
- Bilinear:雙線性插值
- Trilinear:三線性插值
關于MipMap和濾波的原理可以參照【Unity Shader入門精要 第7章】基礎紋理補充內容:MipMap原理
3. 在Shader中使用紋理
- 創建Chapter_7_BaseTexture_Mat 作為測試材質
- 創建Chapter_7_BaseTexture作為測試shader,并賦給Chapter_7_BaseTexture_Mat 材質
- 場景中創建膠囊體,將Chapter_7_BaseTexture_Mat 材質賦給膠囊體
- 場景中添加一盞平行光,并調整平行光角度
在Shader中,我們通過對紋理采樣得到的顏色來代替之前固定的漫反射顏色,并在片元著色器中進行逐像素的光照計算,得到最終的效果:
- 在Properties中添加屬性 _MainTex(“MainTex”, 2D) = “white”{} 用于設置紋理,此時選中膠囊體可在Inspector面板中看到如下紋理設置選項
- Tiling:平鋪數量,可以理解為反向的縮放度,值越大單位空間平鋪數量越多,相當于對紋理縮小,反之相當與放大
- Offset:偏移
- 在CG代碼段中添加與屬性同名的變量 _MainTex來使用面板中設置的紋理,其類型為 sampler2D
- 頂點著色器中輸入的uv坐標是模型導出時頂點對應的uv坐標,為了能夠得到正確的紋理采樣結果,除了考慮頂點自身的uv坐標外,還需要考慮面板中對要采樣的紋理設置的縮放和偏移,這部分信息可通過在Shader中聲明 XXX_ST 的變量獲得,其中XXX為紋理屬性的名字,當前Shader中也就是 _MainTex_ST,該變量類型為float4,其中xy表示縮放,zw表示平移
- 通過 input.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw 即可得到實際采樣使用的uv坐標,也可以通過內置的TRANSFORM_TEX(_MainTex, input.uv)方法獲得,此時引擎會自己使用名為 _MainTex_ST 的變量進行上述計算,因此即使使用內置方法計算uv也需要聲明_ST變量
- 最后在著色器中通過 tex2D(_MainTex, i.uv) 方法即可對紋理進行采樣獲得顏色,我們以該顏色作為物體自身的漫反射顏色系數帶入漫反射光照計算
Shader如下:
Shader "MyShader/Chapter_7/Chapter_7_BaseTexture"
{Properties{_MainTex("MainTex", 2D) = "white"{}_Specular("Specular", Color) = (1,1,1,1)_Gloss("Gloss", Range(1.0, 256.0)) = 10}SubShader{Pass{Tags {"LightMode" = "ForwardBase"}CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"struct a2v{float4 vertex : POSITION;float3 normal : NORMAL;float2 uv : TEXCOORD0;};struct v2f{float4 vertex : SV_POSITION;float2 uv : TEXCOORD0;float3 worldPos : TEXCOORD1;float3 worldNormal : TEXCOORD2;};sampler2D _MainTex;float4 _MainTex_ST;fixed4 _Specular;half _Gloss;v2f vert(a2v i){v2f o;o.vertex = UnityObjectToClipPos(i.vertex);o.worldPos = mul(unity_ObjectToWorld, i.vertex);o.worldNormal = mul(i.normal, (float3x3)unity_WorldToObject);o.uv = TRANSFORM_TEX(i.uv, _MainTex);return o;}fixed4 frag(v2f i) : SV_Target{fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;float3 _worldNormal = normalize(i.worldNormal);float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);fixed4 _albedo = tex2D(_MainTex, i.uv);fixed3 _diffuse = _LightColor0.rgb * _albedo.rgb * saturate(dot(_worldNormal, _worldLight));float3 _worldRefl = normalize(reflect(-_worldLight, _worldNormal));float3 _worldView = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);fixed3 _specular = _LightColor0.rgb * _Specular.xyz * pow( saturate(dot(_worldRefl, _worldView)),_Gloss);return fixed4(_ambient + _diffuse + _specular, 1);}ENDCG}}}
效果如下: