Unity Projector 投影器原理以及優(yōu)化

本文轉(zhuǎn)自Unity Connect博主 dreamfairy

先上成平圖

測(cè)試效果圖舔亭, 圖中的褲子上投影了一個(gè)眼睛

那么投影的原理是什么呢检疫。厘熟。嘲恍。 那么請(qǐng)看下面這張

這張圖左下角就是投影器看到的景象息堂,投影貼圖“眼睛” 充滿了整個(gè)投影器的視野嚷狞,那么原理就呼之而出了。

在正常渲染褲子的頂點(diǎn)時(shí)荣堰,順便變換到投影器的屏幕空間床未,然后再渲染褲子的片段處理函數(shù)中將位于投影器屏幕空間的像素都換成眼睛即可。

渲染褲子的Shader

Shader "Unlit/ProjectorShader"

{

? ? Properties

? ? {

? ? ? ? _MainTex ("Texture", 2D) = "white" {}

? ? }

? ? SubShader

? ? {

? ? ? ? Tags { "RenderType"="Opaque" }

? ? ? ? Pass

? ? ? ? {

? ? ? ? ? ? CGPROGRAM

? ? ? ? ? ? #pragma vertex vert

? ? ? ? ? ? #pragma fragment frag


? ? ? ? ? ? #include "UnityCG.cginc"

? ? ? ? ? ? struct appdata

? ? ? ? ? ? {

? ? ? ? ? ? ? ? float4 vertex : POSITION;

? ? ? ? ? ? ? ? float2 uv : TEXCOORD0;

? ? ? ? ? ? };

? ? ? ? ? ? struct v2f

? ? ? ? ? ? {

? ? ? ? ? ? ? ? float2 uv : TEXCOORD0;

? ? ? ? ? ? ? ? float4 projectorUV : TEXCOORD1;

? ? ? ? ? ? ? ? float4 vertex : SV_POSITION;

? ? ? ? ? ? };

? ? ? ? ? ? float4x4 _ProjectorP;

? ? ? ? ? ? float4x4 _ProjectorV;

? ? ? ? ? ? float4x4 _ProjectorVP;

? ? ? ? ? ? sampler2D _ProjectorTex;

? ? ? ? ? ? sampler2D _ProjectorFallOut;

? ? ? ? ? ? sampler2D _MainTex;

? ? ? ? ? ? float4 _MainTex_ST;


? ? ? ? ? ? v2f vert (appdata v)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? v2f o;

? ? ? ? ? ? ? ? o.vertex = UnityObjectToClipPos(v.vertex);

? ? ? ? ? ? ? ? o.uv = TRANSFORM_TEX(v.uv, _MainTex);

? ? ? ? ? ? ? ? float4x4 propMVP = mul(_ProjectorVP, unity_ObjectToWorld);

? ? ? ? ? ? ? ? float4 projProjPos = mul(propMVP, v.vertex);

? ? ? ? ? ? ? ? projProjPos = ComputeScreenPos(projProjPos);

? ? ? ? ? ? ? ? o.projectorUV = projProjPos;

? ? ? ? ? ? ? ? return o;

? ? ? ? ? ? }


? ? ? ? ? ? fixed4 frag (v2f i) : SV_Target

? ? ? ? ? ? {

? ? ? ? ? ? ? ? // sample the texture

? ? ? ? ? ? ? ? fixed4 col = tex2D(_MainTex, i.uv);


? ? ? ? ? ? ? ? fixed4 projectorCol = tex2Dproj(_ProjectorTex, i.projectorUV);

? ? ? ? ? ? ? ? // tex2Dproj = xyz/ w

? ? ? ? ? ? ? ? fixed4 projectorFallOutCol = tex2Dproj(_ProjectorFallOut, i.projectorUV);

? ? ? ? ? ? ? ? projectorCol *= projectorFallOutCol;

? ? ? ? ? ? ? ? col.rgb += projectorCol.rgb;

? ? ? ? ? ? ? ? return col;

? ? ? ? ? ? }

? ? ? ? ? ? ENDCG

? ? ? ? }

? ? }

}

自定義投影器的CS腳本

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

[ExecuteInEditMode]

