【原】Unity Shader VS UDK Material Editor

UDK 的材质编辑器十分好用,毕竟是所见即所得的。虽然unity也有类似第三方插件,但易用性还是差很多,下面主要是,把一些常见表达式概念对应起来。

1. UDK CameraVector (相机位向量)表达式
 相机位向量表达式使您能够在游戏运行时访问相机的指向向量。在要求材质于不同视角角度下呈现出不同效果时
 对应unity shader中Input结构附加变量: float3 viewDir。对于内建的viewDir, 它和CameraVector一样是切线空间中的变量。
 对于unity 如果不使用附加变量,计算过程如下:ObjSpaceViewDir() 函数求摄像机到顶点的方向。
 在顶点shander中还可以求反射方向
   float3 viewDir = ObjSpaceViewDir( v.vertex );
   //float3 I = reflect( -viewDir, v.normal );   求 ReflectionVector 反射向量
   计算到切线空间
   TANGENT_SPACE_ROTATION;
   viewDir = normalize(mul(rotation, normalize(ObjSpaceViewDir(v.vertex))));
   这个是基于物体切线空间的位置。 相机的世界坐标向量是:
   
float3 worldView = normalize(WorldSpaceViewDir(v.vertex));

2. UDK PixelDepth (像素深度)表达式
   在udk 中 float z = ScreenPosition.w.r 
  
   对应unity计算如下:
   float z = mul (UNITY_MATRIX_MVP, v.vertex).w;
   float z = - mul(UNITY_MATRIX_MV, v.vertex).z;
   如果是在surf 函数中,也可以从screenPos.w.r 获得。

3. UDK ScreenPosition (屏幕位置)表达式
  屏幕位置表达式用于输出当前指定像素在屏幕中的位置。像素处于屏幕空间中的位置,范围从[-1,-1],即屏幕左下角,到[1, 1],即右上角。它有一个参数,屏幕校准(ScreenAlign),用来将坐标范围改动为从[0, 0]到[1, 1]
  udk 当前像素ScreenPosition是由CalcMaterialParameters函数计算出来的。
  unity 有两种方法获得ScreenPos 
   1. unity中顶点经过投影变换后再使用ComputeScreenPos计算出来。
   o.pos = mul (UNITY_MATRIX_MVP, v.vertex); 
   o.ScreenPos = ComputeScreenPos (o.pos); //由[-1,1]范围映射到[0,1]范围.

   2. unity 方法2 是surface shader中, 直接在surf Input 参数结构中声明screenPos变量 ,unity 会自动填入值.
     screenPos.xy和上面计算的 o.ScreenPos.xy 相同
     struct Input
  {
        float4 screenPos;
      };
     
    btw: 做为uv使用前都要除w值:screenPos.w 或者 ScreenPos.w。
    ComputeGrabScreenPos 函数用来映射像素到graptexture上,从GrabTexture 上可以获得 DestColor (目标颜色)
 ,这个grabuv和screenPos是不同的,在我机器上是除w之后,grabuv.y 是 1-screenPos.y(可能和我的环境有关,需要grabuv时最好从顶点中计算)。

   
4. UDK SceneDepth (场景深度)表达式
   场景深度表达式跟目标深度表达式(DestDepth)非常相似,但它可以针对整个关卡场景中对深度进行采样,改变输入的UV即可。
   LinearEyeDepth (tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD( IN.screenPos)).r);
   LinearEyeDepth 线性化函数,正交投影时是针对z值进行了1/z变换(让近处物件有更好的z值分辨率,如下公式),这样近处的东西有更大的分辨率

  图片 
     (opengl)  求 -Pz 即eyeZ  对应公式:  -Pz = (2 * n * f) / (f + n - z' * (f - n))

    -Pz 是顶点在3D空间中的z值, 这和计算出来的w值以及PixelDepth是相同的。z'是投影变换后的z值。也是_CameraDepthTexture记录的z值。所以进行z值差值,要求线性结果。例如:

   //#define UNITY_SAMPLE_DEPTH(value)  (value).r

   half depthdiff = UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)));   
   depthdiff = LinearEyeDepth(depth) - IN.screenPos.w;
   BlendFactors = saturate(Paremeter * depthdiff); 
 
   对于当前点求的SceneDepth 也就是 udk 的DestDepth表达式。

5. UDK BumpOffset (凹凸偏移)表达式
  用来创建虚拟位移贴图(Virtual Displacement Mapping)效果。在使用法线贴图来产生表面高度的物理差异假象时,此表达式能改善其效果。
   BumpOffset 表达式公式如下:
   BumpOffset = TexCoord + CameraVector.RG * (Height * HeightRatio - HeightRatio * RefPlane);

   需要带Alpha高度图的法线贴图,用alpha通道作为Height节点输入。

   unity 对应的函数是
   inline float2 ParallaxOffset( half h, half height, half3 viewDir )
   {
     h = h * height - height/2.0;
     float3 v = normalize(viewDir);
     v.z += 0.42;
     return h * (v.xy / v.z);
   }

    half h = tex2D (_SpecMap, IN.uv_BumpMap).w;      //从法线贴图alpha通道读取高度

    float2 offset = ParallaxOffset (h, _HeightRatio, IN.viewDir);  //计算uv bumpoffset, _HeightRatio 是输入参数
    IN.uv_MainTex += offset;

    IN.uv_BumpMap += offset;

    虽然者略有不同,但都是属于虚拟位移贴图

