體感Kinect結合Unity3D引擎開發虛擬現實AR 頂 原 薦

        2015年的第一場源創會,由 @愛吃魚的貓大哥 做了一篇虛擬現實—我們能做的其實很多的豬蹄,主要介紹了增強現實框架-MetaioSDK開發包,我想大家對於這個主題肯定吃的還不夠過癮,那麼今天就在來點猛料,滿足大家慾望!由於公司業務需要,我也用這個sdk做過Android上一個虛擬現實app小例子,畢竟收費,有一些功能不付費體驗不到,在加上後面跟上需要做一個大型戶外體驗的虛擬項目,綜合考慮選擇微軟體感Kinect結合Unity3D引擎開發虛擬現實AR! @紅薯 是不是後面考慮讓我參加一期嘉賓演講了,哈哈!廢話不多扯,直接上乾貨!

       首先上好豬蹄和各種佐料:

       

        大概的流程:Kinect打開之後運行,unity通過綁定的腳本會首先獲取RGB流和Depth流,以及骨骼數據(主要爲識別手勢和人多位置做準),其次通過腳本獲取各種手勢,以及位置,比如SwipeRight沒右手揮動一次,就會切換一個例子場景直接與實時拍攝的場景完美融合(效果圖稍後呈上),包括RaiseRightHand會出現流星撞地面,感覺像世界末日的感覺。最後就是同時利用unity的GuiTexture實時顯示RGB流!說了那麼多先上一張效果圖感受下激情摧毀辦公室:

     好了,材料準備好了,該看的都看了,接下來重點來了,就是放乾貨一步一步看看怎麼做出來:

       第一步,也是最重要的一步,就是實現3D模型顯示在平面2D圖片上面,這個用unity真的很方便,首先需要添加一個攝像機Camera,並在攝像機Depth屬性設置爲-1,在MainCamera設置爲0,說明背景層是最深,其他model所在的相機的Depth是0,這樣確保3D模型在背景的前面!這裏面有一點需要注意的就是一定要保證MainCamera和所有的模型的layer都在Defaule層!(在這裏不得不說unity真的很方便)!

      在第一步基礎上,第二步實現把Kinect獲得RGB流顯示出來,這個如果大家不會封裝可以利用Kinect結合Unity3D引擎開發體感遊戲(一) 裏面的卡耐基梅隆的kinectWrapper.unitypackage,我這個就是在這個基礎上修改的拓展了不少新功能,目前可以支持KinectSdk1.8版本!如果你比較熟悉unity,可以這樣做:

   注:kinect封裝腳本代碼過多,這裏接貼出一些關鍵點,不是很清楚,可以看卡耐基梅隆包,裏面實現方法都差不多

