2012年7月26日 星期四

KinectSDK初探

使用作業系統 Windows 7
下載Kinect for Windows SDK 1.5 (update 2012.5.21)

Kinect Developer Toolkit

Audio
NearMode
Color
  RGB
  YUV
Depth
  Near Range
  有重影現象
Skeleton
  Default
  Seated
  Chooser:
     Default
     Closest1Player
     Closest2Player
     Sticky1Player
     Sticky2Player
     MostActive1Player
     MostActive2Player
Face Tracking
Tilt Angle (-27/+27)
Console

首先先試最簡單的Color Stream
開啟了 ColorBasics-WPF 專案
它Reference了 Microsoft.Kinect
最重要的是
private KinectSensor sensor;
一個KinectSensor物件

UI介面也很簡單 就是一個Image接收Color Stream傳來的資料, 和一個可以儲存目前畫面的button
sensor的ColorFrameReady event有一個Handler, 隨時更新Color的資料給Image
結果如下:

其實就是像一個WebCam的功能一樣


  • DepthBasics-WPF
這個專案和剛才的ColorBasics很像, 只是有一些不一樣,
this.sensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
一開始會Enable DepthStream, 並指定格式
同樣的也為DepthFrameReady event加了一個Handler
this.sensor.DepthFrameReady += this.SensorDepthFrameReady;

結果如下:
 

DepthStream有兩種Range, 所以在介面上按下Near Mode選項, 這麼做

  if (this.checkBoxNearMode.IsChecked.GetValueOrDefault())
  {
    this.sensor.DepthStream.Range = DepthRange.Near;
  }
  else
  {
    this.sensor.DepthStream.Range = DepthRange.Default;
  }
從文件裏得知:

Default range from [800mm - 4000mm] inclusive.


Near range from [400mm - 3000mm] inclusive.

打開Near Mode的結果:

初步觀察:


  1. depth進來的數值為-1~4095 相當於 2的14次方如果我們把它除以16分佈到藍色和綠色的話, 可以得到這樣的影像:





  1. 場景中黑色物體 (如頭髮, 隔間牆的下擺) 因為會吸光 所以容易造成誤判
  2. 因為距離應該是用反射方式獲得, 所以每個東西會有類似影子的死角, 也就是如果有物件前後重疊時, 後面那個東西的偵測範圍會被前面的東西多擋到一些, 如圖中人的旁邊多了一片黑色的區域, 這個區域的大小會依據前後物件的距離成正比. 也就是如果兩個物件貼近時, 黑色區域會縮小甚至消失, 如果距離很大(例如人和牆的距離), 黑色區域也會變大.

  • D3D範例
在D3D中開啟Stream的方法和WPF中不太一樣
WPF:
newSensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30);

D3D:
 hr = m_pNuiSensor->NuiImageStreamOpen 

D3D正在用一套奇怪的Nui(Natural User Interface)程式庫, 感覺跟Kinect又有一點不直覺

static const int _MaxPlayerIndices = 8;
在ProcessDepth中
int player = depth & NUI_IMAGE_PLAYER_INDEX_MASK;
Kinect引擎自動將取到的玩家分開給深度值, 然後再用Shader去畫出不同顏色的玩家
static const float4 playerColorCoefficients[8] = 
{
    float4(1.0,  1.0,  1.0,  1.0), 
    float4(1.4,  0.8,  0.8,  1.0),
    float4(0.8,  1.4,  0.8,  1.0),
    float4(0.8,  0.8,  1.4,  1.0),
    float4(0.6,  1.2,  1.2,  1.0),
    float4(1.2,  0.6,  1.2,  1.0),
    float4(1.2,  1.2,  0.6,  1.0),
    float4(1.3,  0.9,  0.8,  1.0)
};
(注意這裏的Color Format有點奇怪, 是BGRA, 也就是藍色為先, 所以以玩家1來說是偏紫色)