6. UDK ReflectionVector (反射向量)
 反射向量表达式用来表示CameraVector(相机向量)通过应用材质的物体表面的法线的方向发射
 
对应着unity 顶点附加变量 float3 worldRefl. 但ReflectionVector 是切向空间的, worldRefl 则是世界空间的。对于UDK可以使用Transfrom表达式进行空间变化。
 Unity 局部空间变换到切向空间使用 TANGENT_SPACE_ROTATION得到rotation; 计算过程参考 CameraVector条目

   //一个unity环境反射的例子
   half4 tex = tex2D(_MainTex, IN.uv_MainTex);
   o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
   float3 worldRefl = WorldReflectionVector (IN, o.Normal);
   half4 reflclr = texCUBE (_Cube, worldRefl);
   reflclr *= tex.a;
   o.Emission = reflclr.rgb * _ReflectColor.rgb;

7. UDK Fresnel (菲涅尔)
  菲涅尔表达式是多种功能的复杂综合:读取像素的法线向量,并在向量指向镜头时输出值0,在法线方向与相机视角垂直时输出1
 如下计算过程。法线和CameraVector都是位于切线空间的矢量。 如果没有法线贴图提供法线,那么法线就是 (0, 0, 1.0), 参考切线空间定义
 计算过程  Fresnel = (1 - Max(Normal dot Camera, 0)) ^ Exponent
  F = pow(1- max(N*C, 0), E)
  UDK 对应 Shader
  float Local2 = dot(float3(0.00000000,0.00000000,1.00000000),Parameters.TangentCameraVector);  //切线空间viewDir
  float Local3 = max((0.00000000),Local2);
  float Local4 = ((1.00000000) - Local3);
  float Local5 = ClampedPow(Local4,(3.00000000));

  转换到unity
  o.Normal = half3(0,0,1.0f);  //有法线贴图则: o.Normal = UnpackNormal(tex2D(_BumpMap, IN.BumpUV));
  half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
  o.Emission = float3(1.0f,0,0) * pow (rim, 3);
viewDir 基于切线空间, 法线也是。如果没有法线贴图, 可以使用 (0,0,1)

8. unity 变量_ ZBufferParams 的值. 主要用于 ComputeScreenPos 函数中
 
double zc0, zc1;
 //OPenGL would be this:
 zc0 = (1.0 - m_FarClip / m_NearClip) / 2.0;
 zc1 = (1.0 + m_FarClip / m_NearClip) / 2.0;

 //D3D is this: 
 zc0 = 1.0 - m_FarClip / m_NearClip;
 zc1 = m_FarClip / m_NearClip;

 float4 _ZBufferParams = float4(zc0, zc1, zc0/m_FarClip, zc1/m_FarClip);

 DX:   
 eyeZ 正交投影之前的z值。 z 是经过正交投影的值(即_CameraDepthTexture中的值)。
  eyeZ  = (n * f) / (f - z * (f - n))
  LinearZ = eyeZ / f = n / (f - z * (f - n))

  GL:
  eyeZ  = (2 * n * f) / (f + n - z * (f - n))
  LinearZ = eyeZ / f = (2 * n) / (f + n - z * (f - n))

9. SceneTexture (场景贴图)
   UDK 拥有这个表达式,代表当前的场景颜色信息
   unity 没有当前的color bufrer。代替的方法如下:

  Subshader
  {
    Tags {"RenderType"="Transparent" "Queue"="Transparent"}

    GrabPass {}
    sampler2D _GrabTexture;     //捕获的纹理
   
    Pass {
      。。。。。
      CGPROGRAM   
      #pragma target 3.0    
      #pragma vertex vert
      #pragma fragment frag  
      #pragma glsl           

     。。。。。
     ENDCG
    }
  }

10. Desaturation (冲淡颜色)

   可用于冲淡漫反射图得到灰色图
    
   下面是unity 的函数Luminance, 作用类似
   unity 点乘的常量是 fixed3(0.22, 0.707, 0.071),而UDK默认值是(0.3,0.59,0.11)
    inline fixed Luminance( fixed3 c )
    {
         return dot( c, fixed3(0.22, 0.707, 0.071) );
    }

