2014年6月27日 星期五

2014 mobile 應注意

 GDC 2014 一篇 Unreal講下一代手機繪圖的文章

新的硬體效能大概
300+ GFLOPS
26GB Bandwidth

http://blog.imgtec.com/powervr/graphics-cores-trying-compare-apples-apples

Mobile GPU:
  Tile-Based: ImgTec, Qualcomm...
  Direct: Nvidia, Intel, Qualcomm..

*Qualcomm Adreno 可切換tile-based or direct‧。
    - Extension: GL_QCOM_binning_control

Tile-Based Mobile GPU
- 32x32 pixels (ImgTec) 300x300 (Qualcomm)

ImgTec G6430:
 1 GPU core
 4 USC (Unified Shading Cluster)
   -16 pipeline each
FP16: 2 SOP (Sum of Product) per clock
  - (a*b+c*d)
FP32: 2 MADD per clock
  -(a*b +c)
154 GFLOPS @ 400 MHz
  - 16 bit floating point

改善部分:
 -Support OpenGL ES 3.0
 -Scalar, not Vector 
 -No additional cost for dependent texture reads
   - Pixelshader math on texture coordinates
   - Texture coordinates can be in .zw (swizzle)
   - 上述的意思是在讀貼圖前,對uv做運算不再有額外的cost,swizzle也不會有。
 -Coherent dynamic flow control at full speed (not 1/4th speed)
   -如果 branch是所有pixel 都做  eg.不是透過mask來branch,就不像之前會被降速
-FP16 is the minimun precision ( no more lowp)
   -用lowp不會有額外效能,最低精度就是FP16


Scalar vs  Vector
glsl : vec3 V = A*B + C*D;
Executed on SGX 543 GPU   75% speed , w component is wasted
  vec4 V' = A*B;
  vec4 V = C*D + V';

Executed on G6430 GPU    full speed
 half V.x = A.x*B.x + C.x*D.x;
 half V.y = A.y*B.y + C.y*D.y;
 half V.z = A.z*B.z + C.z*D.z;

ImgTec tip
-Hidden Surface Removal (HSR)
  - For opaque only
  - Don't keep alpha-test enabled all the time
  - Don't keep "discard" keyword in shader source, even if it's not used (不要相信 compiler)
    alpha-test 和 用了discard都會破壞 HSR

-把相同State都group在一起。
-Sort on State, not distant

-Qualcomm 的rendering process
  會有2個vertex shader,其中一個在tile之前,會把face position save in RAM, 等全部triangle 都知道了再做tile。所有tile 結束再flush to RAM

-Qualcomm Snapdragon Rendering Tips
  -Traditional handling of overdraw (via depth test)
    –Cull as much as you can on CPU, to avoid both CPU and GPU cost
    –Sort on distance (front to back) to maximize early z-rejection
The Adreno SIMD is wide
   -效能問題主要是在temp register usage而不是instruction counts
   -Hard optimize in glsl, check in profiler
   -用ALU比Lookup Texture 快。
  - 避免dependent texture fetches

Expensive to switch Frame Buffer Object on Tile-based GPUs
  -避免太多RenderTarget 切換
  -因為driver會把rendertarget save 到ram裡再copy出來。這樣很慢。
  -要用就開新的。

Clear ALL FBO attachments after new frame/rendertarget
–Clear after eglSwapBuffers / glBindFramebuffer
–Avoids reloading FBO from RAM
–NOTE: Do NOT do unnecessary clears on non-tile-based GPUs (e.g. NVIDIA)
  沒有直接的指令告訴api不要copy。只能用hint的,就是clear buffer。

Discard unused attachments before new frame/rendertarget
–Discard before eglSwapBuffers / glBindFramebuffer
–Avoids saving unused FBO attachments to RAM
–glDiscardFramebufferEXT / glInvalidateFramebuffer