這裏學到了兩個東西
m_hNextDepthFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
它讓我們在 RenderFrame的時候 去等
    if ( WAIT_OBJECT_0 == WaitForSingleObject(m_hNextDepthFrameEvent, 0) )
    {
        ProcessDepth();
    }
另一個東西是ID3DBlob* pBlob
它是DX10之後用來compile和create pixel shader和 vertex shader

低階的D3D程式細節很多, 有很長一段時間沒有接觸, 在此先針對Kinect的部份了解
  • 臉部辨視:
先看一下執行結果:

程式一開始並沒有自動出現人臉辨視, 推斷原因為距離Camera太近導致. 將身體往後靠之後即出現辨視後的人臉, 就算移回原先的距離也能持續偵測得到, 可以斷定深度偵測只用在一開始的追綜.
人臉辨視呈現的打光偏暗, 面向會隨著擺動, 嘴巴如果張開, 臉部模型也會跟著變化
程式解析:
本專案連結了另外兩個Reference, 一個是位於
D:\KinectSDK\Microsoft.Kinect.Toolkit\
產生 D:\KinectSDK\Microsoft.Kinect.Toolkit\obj\Debug\Microsoft.Kinect.Toolkit.dll
另一個是
D:\KinectSDK\Microsoft.Kinect.Toolkit.FaceTracking\
產生D:\KinectSDK\Microsoft.Kinect.Toolkit.FaceTracking\obj\Win32\Debug\Microsoft.Kinect.Toolkit.FaceTracking.dll

這個WPF用了練家子的一連串Data Binding和Event Handling來初始化物件, 不費點功夫還搞不清楚它的來龍去脈
一開始在MainWindow的constructor
  var kinectSensorBinding = new Binding("Kinect") { Source = this.sensorChooser};
  this.faceTrackingVisualizer.SetBinding(TexturedFaceMeshViewer.KinectProperty, kinectSensorBinding);
  this.sensorChooser.KinectChanged += this.SensorChooserOnKinectChanged;

newSensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
newSensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30);
newSensor.DepthStream.Range = DepthRange.Near;
newSensor.SkeletonStream.EnableTrackingInNearRange = true;
newSensor.SkeletonStream.TrackingMode = SkeletonTrackingMode.Seated;
newSensor.SkeletonStream.Enable();

其中DepthStream和SkeletonStream都要設成Near模式(如果只設一個會怎麼樣?)

在Microsoft.Kinect.Toolkit.FaceTracking的Utils.cs中 Feature Point有71個(0~108跳號)
比如:
        TopSkull                        = 0,
        TopRightForehead                = 1,
        MiddleTopDipUpperLip            = 7,
        AboveChin                       = 9,
        BottomOfChin                    = 10,
        RightOfRightEyebrow             = 15,
        MiddleTopOfRightEyebrow         = 16,
        LeftOfRightEyebrow              = 17,
        MiddleBottomOfRightEyebrow      = 18,
        AboveMidUpperRightEyelid        = 19,
        OuterCornerOfRightEye           = 20,
...

它在WPF程式中是以WPF的3D呈現臉部模型, 打光等設定也是在WPF中, 我把方向光和環境光的顏色改一下變成這樣:

2012年7月25日 星期三

終於出來了...折射程式

把rt1 TargetPass修改成Render_Scene之後, 終於出來了
(之前一直沒有搞懂 Render_Scene和Render_Quad的差別)

 CompositionTargetPass* target = technique->createTargetPass();
 target->setOutputName("rt1");
 target->setInputMode(Ogre::CompositionTargetPass::IM_NONE);
 target->setVisibilityMask(Visibility_ForDistort);
 {
  CompositionPass* pass = target->createPass();
  pass->setType(Ogre::CompositionPass::PT_CLEAR);
  pass->setClearBuffers(FBT_COLOUR);
  pass->setClearColour(Ogre::ColourValue(0.5f,0.5f,1.0f,1.0f));
 }//Pass
 {
  CompositionPass* pass = target->createPass();
  pass->setType(Ogre::CompositionPass::PT_RENDERSCENE);
  pass->setIdentifier(NormalPass);
 }//Pass

