Unity3d中Shader的一些常用方法

float4 tex2D(sampler2D samp, float2 s)

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等级纹理中,更加平滑的过渡。

TRANSFORM_TEX(tex,name)

这个方法的定义在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,则可以不用经过这个过程。

inline UnpackNormal(fixed4 packednormal)

这个方法是对法线纹理进行采样。它的定义同样在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的数据传输量。

inline float3 UnityObjectToWorldNormal( in float3 norm )

从模型空间到世界空间转换法线。它的定义同样在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的一些关于矩阵变换的基本信息中说过它是当前世界矩阵的逆矩阵。

inline float3 UnityObjectToWorldDir( in float3 dir )

inline float3 UnityObjectToWorldDir( in float3 dir )
{
    return normalize(mul((float3x3)_Object2World, dir));
}

光照的计算

拿”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 shader
fixed4 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;
}

以上就是主要的光照和法线贴图的使用。其实还可以把光照转换到切空间中进行计算,得到计算结果后,再转换回世界空间,都是可行的。

你可能感兴趣的:(Shader)