Programmable Blending
GL_EXT_shader_framebuffer_fetch (gl_LastFragData)
•Reads current pixel background “for free”
•Potential uses:
–Custom color blending
–Blend by background depth value (depth in alpha)
•E.g. Soft intersection against world geometry for particles
–Deferred shading without resolving GBuffer
•Stay on GPU and avoid expensive round-trip to RAM
最基本的應用是 soft particle。讀background的depth是free的只是一個register

Tips
•Think scalar! Avoid using unnecessary components
–Avoid: (vec4*float)*float (8 MUL)
–Use: vec4*(float*float) (5 MUL)
•Prefer 16-bit floating point operations (mediump)
•Leverage ALU to hide memory fetches
–E.g. ALU can be faster than using lookup-textures

More Tips
•Don’t switch back and forth between mediump/highp
–ImgTec: Requires shader instructions to convert each time
–Qualcomm: Many conversions are free
   精度轉換有額外cost。先做高精度,再做低精度的轉換。
•Branch spatially coherent for many pixels
–Uses predication to ignore invalid path

Core Optimization: Opaque Draw Ordering
•All platforms,
–1. Group draws by material (shader) to reduce state changes
•Then for all platforms except ImgTec,
–2. Skybox last: 5 ms/frame savings (vs drawing skybox first)
–3. Sort groups nearest first : extra 3 ms/frame savings
–4. Sort inside groups nearest first : extra 7 ms/frame savings
排序是必做的!! 對效能影響非常大。

參考:
GDC2014 Next-gen Mobile Rendering

2014年6月19日 星期四

Deferred Rendering 下的特殊需求

這是針對Unity4的Deferred Rendering,因為5的做法會大改,而且也還沒發佈所以不確定。

最近有個特殊需求是,在做PostEffect時,希望能把一些東西排除掉,列如背景去飽和了,但人物想不被受影響。

但這個在Deferred rendering下會有問題,因為Unity的Deferred Rendering的PostEffect只能作用在最後一隻畫的Camera上,因此想了幾個方法。

1.畫人物時同時畫Stencil,PostEffect判斷Stencil,把人物排除。這個缺點是邊緣會有些鋸齒,Stencil應該沒AA。而且半透明物件就沒辦法。

2.人物在PostEffect之後用Forward畫,。缺點是,Camera深度沒共用,人不會被場景遮蔽。

3.人物用Shader tag,用一隻Camera RenderwithShader,輸出mask。但RenderWithShader同上,只能在Forward下使用,Mask也沒深度。而且另外做mask,drawcall也會增加。

4.把場景、特效、人物,用不用的RenderTexture輸出,最後在另一個PostEffect合併。這是個可行的方法,會增加一些Blit的drawcall,但是固定的。只是會用掉很多Vram。一張Full HD的rendertarget還滿大的。

上述方法好像都有缺點。

如果不同render path的Camera能Share Depth buffer就好了。

所以,如果拿Deferred Rendering的Camera深度,寫到Forward那隻Camera不就解決了?
於是2的Modify選項就出現了。

Camera結構如下:
MainCamera <-- Deferred Rendering
   -PlayerCamera  <-  Forward Rendering (clear flag -> don't clear)

在PlayerCamera掛上一個DepthWrite的Script
Cull Mask設定一下

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class DetphWrite : MonoBehaviour {

 private Material DepthWriteMaterial = null;
 // Use this for initialization
 void Start () {
  DepthWriteMaterial = new Material(Shader.Find("Hidden/DepthWrite"));
 }
 

 void OnPreRender()
 {

  DrawQuad();
 }

 void DrawQuad()
 {
  GL.PushMatrix(); 
  GL.LoadOrtho();
  
  DepthWriteMaterial.SetPass(0);     
  
  //Render the full screen quad manually.  
  GL.Begin(GL.QUADS); 
  GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(0.0f, 0.0f, 0.1f);  
  GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 0.0f, 0.1f);  
  GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.1f);  
  GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(0.0f, 1.0f, 0.1f);  
  GL.End();
  
  GL.PopMatrix();
 }
}