今天學到VisibilityMask的用法, 也就是在Viewport中, 可以對每個物件設VisibilityFlag, 然後在不同的TargetPass設定VisibilityMask, 就可以在不同的TargetPass出現不一樣的物件


這裏把Distortion設為0.02
ColorAdd設為0.5
所以OgreHead看起來有NormalMap的顏色出現
但如果我們不想要OgreHead出現的是NormalMap的顏色要怎麼辦?
也就是在扭曲的背景前面, 應該要再放一個正常版的OgreHead顏色
目前想到的方式就是再Render一次, 這次要用不同的Material Scheme
那麼, 是不是要用來扭曲的物體, 都要有特製的Material呢(要有不同的Scheme的Technique)?
實驗結果是: 對的


但是事情總是沒有一帆風順的
Color那一層出現了Z-fighting的情形,
於是將Color和Normal的Pass都Clear Depth

pass->setClearBuffers(Ogre::FBT_COLOUR | Ogre::FBT_DEPTH);

成功了

後續計劃:
改成compositor script版
改成Mogre版
OIT script版
OIT + Distort script版

2012年7月24日 星期二

所以我的折射程式...

進入正題,

我的折射程式經調查之後, 應該是留在程式裏...
它變成了 Ripple Compositor, 也就是水波紋程式

我把它複製起來, 改了一個名字叫 SSD, Screen Space Distortion
我們來看看要怎麼改回 折射的程式
首先, 我想到兩個問題,
第一個: 如何製作折射程度圖, 在畫折射圖的時候, 其他的東西都不要畫上去
第二個: 如何將折射圖放入折射Compositor 的 Material

一直到下班前, 大概的進度如下:
用程式產生 一個叫 RzSSD的Compositor, 最後一個OutputTargetPass為

CompositionTargetPass* output = technique->getOutputTargetPass();
{
 CompositionPass* pass = output->createPass();
 pass->setType(Ogre::CompositionPass::PT_RENDERQUAD);
 pass->setIdentifier(OutputTargetPass);
 pass->setMaterialName("Rz/Compositor/SpaceDistortPass");
 pass->setInput(0, "rt0");
 //pass->setInput(1, "rt1");
}

其中 material Rz/Compositor/SpaceDistortPass 長這個樣子


material Rz/Compositor/SpaceDistortPass
{
 technique Best
 {
  pass
  {
   depth_check off

   vertex_program_ref Ogre/Compositor/StdQuad_Cg_vp
   {
   }

   fragment_program_ref Rz/Compositor/SpaceDistortFP
   {
   }

   texture_unit RT
   {
                                tex_coord_set 0
    tex_address_mode clamp
    filtering linear linear linear
   }

   texture_unit Water
   {
    texture WaterNormal1.tga 2d
                                tex_coord_set 1
    //tex_address_mode clamp
    filtering linear linear linear
   }   

   texture_unit SSD_Normal
   {
    //texture SSD_Normal
                                tex_coord_set 1
    //tex_address_mode clamp
    filtering linear linear linear
   }
  }
 }
}

原本應該是Normal圖的地方 變成了 Water圖
fragment_program Rz/Compositor/SpaceDistortFP長這個樣子:
float4 SpaceDistort_FP( 
    float2 iTexCoord : TEXCOORD0
   ,uniform float  Distortion
   ,uniform float  ColorAdd
        ) : COLOR
{
 float4 CompressNormal = tex2D(NormalMap, iTexCoord);
 float4 Normal = CompressNormal *2.0 - 1.0;
 //float4 reflect
 
 float2 UV = clamp(iTexCoord + Normal.xy* Distortion,0.01,1.0);
 Normal.z = 0;
 
 return tex2D(RT, UV) + Normal * ColorAdd;
}