public class CustomProjector : MonoBehaviour {

? ? public Camera ProjectorCam;

? ? public Texture2D Tex;

? ? public Texture2D FallOut;

? ? public Material Mat;

? ? private void Start()

? ? {


? ? }

? ? private void Update()

? ? {

? ? ? ? Matrix4x4 P = GL.GetGPUProjectionMatrix(ProjectorCam.projectionMatrix, false);

? ? ? ? Matrix4x4 V = ProjectorCam.worldToCameraMatrix;

? ? ? ? Mat.SetMatrix("_ProjectorV", V);

? ? ? ? Mat.SetMatrix("_ProjectorP", P);

? ? ? ? Mat.SetMatrix("_ProjectorVP", P * V);

? ? ? ? Mat.SetTexture("_ProjectorTex", Tex);

? ? ? ? Mat.SetTexture("_ProjectorFallOut", FallOut);

? ? }

}

范例中的代碼借用了Unity的相機(jī)振坚,實(shí)際上并不需要相機(jī)薇搁,僅僅是借用了相機(jī)的投影矩陣和世界空間矩陣而已。

很多項(xiàng)目組在制作移動(dòng)端游戲時(shí)渡八,都使用Projector來(lái)制作主角的投影啃洋,雖然比起ShadowMap是優(yōu)化了許多,但是實(shí)際上只要和Projector碰撞到物件其DC 都會(huì)翻倍屎鳍, 對(duì)于我來(lái)說(shuō)宏娄,這還是不可接受的。

而使用上面范例的代碼逮壁,可以讓DC不翻倍孵坚,但是并不通用, 因?yàn)槭躊rojector影響的物體都需要定制Shader.

Unity自帶Projector會(huì)翻倍的原因主要也是通用性窥淆,跨平臺(tái)卖宠,使用方便, 因此它的原理是

1.找到所有和Projector有碰撞的MeshRenderer

2.使用Projector的材質(zhì)球忧饭,將MeshRenderer的頂點(diǎn)再渲染一遍扛伍,并貼圖

也因此被投影的物體無(wú)法觸發(fā)動(dòng)態(tài)合批

那么問題來(lái),有沒有一種方案眷昆,既可以保證通用性蜒秤,不需要定制被投影目標(biāo)的Shader,又可以使DC不翻倍呢? 答案是:有的亚斋, 但是有代價(jià)

代價(jià)1:需要使用深度圖

代價(jià)2:DC不會(huì)翻倍作媚,但是總共的DC為,被投影物體數(shù)量 + 1. 既物體自帶的DC + 1 * (Projector數(shù)量) 其實(shí)代價(jià)2根本不算個(gè)事

說(shuō)搞就搞

1.開啟相機(jī)的深度渲染

Camera.main.depthTextureMode |= DepthTextureMode.Depth;

2.創(chuàng)建一個(gè)表示投影器范圍的網(wǎng)格帅刊,我搞了個(gè)Cube Mesh

3.創(chuàng)建Cube Mesh對(duì)應(yīng)的相關(guān)矩陣纸泡,因?yàn)槭荂ube 因此創(chuàng)建的投影為正交投影, 當(dāng)然赖瞒,如果也可以使用透視投影女揭。

? ? ? ? BoxCollider collider = this.GetComponent<BoxCollider>();

? ? ? ? this.m_size = collider.size.x / 2;

? ? ? ? this.m_nearClip = -collider.size.x / 2;

? ? ? ? this.m_farClip = collider.size.x / 2;

? ? ? ? this.m_aspect = 1;

? ? ? ? Matrix4x4 projector = default(Matrix4x4);

? ? ? ? projector = Matrix4x4.Ortho(-m_aspect * m_size, m_aspect * m_size, -m_size, m_size, m_nearClip,? ? ? ? ? ? ? ? m_farClip);

? ? ? ? m_worldToProjector = projector * this.transform.worldToLocalMatrix;

? ? ? ? MeshRenderer mr = this.GetComponent<MeshRenderer>();

? ? ? ? mr.sharedMaterial.SetMatrix("_WorldToProjector", m_worldToProjector);

好了蚤假,準(zhǔn)備工作做完了。開始渲染吧吧兔。