Shader如下:
Shader "Hidden/DepthWrite" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "black" {}
    }
SubShader {
    Pass {
        ZTest Always Cull Off ZWrite On
      Fog { Mode off }
        
        CGPROGRAM        
            #pragma exclude_renderers gles flash
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 4.0
             #include "UnityCG.cginc" 
            // vertex input: position, UV
            struct appdata {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
            };
           
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
           
            v2f vert (appdata v) {
                v2f o;
                o.pos = mul( UNITY_MATRIX_MVP, v.vertex );
                o.uv = v.texcoord.xy;
                return o;
            }
           

            
            sampler2D _MainTex;     
            sampler2D _CameraDepthTexture;
            
            
            struct fragOut
           {
               // half4 color : COLOR;   don't need
               float depth : DEPTH;
            };
          
            fragOut frag( v2f i ) {
           
                fragOut o;
               
                float depth =UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture,i.uv));
              
              // o.color= 0; // 
                o.depth= depth;
                return o;
           }
            
            

        ENDCG
        }
    }
}
本來以為_CameraDepthTexture要自己copy,但發現竟然Get到的是對的,值沒被Clear掉。如果Forward那支Camera,有下Clear flag Depth,就會被清掉。所以如果要自己畫Mask,前面3的方式,應該也可以傳進來自己判斷。

這個方法的缺點是用到Shader Model 4.0,要把Dx11的flag打開。
另外,在PlayerCamera畫的Soft Particle會失效(明明 _CameraDepthTexture抓得到),我把Particle Shader中#ifdef SOFTPARTICLES_ON 註解掉就可以。應該是Unity判斷Camera不同Render Path時,就 define off了。

如果要在d3d9下可以用,另一個想到的方法是在vertex shader讀入depth map,然後事前產生一個plane,它的點數和螢幕pixel一樣多,再寫入depth。可能對點的位置會有點麻煩,但理論上可行。

2014年6月18日 星期三

一堆Forward限定

Unity文件爛到有剩,一堆東西Forward限定的也不清楚

整理一下,日後再補充

1. Shader tag,所以Camera.RenderWithShader也沒用。
2. Camera.targetTexture:  想抓rendertarget 就用blit吧
3. Stencil Buffer

2014年3月28日 星期五

Unity ColorSpace

之前一直沒認真研究

如果是下Linear space 讀貼圖時會用 sRGB read (power(color, 2.2)) 寫出時會用 sRGB write(power(color,1/2.2))

如果是Gamma space 應該是不會對寫入和寫出做什麼
所以在Gamma space選項打開時,對貼圖設Bypass sRGB Sampling 打開和關掉都沒作用,因為預設就是Linear 的讀入

因此,在Linear space時,要注意如果貼圖是Normal map,要選Normal map,它會自動把Bypass sRGB Sampling開啟, 而自己做LUT貼圖時記得手動打開。以此類推~

2014年2月12日 星期三

Shokwave Shader