所以程式執行起來就像是被Water圖扭曲過一樣

失敗的是原本用來當做扭曲Normal的OgreHead沒有消失, 也不知道怎麼畫在Normal圖上
明天繼續奮鬥

2012年7月23日 星期一

我的Ogre 3D折射程式到那兒去了(下)

接上集

  • TimeSinunoid
試著將參數從shader外傳入
sampler g_samSrcColor : register(s0);

float4 TimeSinunoid_ps (
 float2 Tex : TEXCOORD0,
 uniform float  littleTimer
) : COLOR
{
 Tex.y = Tex.y + (sin(littleTimer + Tex.x * 10.0) * 0.01);
 Tex.x = Tex.x + (cos(littleTimer + Tex.x * 10.0) * 0.03);
 float4 Color = tex2D( g_samSrcColor, Tex.xy);
 return Color;
}
記得宣告參數
fragment_program Rz/Compositor/TimeSinusoidFP cg
{
 source TimeSinusoidFP.cg
 entry_point TimeSinusoid_ps
 profiles ps_2_0 arbfp1
 default_params
 {
  param_named_auto littleTimer time
 }
}
這裏我們用 Ogre3D的 param_named_auto 語法 將參數設為time的自動參數
(詳細參數列表請看 Ogre的Manual)
執行的結果像這樣

它會隨著時間的變化 扭動畫面

  • ManySample
我們可以重覆從tex2D取出圖片的不同部位纍加在一起

sampler g_samSrcColor : register(s0);

float4 ManySample_ps (
 float2 Tex : TEXCOORD0,
 uniform float  littleTimer
) : COLOR
{ 
 float4 Color = tex2D( g_samSrcColor, Tex.xy);
 Color += tex2D( g_samSrcColor, Tex.xy + 0.001);
 Color += tex2D( g_samSrcColor, Tex.xy + 0.002);
 Color += tex2D( g_samSrcColor, Tex.xy + 0.003);
 return Color;
}
效果會像這樣有點糊掉 亮度變好幾倍
要是我們將它除以纍加的次數
return Color/4.0;
那就變成了模糊的效果

利用sampler的加減運算我們可以做出一些奇特的效果 像邊緣銳利化
sampler g_samSrcColor : register(s0);

float4 ManySample_ps (
 float2 Tex : TEXCOORD0,
 uniform float  littleTimer
) : COLOR
{ 
 float4 Color = tex2D( g_samSrcColor, Tex.xy);
 Color += tex2D( g_samSrcColor, Tex.xy + 0.001)*10.0;
 Color -= tex2D( g_samSrcColor, Tex.xy - 0.001)*10.0;
 return Color;
}

或是浮雕效果
sampler g_samSrcColor : register(s0);

float4 ManySample_ps (
 float2 Tex : TEXCOORD0,
 uniform float  littleTimer
) : COLOR
{ 
 float4 Color;
 Color.rgb = 0.5;
 Color += tex2D( g_samSrcColor, Tex.xy + 0.001)*2.0;
 Color -= tex2D( g_samSrcColor, Tex.xy - 0.001)*2.0;
 Color.rgb = (Color.r + Color.g + Color.b)/3.0;
 Color.a = 1.0;
 return Color;
}

拼色版效果
sampler g_samSrcColor : register(s0);

float4 ManySample_ps (
 float2 Tex : TEXCOORD0,
 uniform float  littleTimer
) : COLOR
{ 
 float4 Color  =tex2D( g_samSrcColor, Tex.xy );

 if(Color.r < 0.2 || Color.r > 0.8) 
  Color.r = 0; 
 else 
  Color.r = 1.0;
 if(Color.g < 0.2 || Color.g > 0.8) 
  Color.g = 0; 
 else 
  Color.g = 1.0;
 if(Color.b < 0.2 || Color.b > 0.8) 
  Color.b = 0; 
 else 
  Color.b = 1.0;
 
 return Color;
}
還有黑白效果
sampler g_samSrcColor : register(s0);