11. DepthBiasedAlpha(深度偏移Alpha)
  DepthBiasedAlpha表达式是两个主要用来消除尖锐边缘的表达式之一,这些边缘通常是由平面粒子实例(sprite particle)与几何体交叉而产生的。
  表达式读取两个输入值:Alpha值和Bias值。Bias值对目的地缓冲器中的内容和材质进行实际混合。
  Bias值读取黑、白之间的值,值0(黑)表示完全显示目的地缓冲器中的内容,值1(白)表示完全显示材质的颜色。
  Alpha输入值则直接通过表达式,不需要进行任何运算。默认值为1
  如果没有输入Alpha值,则DepthBiasedAlpha(深度偏移Alpha)表达式的输出值为1。 

  默认属性: float Local2 = DepthBiasedAlpha(Parameters,float((1.00000000)),float((0.50000000)),float((1.0000000)));

  也有两个参数,bNormalize和BiasScale。BiasScale用作与Bias输入值的乘数,从而允许在材质和目的地缓冲器内容之间更大程度地进行混合。

  bNormalize属性将深度值由范围的近到远映射到0到1的范围(bNormalize 暂时没看到起作用)
  公式如下:
  float DepthBiasedAlpha( FMaterialPixelParameters Parameters, float InAlpha, float InBias, float InBiasScale )
  {
    float Result;
    half SceneDepth = PreviousDepth(Parameters.ScreenPosition);
    float DepthBias = (1.0 - InBias) * InBiasScale;
    float BlendAmt = saturate((SceneDepth - Parameters.ScreenPosition.w) / max(DepthBias,0.001));
    Result = InAlpha * BlendAmt;
    return Result;
  }

  unity 在这方面有一个 Soft Particles 的控制选项。 Edit->Project Setting->Quality。 或者在像素shader中:
   Properties

   {
     _Bias("_Bias", Range(0,1) ) = 0.5
   }

  .......
  half ScreenDepthDiff = LinearEyeDepth (tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)).r) - IN.screenPos.w;
  half Pow = pow(ScreenDepthDiff, _Bias);
  float Saturate = saturate(Pow);
  o.Alpha = Saturate;

12.  Distortion扭曲
  Distortion 扭曲在UDK中是整个材质节点的一个输入参数。在unity 可以如下实现:使用法线扰动uv,然后从grab纹理取得像素值
 Shader "Distortion"
{
 Properties
 {
  _Distortion("_Distortion", Range(0,1) ) = 0.1632124
  _BumpMap("_Offset", 2D) = "black" {}
 }
 
 SubShader
 {
  Tags
  {"Queue"="Transparent" "IgnoreProjector"="False" "RenderType"="Transparent"}
  
  GrabPass {}
  
  Cull Back
  ZWrite Off
  ZTest LEqual
  ColorMask RGBA
  Fog{}

  CGPROGRAM
  #pragma surface surf BlinnPhong  vertex:vert
  #pragma target 2.0


  float _Distortion;
  sampler2D _BumpMap;
  sampler2D _GrabTexture;
   
  struct Input
  {   
   float4 grabPos; 
   float2 uv_BumpMap;    
  };

  void vert (inout appdata_full v, out Input o)
  {
    float4 pos = mul (UNITY_MATRIX_MVP, v.vertex);
      o.grabPos = ComputeGrabScreenPos(pos);
  }
   

  void surf (Input IN, inout SurfaceOutput o)
  {
    o.Normal = float3(0.0,0.0,1.0);
    o.Alpha = 1.0;
    o.Albedo = 0.0;                        
    
    float3 Normal=UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
    float2 Multiply1=Normal.xy * _Distortion.xx;
    float2 uvs = IN.grabPos.xy/IN.grabPos.w + Multiply1;        
    float4 Tex2D0 = tex2D(_GrabTexture,uvs);
    o.Emission = Tex2D0.rgb;        
   }
  ENDCG
 }
 Fallback "Diffuse"
}

13.  如果你的程序不会运行到手机设备上,可以禁止生成OpenGL ES 2.0 (Opengl 缩减版)的shader
   #pragma exclude_renderers gles
  这样可以解决 shader 中 isnan 之类函数不能编译问题, 对于flash 可以用 #pragma exclude_renderers flash
 flash 能支持的寄存器太少了

附:

   1. unity 中可以附加到Input结构的变量.

    • float3 viewDir - will contain view direction, for computing Parallax effects, rim lighting etc.
    • float4 with COLOR semantic - will contain interpolated per-vertex color.
    • float4 screenPos - will contain screen space position for reflection effects. Used by WetStreet shader in Dark Unity for example.
    • float3 worldPos - will contain world space position.
    • float3 worldRefl - will contain world reflection vector if surface shader does not write to o.Normal. See Reflect-Diffuse shader for example.
    • float3 worldNormal - will contain world normal vector if surface shader does not write to o.Normal.
    • float3 worldRefl; INTERNAL_DATA - will contain world reflection vector if surface shader writes to o.Normal. To get the reflection vector based on per-pixel normal map, use WorldReflectionVector (IN, o.Normal). See Reflect-Bumped shader for example.
    • float3 worldNormal; INTERNAL_DATA - will contain world normal vector if surface shader writes to o.Normal. To get the normal vector based on per-pixel normal map, use WorldNormalVector (IN, o.Normal)

posted on 2017-04-25 13:24  阿蒙1024  阅读(1547)  评论(0编辑  收藏  举报

导航