參考 http://forums.tigsource.com/index.php?topic=34921.msg928012#msg928012
Shader "Custom/Distort" {
 Properties {
  _MainTex ("Base (RGB)", 2D) = "white" {}
  //_strength("Strength", float) = 1.5
  _strength("Strength", Range(-1.1,1.1)) = 0
  _radius("Radius", float) = 5
  _width("Width", float) = 2.5
  _center("Center", Vector) =(0,0,0,0)
 }
 CGINCLUDE

  #include "UnityCG.cginc"
  
  sampler2D _MainTex;
  
  float _strength;
  float _radius;
  float _width;
  float4 _center;
  struct v2f {
   half4 pos : SV_POSITION;
   half4 uv : TEXCOORD0;
   half4 center : TEXCOORD1;
  };
  float2 offsetFnc(float2 _pos, float2 _center)
  {
   //length from the center of the rendered quad;
   float x = distance(_pos , _center);
   //calculate offset stength
   float val = cos((x-_radius)/(_width/3.1415)) * _strength/2 + _strength/2;
   if(abs(x-_radius)>_width)
      val = 0;
   return normalize(_pos- _center)*val;
   
  }
  v2f vert(appdata_full v) {
   v2f o;
   
   o.pos = mul (UNITY_MATRIX_MVP, v.vertex); 
   o.uv = ComputeScreenPos (o.pos);
   //_center = float4(_Object2World[3][0], _Object2World[3][1], _Object2World[3][2], 1);
   _center = float4(_Object2World[0][3], _Object2World[1][3], _Object2World[2][3], 1);
   o.center = ComputeScreenPos(mul(UNITY_MATRIX_VP, _center));
   //o.center = ComputeScreenPos(mul(UNITY_MATRIX_MVP, _center));
   
   return o; 
  }
  
  fixed4 frag( v2f i ) : COLOR { 
   i.uv.xy /= i.uv.w;
   i.center.xy /= i.center.w;
   fixed4 result = tex2D (_MainTex, i.uv.xy + offsetFnc(i.uv.xy, i.center.xy));
   result.a = 1;
   //result = float4(i.uv.x, i.uv.x, i.uv.x,1);
   return result;
  }    
 ENDCG
 
 SubShader {
  Tags { "RenderType" = "Transparent" "Queue" = "Transparent-2" }
  Cull Off
  Lighting Off
  ZWrite Off
  Fog { Mode Off }
  Blend SrcAlpha OneMinusSrcAlpha
  
 Pass {
 
  CGPROGRAM
  
  #pragma vertex vert
  #pragma fragment frag
  #pragma fragmentoption ARB_precision_hint_fastest 
  #pragma target 3.0
  
  ENDCG
   
  }
    
 } 

 FallBack "Diffuse"
}

2013年10月22日 星期二

Blur CubeMap

有時候會需要Blur CubeMap
例如 要用 CubeMap的Mip Map表現 roughness時
但一般自動產生的mip map邊緣會不連續

這時候可以用cubemapgen/產生連續的mip map結果

產生出來的圖,再到Unity裡讀進來,建立CubeMap
 using UnityEngine;  
 using System.Collections;  
 using System;  
 public class BlurCubeMap : MonoBehaviour {  
      public Cubemap TextureCube;  
      public Texture2D [] mTexture;  
      CubemapFace[] faces = {CubemapFace.PositiveX, CubemapFace.NegativeX, CubemapFace.PositiveY, CubemapFace.NegativeY, CubemapFace.PositiveZ, CubemapFace.NegativeZ };  
      // Use this for initialization  
      void Start () {  
       int miplevel = 0;  
       if(mTexture.Length > 0)  
       {  
           miplevel = mTexture[0].mipmapCount;  
       }  
      // TextureCube = new Cubemap (256, TextureFormat.ARGB32, true);  
       for(int i = 0; i <6 ; ++i){  
                for(int m = 0 ; m < miplevel; ++m)  
                {  
                     Color[] c = mTexture[i].GetPixels(m);  
                     //mip map level 6 is stranger   
                     if(m == 6)  
                     {  
                          if(i == 0)  
                               c = mTexture[1].GetPixels(m);  
                          if(i == 1)  
                               c = mTexture[0].GetPixels(m);                                
                     }  
                     if(m == 6) //mirror it  
                     {  
                          Color[] c2 = mTexture[i].GetPixels(m);  
                          int width =(int)Math.Sqrt(c.Length);  
                          int currentidx = 0;  
                          for(int idx = 0; idx < width; ++idx)  
                          {  
                               for(int idx2 = 0; idx2 < width; ++idx2)  
                               {  
                                    int switchidx = currentidx + (width -1 -(idx2 * 2));  
                                    c[currentidx] = c2[switchidx];  
                                    currentidx++;  
                               }  
                          }  
                     }  
                     else{  
                          Array.Reverse(c);  
                     }  
                     TextureCube.SetPixels(c,faces[i], m);  
                     //Debug.Log(m);  
                }  
           }  
      TextureCube.Apply(false);  
      }  
      // Update is called once per frame  
      void Update () {  
      }  
 }  