float4 ManySample_ps (
 float2 Tex : TEXCOORD0,
 uniform float  littleTimer
) : COLOR
{ 
 float4 Color  =tex2D( g_samSrcColor, Tex.xy );
 Color.rgb = (Color.r + Color.g + Color.b)/3.0;

 if(Color.r < 0.2 || Color.r > 0.8) 
  Color.rgb = 0; 
 else 
  Color.rgb = 1.0;
 
 return Color;
}

但是, 我的折射程式到底在那裏呢?

2012年7月20日 星期五

我的Ogre 3D 折射程式到那兒去了(上)


之前寫折射的時候沒有記錄, 所以完全忘了怎麼寫了…
重新開始寫吧
To do list:
  • 整理 RzMaterial
  • 寫成 Compositor
  • Refraction ? Distortion?
RzApp::switchCompositor 為目前可以開關的機制
目前可用的 Compositor為
Example.compositor
  • Bloom
  • Glass
  • “Old TV"
  • B&W
  • Embossed
  • “Sharpen Edges"
  • Invert
  • Posterize
  • Laplace
  • Tiling
  • “Old Movie"
  • HDR
  • “Gaussian Blur"
  • TestMRT
  • “Radial Blur"
  • ASCII
  • Halftone
  • “Night Vision"
  • Dither
Rz.compositor
  • Rz/Compositor/GaussianBlurNoBG
其中HeatVision是完全寫在Code裏的
從Ogre Manual上看來的一些基礎知識做個筆記:

3.2.1 Techniques

compositor_logic 可以放一個名字 比如說 HDR, 到了程式中再把這個名字代換成真正物件的名稱

Ogre::CompositorManager& compMgr = Ogre::CompositorManager::getSingleton();

compMgr.registerCompositorLogic("HDR", new HDRLogic);

class HDRLogic : public ListenerFactoryLogic
{
protected:
virtual Ogre::CompositorInstance::Listener* createListener(Ogre::CompositorInstance* instance);
};
Target Pass 是一個Compositor中最重要的元件, 它可以有以下東西:
  • input
  • only_initial
  • visibility_mask
  • lod_bias
  • material_scheme
  • shadows
  • pass
其中最重要的, 又是這個叫pass的東西, 由先它的格式為:
‘pass’ (render_quad | clear | stencil | render_scene | render_custom) [custom name] { }
這裏來個最最簡單的compositor示範

compositor Rz/Compositor/SimplePixel
{
    technique
    {
        texture rt0 target_width target_height PF_A8R8G8B8
  
        target rt0
        {
            input previous
        }

        target_output
        {
            input none
   
            pass render_quad
            {
                material Rz/Compositor/Material/Invert
                input 0 rt0
            }
        }
    }
}

這裏用了一個material Rz/Compositor/Material/SimplePixel

讓我們繼續追踪下去:

material Rz/Compositor/Material/Invert
{
 technique
 {

  pass
  {
   cull_hardware none
   cull_software none
   depth_func always_pass

   vertex_program_ref Ogre/Compositor/StdQuad_Tex2a_vp
   {
   }

   fragment_program_ref Rz/Compositor/InvertFP
   {
   }

   texture_unit RT
   {
                          tex_coord_set 0
     tex_address_mode clamp
     filtering trilinear
   }

  }
 }
}



這個vertex shader很簡單, 是個公版
void StdQuad_Tex2a_vp
(
    in float4 inPos : POSITION,

    out float4 pos : POSITION,
    out float2 uv0 : TEXCOORD0,
    out float2 uv1 : TEXCOORD1,

    uniform float4x4 worldViewProj
)
{
    // Use standardise transform, so work accord with render system specific (RS depth, requires texture flipping, etc)
    pos = mul(worldViewProj, inPos);

    // The input positions adjusted by texel offsets, so clean up inaccuracies
    inPos.xy = sign(inPos.xy);

    // Convert to image-space
    uv0 = (float2(inPos.x, -inPos.y) + 1.0f) * 0.5f;
    uv1 = inPos.xy;
}