void Update () 
{
KinectManager manager = KinectManager.Instance;//RGB流封裝在這個腳本里面
		if(manager && manager.IsInitialized())
		{
		        //利用GetUsersClrTex()函數獲得Texture2D的RGB流!
			this.renderer.material.mainTexture = manager.GetUsersClrTex();
			
/*******************在KinectManager腳本里面函數實現*********************/
 public Texture2D GetUsersClrTex()
    { 
	return usersClrTex;
    }
/*******************************************/

到了這一步,就可以把這個腳本直接綁定到Plane對象!這個時候RGB流就顯示出來了!如果你和我一樣用plane對象,有一個地方需要注意,就是它的屬性的Tiling的x設置爲-1(這裏在扯一句廢話,不清楚的可以先了解下unity怎麼用,在來看可能更快)。

     第三步,在第二步基礎上,綁定Tornado(龍捲風)模型,綁定這個模型是爲了讓Tornado跟着人一起平移運動,人道哪裏,Tornado就跟隨着!直接看代碼:

     

void Update () 
	{
		KinectManager manager = KinectManager.Instance;
		if(manager && manager.IsInitialized())
		{
			this.renderer.material.mainTexture = manager.GetUsersClrTex();
		
			int iJointIndex = (int)TrackedJoint;
			
			if(manager.IsUserDetected())
			{
		    uint userId = manager.GetPlayer1ID();		
		    if(manager.IsJointTracked(userId, iJointIndex))
			{
			//獲得需要綁定骨骼點的X,Y,Z座標,綁定Tornado
		      Vector3 posJoint = manager.GetRawSkeletonJointPos(userId, iJointIndex);
		      Vector2 posDepth = manager.GetDepthMapPosForJointPos(posJoint);	
		      Vector2 posColor = manager.GetColorMapPosForDepthPos(posDepth);
					
		      float scaleX = (float)posColor.x / KinectWrapper.Constants.ImageWidth;
		float scaleY = 1.0f - (float)posColor.y / KinectWrapper.Constants.ImageHeight;				
                      Vector3 localPos = new Vector3(scaleX * 10f - 5f, 0f, scaleY * 10f - 5f);
		      Vector3 vPosOverlay = transform.TransformPoint(localPos);
					
                                        //保證Tornado一直是水平移動
					if(OverlayObject)
					{
						double num_y = -11.9;
						double num_z = -4.52;
						vPosOverlay.y = (float)num_y;
						vPosOverlay.z = (float)num_z;
						OverlayObject.transform.position = vPosOverlay;
					}

				}
				
			}
			
		}
	}

這裏在扯一句廢話,這個代碼編輯,有很大的提高空間!

      第四步,這個讓Tornado已經隨人一起移動,接下來實現RaiseRightHand和SwipeRight切換粒子場景,直接上代碼

//這個就是手勢判定產生動作切換場景代碼
if(slideChangeWithGestures && gestureListener)
			{
				if(gestureListener.IsSwipeLeft())
				{
					m_CurrentElementIndex++;
					m_CurrentParticleIndex = 0;
					ShowParticle();
					RotateToNext();
				}
				else if(gestureListener.IsSwipeRight())
				{
					m_CurrentElementIndex++;
					m_CurrentParticleIndex = 0;
					ShowParticle();
					RotateToPrevious();
				}
				else if(gestureListener.IsRaiseRightHand())
				{
					m_CurrentElementIndex = 6;;
					m_CurrentParticleIndex = 0;
					ShowParticle();
					RotateToPrevious();
				}
				else if(gestureListener.IsRaiseLeftHand())
				{
					m_CurrentElementIndex = 7;
					m_CurrentParticleIndex = 0;
					ShowParticle();
					RotateToPrevious();
				}
	}

那麼裏面的ShowParticle() 函數和  RotateToPrevious()函數的作用分別是:

ShowParticle() :主要切換顯示不同場景粒子效果,比如Rain、Snow、Lighting等效果。上實現代碼:

#region Functions
	void ShowParticle()
	{
		if(m_CurrentElementIndex == 6 && m_CurrentElementIndex == 7)
		{
			;
		}
		else if(m_CurrentElementIndex<0)
		{
			m_CurrentElementIndex = 5;
		}
		
		if(m_CurrentElementIndex==0)
		{
			music.Stop();
			m_CurrentElementList = m_PrefabListFire;
			m_ElementName = "FIRE";
			music.PlayOneShot(theSound);
			//music.Play(); 
		}
		else if(m_CurrentElementIndex==1)
		{
			music.Stop();
			m_CurrentElementList = m_PrefabListWater;
			m_ElementName = "WATER";
			music.PlayOneShot(theSound);
			//music.Play(); 
		}
		else if(m_CurrentElementIndex==2)
		{
			music.Stop();
			m_CurrentElementList = m_PrefabListWind;
			m_ElementName = "WIND";
			music.PlayOneShot(theSound);
			//music.Play(); 
		}
		else if(m_CurrentElementIndex==6)
		{
			music.Stop();
			m_CurrentElementList = m_PrefabListEarth;
			m_ElementName = "EARTH";
			music.PlayOneShot(theSound);
			//music.Play(); 
		}
		else if(m_CurrentElementIndex==3)
		{
			music.Stop();
			m_CurrentElementList = m_PrefabListThunder;
			m_ElementName = "THUNDER";
			//music.PlayOneShot(theSound);
			music.Play(); 
		}
		else if(m_CurrentElementIndex==4)
		{
			music.Stop();
			m_CurrentElementList = m_PrefabListIce;
			m_ElementName = "ICE";
			music.PlayOneShot(theSound);
			//music.Play(); 
		}
		else if(m_CurrentElementIndex==5)
		{
			music.Stop();
			m_CurrentElementList = m_PrefabListLight;
			m_ElementName = "LIGHT";
			music.PlayOneShot(theSound);
			//music.Play(); 
		}
		else if(m_CurrentElementIndex==7)
		{
			music.Stop();
			m_CurrentElementList = m_PrefabListDarkness;
			m_ElementName = "DARKNESS";
			music.PlayOneShot(theSound);
			//music.Play(); 
		}

		if(m_CurrentElementIndex == 5 || m_CurrentElementIndex == 6 || m_CurrentElementIndex == 7)
		{
			m_CurrentElementIndex = 0;
		}
		
		if(m_CurrentParticleIndex>=m_CurrentElementList.Length)
		{
			m_CurrentParticleIndex = 0;
		}
		else if(m_CurrentParticleIndex<0)
		{
			m_CurrentParticleIndex = m_CurrentElementList.Length-1;
		}
		
		m_ParticleName = m_CurrentElementList[m_CurrentParticleIndex].name;
		
		if(m_CurrentParticle!=null)
		{
			DestroyObject(m_CurrentParticle);
		}

		m_CurrentParticle = (GameObject) Instantiate(m_CurrentElementList[m_CurrentParticleIndex]);

		music.volume = musicVolume; 
	}
	#endregion {Functions}

RotateToPrevious():主要也是切換場景,不過不是粒子,是爲了切換一個立方體,以提示切換場景成功信號(主要是考慮用戶體驗性)!還是上代碼吧!

private void RotateToNext()
	{
		tex = (tex + 1) % maxTextures;
		
		if(!isBehindUser)
		{
			side = (side + 1) % maxSides;
		}
		else
		{
			if(side <= 0)
				side = maxSides - 1;
			else
				side -= 1;
		}

		if(horizontalSides[side] && horizontalSides[side].renderer)
		{
			horizontalSides[side].renderer.material.mainTexture = slideTextures[tex];
		}
		
		float yawRotation = !isBehindUser ? 360f / maxSides : -360f / maxSides;
		Vector3 rotateDegrees = new Vector3(0f, yawRotation, 0f);
		targetRotation *= Quaternion.Euler(rotateDegrees);
		isSpinning = true;
	}

然後將腳本綁定到camera或者自己需要的對象物體上,這樣就可以控制切換場景了,至於手勢判定,這個只需要你獲得了骨骼數據了,是很好寫出來手勢判定的,這裏就不贅述了!

     第五步,我們就看看效果吧,直接上圖:

下圖爲:流星撞地球,激情毀滅辦公室


下圖爲閃電效果:


下圖爲龍捲風效果:

下圖爲下雨效果:


真正的效果,可能截圖感覺不是逼真,但是在程序跑的時候,感覺會好很多,圖像不是很清晰主要一是kinect的分辨率只有640*480,二來室內還沒開燈,有一張閃電圖片就是開燈了,就會清楚多!


後期計劃:加入高清攝像頭,使2D畫面更加清晰;

         添加更多音效;

         加入更多手勢動作判斷,更多互動使其更好玩。


這篇文章主要比較簡單介紹了下虛擬現實,希望可以起到一個拋磚引玉的作用,歡迎指正,謝謝!



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章