Toon Shader

參考
http://u16hp.blog134.fc2.com/page-2.html
的Shader

原來的效果可以找TMX Editor來看


Shader Code:
 Shader "Outlined/Diffuse" {  
      Properties {  
           _Color ("Main Color", Color) = (.5,.5,.5,1)  
           _OutlineColor ("Outline Color", Color) = (0,0,0,1)  
           _Outline ("Outline width",Float) = .005  
         _RimColor ("Rim Color", Color) = (0.26,0.19,0.16,0.0)  
         _RimPower ("Rim Power", Range(0.5,8.0)) = 3.0  
           _MainTex ("Base (RGB)", 2D) = "white" { }  
           _SpecTex ("Specular (RGB)", 2D) = "white" { }  
           _RampTex ("Ramp (RGB)", 2D) = "white" { }  
      }  
 CGINCLUDE  
 #include "UnityCG.cginc"  
 struct appdata {  
      float4 vertex : POSITION;  
      float3 normal : NORMAL;  
 };  
 struct v2f {  
      float4 pos : POSITION;  
      float4 color : COLOR;  
 };  
 uniform float _Outline;  
 uniform float4 _OutlineColor;  
 v2f vert(appdata v) {       
      v2f o;  
      o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
      float3 norm  = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);  
      float2 offset = TransformViewToProjection(norm.xy);  
      float viewScaler = (o.pos.z + 1) *0.5;  
      o.pos.xy += offset * viewScaler * _Outline;  
      o.color = _OutlineColor;  
      return o;  
 }  
 ENDCG  
      SubShader {  
      Tags { "RenderType"="Opaque" }  
 CGPROGRAM  
 #pragma surface surf Ramp vertex:myvert  
 #pragma target 3.0  
 sampler2D _MainTex;  
 sampler2D _RampTex;  
 sampler2D _SpecTex;  
 fixed4 _Color;  
 float4 _RimColor;  
 float _RimPower;  
 struct Input {  
      float2 uv_MainTex;  
      float3 Vnormal;  
 };  
 half4 LightingRamp (SurfaceOutput s, half3 lightDir, half atten) {  
      half NdotL = dot (s.Normal, lightDir);  
      half diff = max(NdotL,0); 
      half3 ramp = tex2D (_RampTex, float2(0,diff)).rgb;  
      half4 c;  
      // c.rgb = _LightColor0.rgb * ramp * (atten * 2);  
      c.rgb = s.Albedo * ramp;  
      c.a = s.Alpha;  
      return c;  
 }  
 void myvert (inout appdata_full v, out Input o) {  
      UNITY_INITIALIZE_OUTPUT(Input,o);  
      o.Vnormal = normalize(mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal));
    }  
 void surf (Input IN, inout SurfaceOutput o) {  
      fixed4 c = tex2D(_MainTex, IN.uv_MainTex)* _Color;  
      float2 specUV = float2(IN.Vnormal.x *0.5 +0.5, IN.Vnormal.y * 0.5 + 0.5);  
      fixed4 spec = tex2D(_SpecTex, specUV);  
   o.Emission = spec * _RimColor.rgb;  
      o.Albedo = c.rgb;  
      o.Alpha = c.a;  
 }  
 ENDCG  
           Pass {  
                Name "OUTLINE"  
                Tags { "LightMode" = "Always" }  
                Cull Front  
                //Cull off  
                ZWrite On  
                //ZTest Off  
                ColorMask RGB  
                Blend SrcAlpha OneMinusSrcAlpha  
                //Offset 20,20  
                CGPROGRAM  
                #pragma vertex vert  
                #pragma fragment frag  
                half4 frag(v2f i) :COLOR   
                {  
                 i.color.a = 0.5;  
                 return i.color;   
                }  
                ENDCG  
           }  
      }  
      Fallback "Diffuse"  
 }  