回到material來, 它的fragment program 是主要的shader程式
fragment_program Rz/Compositor/InvertFP cg
{
 source RzInvertFP.cg
 entry_point Invert_ps
 profiles ps_2_0 arbfp1
}

裏面是長這樣
sampler RT : register(s0);

float4 Invert_ps (float2 iTexCoord : TEXCOORD0) : COLOR
{
 return 1 - tex2D(RT, iTexCoord);
}

今天我們來試試其他簡單的pixel shader, 以下是參考網站(Facewound)

首先來個3倍亮度:
把 compositor裏的material
material Rz/Compositor/Material/Invert
改成
material Rz/Compositor/Material/TrippleColor
那麼我們也要新增一個相對應的material script (TrippleColor.material)給它:
fragment_program Rz/TrippleColorFP cg
{
 source TrippleColorFP.cg
 entry_point TrippleColor_ps
 profiles ps_2_0 arbfp1
}

material Rz/Compositor/Material/TrippleColor
{
 technique
 {

  pass
  {
   cull_hardware none
   cull_software none
   depth_func always_pass

   vertex_program_ref Ogre/Compositor/StdQuad_Tex2a_vp
   {
   }

   fragment_program_ref Rz/TrippleColorFP
   {
   }

   texture_unit RT
   {
                           tex_coord_set 0    
      tex_address_mode clamp
      filtering trilinear
   }

  }
 }
}

這麼一來, 又要新增一個 fragment_program (trippleColorFP.cg)給它
sampler RT : register(s0);

float4 TrippleColor_ps (float2 iTexCoord : TEXCOORD0) : COLOR
{
 return tex2D(RT, iTexCoord) * 3;
}
結果變成

同樣的原理, 我們可以嚐試很多不一樣的東西
比如改成 material Rz/Compositor/Material/GradientY
新增 GradientYFP.cg
sampler RT : register(s0);

float4 GradientY_ps (float2 iTexCoord : TEXCOORD0) : COLOR
{
 return tex2D(RT, iTexCoord) * iTexCoord.y;
}

畫面就會變成根據Y從0到1的漸層

再來, 試試將紅色變成2倍, 過程和細節就不再重覆
只寫出 pixel shader的部份
sampler RT : register(s0);

float4 DoubleRed_ps (float2 iTexCoord : TEXCOORD0) : COLOR
{
 float4 Color=tex2D(RT, iTexCoord);
 Color.r = Color.r * 2.0;
 return  Color;
}
結果如下:
哈哈
再試一個
  • CrazySin的效果吧
sampler g_samSrcColor : register(s0);

float4 CrazySin_ps (float2 Tex : TEXCOORD0) : COLOR
{
  float4 Color = tex2D( g_samSrcColor, Tex.xy);
  Color.r = Color.r*sin(Tex.y*100)*2;
  Color.g = Color.g*cos(Tex.y*200)*2;
  Color.b = Color.b*sin(Tex.y*300)*2;
  return Color;
}


是不是很Crazy
  • HalfU
sampler g_samSrcColor : register(s0);

float4 HalfU_ps (float2 Tex : TEXCOORD0) : COLOR
{
 Tex.x = Tex.x * 0.5;
 float4 Color = tex2D( g_samSrcColor, Tex.xy);
 return Color;
}


雖然名為HalfU 但圖看起變成橫幅兩倍大
  • SinusoidV
sampler g_samSrcColor : register(s0);

float4 SinusoidV_ps (float2 Tex : TEXCOORD0) : COLOR
{
 Tex.y = Tex.y + (sin(Tex.x * 200) * 0.01);
 float4 Color = tex2D( g_samSrcColor, Tex.xy);
 return Color;
}