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;}以上就是主要的光照和法线贴图的使用。其实还可以把光照转换到切空间中进行计算,得到计算结果后,再转换回世界空间,都是可行的。
新闻热点
疑难解答