對描邊的效果還不大滿意
比較特別的是拿View Space的Normal x, y 當uv查一張貼圖做Rim Light的效果,這樣可以自己決定有漸層的顏色和Rim的範圍
For 3dsMax的(聽美術說有點問題,但還沒問是什麼問題 XD)
 // 3ds max effect file  
 // Simple Lighting Model  
 // This is used by 3dsmax to load the correct parser  
 string ParamID = "0x0000";  
 //DxMaterial specific   
 // light direction (view space)  
 float3 lightDir : Direction <   
      string UIName = "Light Direction";   
      string Object = "TargetLight";  
   string Space = "World";  
      //int refID = 0;  
      >;  
 // light intensity  
 float4 I_a = { 0.1f, 0.1f, 0.1f, 1.0f };  // ambient  
 float4 I_d = { 1.0f, 1.0f, 1.0f, 1.0f };  // diffuse  
 float4 I_s = { 1.0f, 1.0f, 1.0f, 1.0f };  // specular  
 // material reflectivity  
 float4 k_d <  
      string UIName = "Diffuse";  
      > = float4( 0.47f, 0.47f, 0.47f, 1.0f );  // diffuse  
 float outline <  
   string UIName = "outline";  
      > = float(0.005f);  
 float4 rimColor<  
      string UIName = "Rim Color";  
      > = float4( 1.0f, 1.0f, 1.0f, 1.0f );   
 float4 outlineColor<  
      string UIName = "Outline Color";  
      > = float4( 0.0f,0.0f, 0.0f, 0.0f );   
 texture diffuseTexture : DiffuseMap<   
      //string name = "seafloor.dds";   
      string UIName = "Diffuse Texture";  
 //     int Texcoord = 0;  
 //     int MapChannel = 3;       
      >;  
 texture specularTexture : SpecularMap<   
      //string name = "seafloor.dds";   
      string UIName = "Specular Texture";  
 //     int Texcoord = 0;  
 //     int MapChannel = 3;       
      >;       
 texture rampMap : RampMap <   
 //     string name = "NMP_Ripples2_512.dds";   
      string UIName = "Ramp Texture";  
 //     int Texcoord = 0;  
 //     int MapChannel = 3;       
      >;  
 //vertex color       
 int texcoord0 : Texcoord  
 <  
      int Texcoord = 0;  
      int MapChannel = 0;  
 >;  
 //uv 1  
 int texcoord1 : Texcoord  
 <  
      int Texcoord = 1;  
      int MapChannel = 1;  
 >;       
 // transformations  
 float4x4 World   :           WORLD;  
 float4x4 View    :           VIEW;  
 float4x4 Projection :           PROJECTION;  
 float4x4 WorldViewProj :      WORLDVIEWPROJ;  
 float4x4 WorldView :           WORLDVIEW;  
 struct VS_OUTPUT  
 {  
   float4 Pos : POSITION;  
      float2 TexCoord0 : TEXCOORD0;  
      float3 Normal : TEXCOORD1;  
      float3 LightVector : TEXCOORD2;  
      float3 Vnormal : TEXCOORD3;  
   float4 col : COLOR0;  
 };  
 struct VS_OUTPUT2  
 {  
   float4 Pos : POSITION;  
   float4 col : COLOR0;  
 };  
 VS_OUTPUT VS(  
   float3 Pos : POSITION,   
   float4 col     : TEXCOORD0,  
   float3 Norm : NORMAL,   
   float2 Tex : TEXCOORD1)  
 {  
   VS_OUTPUT Out = (VS_OUTPUT)0;  
   float3 L = lightDir;  
   float3 P = mul(float4(Pos, 1),(float4x4)World); // position (view space)  
   float3 N = normalize(mul(Norm,(float3x3)World)); // normal (view space)  
   float3 R = normalize(2 * dot(N, L) * N - L);     // reflection vector (view space)  
   float3 V = normalize(P);               // view direction (view space)  
   Out.Pos = mul(float4(Pos,1),WorldViewProj);  // position (projected)  
   Out.col = col;  
      Out.Normal = N;  
      Out.Vnormal = normalize(mul(Norm,(float3x3)WorldViewProj));  
   Out.TexCoord0 = Tex;  
      Out.LightVector = normalize(lightDir);  
      //Out.LightVector = normalize(mul(lightDir,(float3x3)World));;  
   return Out;  
 }  
 sampler2D diffuseSampler = sampler_state  
 {  
      Texture = <diffuseTexture>;  
      MinFilter = Linear;  
      MagFilter = Linear;  
      MipFilter = Linear;  
      ADDRESSU = Wrap;  
      ADDRESSV = Wrap;  
 };  
 sampler2D specularSampler = sampler_state  
 {  
      Texture = <specularTexture>;  
      MinFilter = Linear;  
      MagFilter = Linear;  
      MipFilter = Linear;  
      ADDRESSU = CLAMP;  
      ADDRESSV = CLAMP;  
 };  
 sampler2D rampSampler = sampler_state   
 {  
      Texture = <rampMap>;  
      MinFilter = Linear;  
      MagFilter = Linear;  
      MipFilter = Linear;  
      ADDRESSU = CLAMP;  
      ADDRESSV = CLAMP;  
 };  
 float4 PS(VS_OUTPUT IN,   
            uniform sampler2D DiffuseMap,  
            uniform sampler2D RampMap,  
            uniform sampler2D SpecularMap) : COLOR  
 {  
      float4 color = tex2D(DiffuseMap, IN.TexCoord0);  
      float2 specUV = float2(IN.Vnormal.x *0.5 +0.5, IN.Vnormal.y * 0.5 + 0.5);  
      half NdotL = max(0,dot (IN.Normal, IN.LightVector));  
      NdotL = clamp(NdotL, 0, 1);  
      half3 ramp = tex2D (RampMap, float2(0,1.0-NdotL)).rgb;  
      ramp = ramp* (IN.col.r) + 1.0 * (1.0- IN.col.r);  
      float4 spec = tex2D(SpecularMap, specUV);  
   color.rgb = color.rgb * ramp* (1.0 - IN.col.r) + color.rgb * ramp* (IN.col.r) + spec * rimColor;  
      //color.rgb = color*k_d;  
      //color.rgb = tex2D(DiffuseMap, IN.TexCoord0).rgb;  
      //color.a = 1;  
   return color ;  
 }  
 VS_OUTPUT2 VS_Outline(  
   float3 Pos : POSITION,   
   float3 Norm : NORMAL )  
 {  
      VS_OUTPUT2 Out;  
      Out.Pos = mul(float4(Pos,1),WorldViewProj);   
      float3 norm = (mul(Norm,(float3x3)WorldViewProj));  
      float viewScaler = (Out.Pos.z + 1) *0.5;  
      //float viewScaler = Out.Pos.z;  
      Out.Pos.xy += norm.xy * viewScaler * outline;  
      Out.col = outlineColor;  
      return Out;  
 }  
 float4 PS_Outline(  
      VS_OUTPUT IN  
 ) : COLOR  
 {  
  float4 color = IN.col;  
  color.a = 0.5;  
  return color;  
 }  
 technique DefaultTechnique  
 {  
   pass P1  
   {  
     // shaders  
           ZEnable = true;  
           ZWriteEnable = true;  
     CullMode = cw;  
           AlphaBlendEnable = true;  
           SrcBlend = srcalpha;  
           DestBlend = zero;  
     VertexShader = compile vs_2_0 VS_Outline();  
     PixelShader = compile ps_2_0 PS_Outline();  
   }   
    pass P0  
   {  
     // shaders  
           ZEnable = true;  
           ZWriteEnable = true;  
     CullMode = ccw;  
     // ShadeMode = Gouraud;  
     VertexShader = compile vs_2_0 VS();  
     PixelShader = compile ps_2_0 PS(diffuseSampler, rampSampler, specularSampler);  
   }   
 }