首先是將投影器覆蓋的區(qū)域磷仰,采樣出當(dāng)前屏幕空間的深度,類似這樣的效果

要實(shí)現(xiàn)這樣的效果境蔼,就是講頂點(diǎn)變換到投影平面灶平,并將坐標(biāo)變換到UV值域下

大概這樣

vert part

o.screenPos = ComputeScreenPos(o.vertex);

fragment part

fixed4 screenPos = i.screenPos;

screenPos.xy = screenPos.xy / screenPos.w;

float depth = tex2D(_CameraDepthTexture, screenPos).r;

好了,現(xiàn)在我們有了深度箍土,下一部就是講當(dāng)前像素的深度還原回該深度對(duì)應(yīng)的世界坐標(biāo)了

只需要兩部矩陣變換

1.從屏幕空間變換到相機(jī)空間 unity_CameraInvProjection

2.從相機(jī)空間變換到世界空間 unity_MatrixInvV

有了世界坐標(biāo)后逢享,就可以將該坐標(biāo)變換到Projector的控件,就是準(zhǔn)備工作中的 _WorldToProjector

變換到Projector空間后吴藻,還記得范例上的投影器的全部視野就是需要投射的貼圖范圍嗎瞒爬?因此這里要做UV值域的變換

//變換到自定義投影器投影空間

fixed4 projectorPos = mul(_WorldToProjector, worldSpacePos);

projectorPos /= projectorPos.w;

fixed2 projUV = projectorPos.xy * 0.5 + 0.5;? //變換到uv坐標(biāo)系

fixed4 col = tex2D(_ProjectorTex, projUV);? //采樣投影貼圖

fixed4 mask = tex2D(_ProjectorTexMask, projUV); //采樣遮罩貼圖

col.rgb =? lerp(fixed3(1, 1, 1), col.rgb, (1 - mask.r)); //融合

大功告成! 你可能會(huì)好奇沟堡,為什么多了一個(gè)遮罩貼圖侧但? 雖然你講投影器視野內(nèi)的像素部分都貼了投影貼圖,但是是野外的像素怎么辦弦叶?這個(gè)時(shí)候就需要遮罩圖抹掉俊犯,因此遮罩圖的紋理設(shè)置要設(shè)置為Clamp,保證邊緣像素為拉伸且外側(cè)的Alpha為0

項(xiàng)目完整源碼:https://github.com/dreamfairy/Unity-CubeProjector

原文鏈接:https://connect.unity.com/p/unity-projector-tou-ying-qi-yuan-li-yi-ji-you-hua?app=true

歡迎戳上方原文鏈接,下載Unity官方技術(shù)社區(qū)app伤哺,發(fā)現(xiàn)更多資源干貨~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市者祖,隨后出現(xiàn)的幾起案子立莉,更是在濱河造成了極大的恐慌,老刑警劉巖七问,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜓耻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡械巡,警方通過(guò)查閱死者的電腦和手機(jī)刹淌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)讥耗,“玉大人有勾,你說(shuō)我怎么就攤上這事」懦蹋” “怎么了蔼卡?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)挣磨。 經(jīng)常有香客問我雇逞,道長(zhǎng)荤懂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任塘砸,我火速辦了婚禮节仿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掉蔬。我一直安慰自己廊宪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布眉踱。 她就那樣靜靜地躺著挤忙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谈喳。 梳的紋絲不亂的頭發(fā)上册烈,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音婿禽,去河邊找鬼赏僧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扭倾,可吹牛的內(nèi)容都是我干的淀零。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼膛壹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼驾中!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起模聋,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肩民,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后链方,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體持痰,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年祟蚀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了工窍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡前酿,死狀恐怖患雏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薪者,我是刑警寧澤纵苛,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響攻人,放射性物質(zhì)發(fā)生泄漏取试。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一怀吻、第九天 我趴在偏房一處隱蔽的房頂上張望瞬浓。 院中可真熱鬧,春花似錦蓬坡、人聲如沸猿棉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萨赁。三九已至,卻和暖如春兆龙,著一層夾襖步出監(jiān)牢的瞬間杖爽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工紫皇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慰安,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓聪铺,卻偏偏與公主長(zhǎng)得像化焕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铃剔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容