2D紋理采樣,CG內置函數。 內部實現分為以下幾步: 1. 用圖片的寬高度乘以uv數值,得到像素坐標。widthPixel=samp.x*s.x;heightPixel=samp.y*s.y; 2. 因為取到的數值基本上都是帶有小數點的,也就是說不是一個整數,這個時候,需要看圖片的過濾設置了。也就是Unity3d的圖片設置中的Filter Mode。 3. Filter Mode是Point,不過濾,就會取像素點最靠近的整數,也就是四舍五入,得到像素點的坐標,然后出去圖片中,這個坐標的顏色。 4. 雙線性過濾,會取目標像素的附近4個像素,然后進行插值計算,得到平均顏色值,作為最終顏色。適合紋理由小放大過程中,出現的“馬賽克”。 5. 三線性過濾,在雙線性過濾的基礎上考慮到了深度LOD,會進行兩次雙線性過濾,來使不同的LOD等級紋理中,更加平滑的過渡。
這個方法的定義在UnityCG.cginc中,它有兩個參數,tex.xy是頂點的uv值,name##_ST則是在這個shader所在的材質球中,紋理圖片的縮放和偏移,S指Scale,T指Transform,它是一個float4類型,其值分別為(Tiling.x,Tiling.y,Offset.x,Offset.y)。這個方法運算后,得到的是經過偏移和縮放的uv。它的運算公式是TextureCoordinate = tex.xy * name##_ST.xy + name##_ST.zw。如果偏移為0,縮放為默認1,則可以不用經過這個過程。
這個方法是對法線紋理進行采樣。它的定義同樣在UnityCG.cginc里。
inline fixed3 UnpackNormal(fixed4 packednormal){#if defined(UNITY_NO_DXT5nm) return packednormal.xyz * 2 - 1;#else return UnpackNormalDXT5nm(packednormal);#endif}inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal){ fixed3 normal; normal.xy = packednormal.wy * 2 - 1; normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy))); return normal;}這里有兩個方法,以UnpackNormal方法來說,它最主要的也就是 packednormal.xyz * 2 - 1; 要解釋這個,就必須講到法線紋理的生成。法線紋理是把模型的法線信息存到圖片中去,每條法線的x,y,z對應的存到每個像素的r,g,b中。每條法線里的每個數值都是一個[-1,1]的閉合區間里,像素的每個數值則都是在[0,255]中,(n + vec3(1.0,1.0,1.0)) * (255.0 / 2.0),每個法線向量,經過加上 vec3(1.0,1.0,1.0)。變成[0,2]的閉合區間里,然后除以2,再乘以255,發現向量,就會轉換成了[0,255]里的數值。這也是上述那條公式的由來。 至于法線紋理如何生成,有興趣的可以詳細了解一下這個算法,各個軟件的生成算法不一樣,最終得到的法線紋理也不一樣。但是紋理里的數據,肯定是符合規范的法線紋理數據,可以在shader中使用。 另外一個方法UnpackNormalDXT5nm ,則是一個壓縮法線紋理后的方法。大家都知道,法線是一個單位向量,也就是它的長度是1,所以只需要知道x,y的數值,是可以計算得到z的數值的,z=1-(x+y)的平方。這樣就可以減少貼圖的大小,減少GPU的數據傳輸量。
從模型空間到世界空間轉換法線。它的定義同樣在UnityCG.cginc里
inline float3 UnityObjectToWorldNormal( in float3 norm ){ // Multiply by transposed inverse matrix, actually using transpose() generates badly optimized code return normalize(_World2Object[0].xyz * norm.x + _World2Object[1].xyz * norm.y + _World2Object[2].xyz * norm.z);}其實也相當于
normalize(mul((float3x3)_World2Object),norm);_World2Object在上一篇 Unity3d中Shader的一些關于矩陣變換的基本信息中說過它是當前世界矩陣的逆矩陣。
拿”Mobile/Bumped Diffuse”這個shader來說,它的代碼很短,是一個中間代碼,需要點擊右方的Show generated code,出現詳細的代碼,其中的頂點函數
v2f_surf vert_surf (appdata_full v) { v2f_surf o; UNITY_INITIALIZE_OUTPUT(v2f_surf,o); o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex); //這里是計算頂點的世界坐標 float3 worldPos = mul(_Object2World, v.vertex).xyz; //得到頂點法線轉換到世界空間的法線,得到切線空間的N fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); //得到頂點的切線轉換到世界空間的切線,得到切線空間的T fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); //計算方向,后面用到 fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w; //通過T和N的向量積,得到垂直這兩個向量的向量,但是它的方向有兩個,所以乘以上面得到的方向參數,得到最終的向量,得到切線空間的B fixed3 worldBinormal = cross(worldNormal, worldTangent) * tangentSign; //所以下面是得到模型的頂點的切線空間到世界空間的矩陣,分行顯示,沒看明白的可以看一下矩陣的基本知識 o.tSpace0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x); o.tSpace1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y); o.tSpace2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z); #ifndef DYNAMICLIGHTMAP_OFF o.lmap.zw = v.texcoord2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw; #endif #ifndef LIGHTMAP_OFF o.lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw; #endif // SH/ambient and vertex lights #ifdef LIGHTMAP_OFF #if UNITY_SHOULD_SAMPLE_SH o.sh = 0; // ApPRoximated illumination from non-important point lights #ifdef VERTEXLIGHT_ON o.sh += Shade4PointLights ( unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb, unity_4LightAtten0, worldPos, worldNormal); #endif o.sh = ShadeSHPerVertex (worldNormal, o.sh); #endif #endif // LIGHTMAP_OFF TRANSFER_SHADOW(o); // pass shadow coordinates to pixel shader UNITY_TRANSFER_FOG(o,o.pos); // pass fog coordinates to pixel shader return o;}像素片段函數
// fragment shaderfixed4 frag_surf (v2f_surf IN) : SV_Target { // prepare and unpack data Input surfIN; UNITY_INITIALIZE_OUTPUT(Input,surfIN); surfIN.uv_MainTex.x = 1.0; surfIN.uv_MainTex = IN.pack0.xy; //把頂點函數取得的世界頂點坐標取出來< float3 worldPos = float3(IN.tSpace0.w, IN.tSpace1.w, IN.tSpace2.w); #ifndef USING_DIRECTIONAL_LIGHT //如果用的是直線光,就把頂點的位置轉換成向量,成為光照向量 fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos)); #else //否者直接用世界光照的方向 fixed3 lightDir = _WorldSpaceLightPos0.xyz; #endif #ifdef UNITY_COMPILER_HLSL SurfaceOutput o = (SurfaceOutput)0; #else SurfaceOutput o; #endif o.Albedo = 0.0; o.Emission = 0.0; o.Specular = 0.0; o.Alpha = 0.0; o.Gloss = 0.0; fixed3 normalWorldVertex = fixed3(0,0,1); // call surface function surf (surfIN, o); // compute lighting & shadowing factor UNITY_LIGHT_ATTENUATION(atten, IN, worldPos) fixed4 c = 0; fixed3 worldN; /*通過向量計算,得到世界法線的方向,這里Surf方法里o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex)); 得到的是該頂點的切空間的法線方向,實際上,下面這個方法也等于 worldN= normalize(mul( float3x3(IN.tSpace0.xyz, IN.tSpace1.xyz, IN.tSpace2.xyz),o.Normal)); */ worldN.x = dot(IN.tSpace0.xyz, o.Normal); worldN.y = dot(IN.tSpace1.xyz, o.Normal); worldN.z = dot(IN.tSpace2.xyz, o.Normal); o.Normal = worldN; // Setup lighting environment UnityGI gi; UNITY_INITIALIZE_OUTPUT(UnityGI, gi); gi.indirect.diffuse = 0; gi.indirect.specular = 0; #if !defined(LIGHTMAP_ON) gi.light.color = _LightColor0.rgb; gi.light.dir = lightDir; //進行光照計算,獲得夾角 gi.light.ndotl = LambertTerm (o.Normal, gi.light.dir); #endif // Call GI (lightmaps/SH/reflections) lighting function UnityGIInput giInput; UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput); giInput.light = gi.light; giInput.worldPos = worldPos; giInput.atten = atten; #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON) giInput.lightmapUV = IN.lmap; #else giInput.lightmapUV = 0.0; #endif #if UNITY_SHOULD_SAMPLE_SH giInput.ambient = IN.sh; #else giInput.ambient.rgb = 0.0; #endif giInput.probeHDR[0] = unity_SpecCube0_HDR; giInput.probeHDR[1] = unity_SpecCube1_HDR; #if UNITY_SPECCUBE_BLENDING || UNITY_SPECCUBE_BOX_PROJECTION giInput.boxMin[0] = unity_SpecCube0_BoxMin; // .w holds lerp value for blending #endif #if UNITY_SPECCUBE_BOX_PROJECTION giInput.boxMax[0] = unity_SpecCube0_BoxMax; giInput.probePosition[0] = unity_SpecCube0_ProbePosition; giInput.boxMax[1] = unity_SpecCube1_BoxMax; giInput.boxMin[1] = unity_SpecCube1_BoxMin; giInput.probePosition[1] = unity_SpecCube1_ProbePosition; #endif LightingLambert_GI(o, giInput, gi); // realtime lighting: call lighting function c += LightingLambert (o, gi); UNITY_APPLY_FOG(IN.fogCoord, c); // apply fog UNITY_OPAQUE_ALPHA(c.a); return c;}以上就是主要的光照和法線貼圖的使用。其實還可以把光照轉換到切空間中進行計算,得到計算結果后,再轉換回世界空間,都是可行的。
新聞熱點
疑難解答