您當前的位置:首頁 > 攝影

案例學習——Interior Mapping 室內對映(假室內效果)

作者:由 清清 發表于 攝影時間:2021-05-31

最近油管推薦了Interior Mapping的教程,發現很有意思

好像各種資料都比較零散,於是到處蒐集了一些,有了這篇文章彙總,一起學習學習

0 效果展示

因為有點長,所以先展示一些效果

以一張2d投影圖為基底,用alpha值存深度&手動指定深度,在圖集中隨機生成,以及加上窗框,

菲涅爾效應

,窗戶汙垢等等

幾個樣例效果展示(場景中只有基本幾何形狀,是沒有那些室內模型的)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

深度的調整

案例學習——Interior Mapping 室內對映(假室內效果)

1 背景介紹——虛假的窗戶

什麼是Interior Mapping?我們先從遊戲裡的窗戶開始說起

這是GTA IV的中的一個傢俱店

案例學習——Interior Mapping 室內對映(假室內效果)

我們仔細看看,可以發現

它在美術上很好看,在視覺上的顏色啊搭配啊也很有趣,適合傢俱店的主題和位置,氛圍感也很好……只是有些不對勁。

透過窗戶,我們能看到的只是一張商店的圖片,就像玻璃上的貼紙一樣,直接拍到了窗玻璃上。 在轉角的不同部分上的各個視窗之間,並沒有透視差異。 即使相機與牆成一定角度,內部的視野也始終是朝向正面的。 這種透視缺失的效果大大削弱了氛圍感。

讓房間出現在窗戶後面,最簡單的方法就是用實際的模型填充每個房間。

這對小規模的場景來說是適用的,但對於大型遊戲顯然是不切實際的。

這些內部模型三角形面片所產生的消耗,對於大型遊戲來說實在太誇張了,尤其是我們在遊玩時往往只會偶爾看到少數幾個房間的一小部分。

那麼,如何來平衡效能和效果呢?答案正如最開始提到的,是Shader上的小Trick

2 怎麼模擬窗戶?

2。1 通用——視差對映(Parallax Mapping)

Shader是將幾何資訊作為輸入,一頓操作輸出顏色,我們唯一關心的只是最終輸出顏色在場景中看起來正確,中間發生的事情並不重要。

所以如果我們能偏移輸出顏色,使它看起來像輸入了幾何模型一樣偏移,不就達到目的了嘛。

如果輸出的偏移不均勻,則可以使渲染的影象看起來好像在某種程度上發生了歪斜,扭曲或變形。

怎麼偏移呢?我們會馬上想到這方面通用的偏移技術,

視差對映(Parallax Mapping)

下圖是現實生活中利用視差效果的典例,類似一種投影

案例學習——Interior Mapping 室內對映(假室內效果)

這裡是之前學習LearnOpenGL上視差對映的筆記。

在使用視差對映時,我們輸入紋理座標,根據觀察者角度和每個畫素的“深度”值進行偏移。透過確定相機射線與表面高度相交的點,我們可以建立相當於3D投影出來的2D影象。

視差對映能實現室內效果嗎?雖然看起來非常適合我們的需求,也就是在2D平面表示3D效果的需求。

但作為通用解決方案的視差對映,在針對室內場景做的特定情況下使用時,似乎又不太行。

視差對映在較平滑的高度圖上效果最佳。 如果高度圖上紋素的高度差異過大會造成奇怪的視覺失真,有一些替代方法可以解決此問題(例如“陡峭視差對映”),但替代方法基本是迭代的,並且隨著深度與迭代次數之比的增加,會產生階梯狀效果。

除了迭代次數的消耗大,我們也發現窗戶的體積是在內部的,視差通常模擬的是表面的凹凸程度,而不是對於內部的模擬。

當通用解決方案失敗時,我們需要考慮可能滿足的簡單的特定解決方案。

2。2 特定——室內對映(Interior Mapping)

具體問題具體分析,我們要窗戶的shader如何進行偏移呢?

在我們的情況下,我們希望將矩形房間插入顯示到我們的窗戶中。

視差貼圖的通用性意味著必須使用迭代數值方法,因為沒有分析方法可以確定我們的相機射線在哪裡與表面高度相交。

如果我們僅將問題限制在矩形盒子的房間上,那麼,只需將房間體積內的相交點對映到紋理,然後輸出正確的顏色即可。

案例學習——Interior Mapping 室內對映(假室內效果)

Joost van Dongen在2008年CGI會議上發表了一篇論文:Interior Mapping: A new technique for rendering realistic buildings,作為該技術的起源(這是作者提供的演示demo)。

和之前體繪製shader的思路很像,論文中,Interior Mapping考慮建築物本身不包含任何額外的幾何形狀,內部的體積僅是虛假的存在於著色器中。

其把建築網格的mesh劃分為很多“房間”,並對每個房間窗戶的紋理畫素進行raycast。 使用相機射線與房間box之間的交點處的座標,來取樣一組“房間紋理”。

案例學習——Interior Mapping 室內對映(假室內效果)

以盒狀房間為例,一個房間有六個面,四面牆加天花板加地板,但我們只要考慮看得到的三個面就行了。 計算射線與這3個平面中每個平面的交點,如P‘,然後,我們使用交點P’作為

紋理座標

來查詢畫素P的顏色

案例學習——Interior Mapping 室內對映(假室內效果)

於是,類似於視差貼圖,它偏移了輸入紋理的座標,給每個隱藏的“房間box”提供牆壁天花板和地板紋理的投影。

在不增加其他幾何形狀和材質複雜性的情況下,它較好的表示了內部空間。

案例學習——Interior Mapping 室內對映(假室內效果)

其技術廣泛運用在如今的遊戲裡,像下面這些窗戶中“以假亂真”的房間

在《漫威蜘蛛俠》,角色在建築物上爬牆的演示影片中,似乎能“透過”玻璃看到建築內部 但其實在拐角處的時候能發現奇怪的地方

案例學習——Interior Mapping 室內對映(假室內效果)

這是另一個《漫威蜘蛛俠》中的演示影片,也可以發現房間實際上並不存在於幾何體中,轉角的玻璃有一扇門,但那裡顯然應該有一個窗戶

案例學習——Interior Mapping 室內對映(假室內效果)

在《七大罪》的技術分享中,演講者分享了一種稱之為FakeInterior的技術,技術人員用其來模擬室內的效果

案例學習——Interior Mapping 室內對映(假室內效果)

在《守望先鋒》中玩家發現了一面神奇的窗戶

案例學習——Interior Mapping 室內對映(假室內效果)

還有很多遊戲中的例子,儘管所有這些都表明,它們這些透過窗戶看到的房間是偽造的,但Interior Mapping的應用讓它們在透視上是完全正確的,並且具有真實的深度。

3 實現方式

3。1 物件空間/切線空間

在論文的實現中,內部的房間是在物件空間或世界空間中定義的。

這確實很容易直接使用,但其在帶有傾斜或彎曲牆壁的建築物上表現不太好

房間受到建築幾何形狀的限制,可能導致不平坦或房間截斷,就像下圖,論文作者的演示demo中的例子

案例學習——Interior Mapping 室內對映(假室內效果)

在現實中,房間幾乎總是與建築物的外部對齊。

所以我們更願意讓所有的房間與mesh對齊,然後向內擠壓,向建築的中心延伸。

案例學習——Interior Mapping 室內對映(假室內效果)

為了做到這一點,我們可以計算尋找一個替代的座標系,它與我們的表面一致,也就是我們可以到切線空間去做raycast計算。

即使世界空間是彎曲的,但是切線空間永遠是軸對齊的。

在切線空間計算後,我們的房間可以隨建築物的曲率而變化,並且始終具有與建築物外部平行的整面牆。

3。2 房間貼圖

對於房間的貼圖,論文中要求為牆壁、地板和天花板提供單獨的紋理。

這雖然能用,但很難操作。要保持這三種紋理的同步一致,將多個房間的紋理放在一起,是比較困難的。

於是人們也提出了一些其他的方法

3。2。1 立方體貼圖——《七大罪》

Zoe J Wood在 “Interior Mapping Meets Escher”中用立方體貼圖替代了原本的貼圖。

在《七大罪》的技術分享中,也使用了這個方式,他們室內用了cubeMap,然後加上一張窗戶的貼圖,以及提供深度調節景深

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

但立方體貼圖也意味著,人們基本不可能人為對貼圖進行繪製微調,這對藝術家構建多種的內部貼圖資產不太友好。

3。2。2 預投影2D貼圖——《模擬城市5》

《模擬城市5》的開發者Andrew Willmott在“From AAA to Indie: Graphics R&D”的演講中提到,他們在《模擬城市5》中為內部地圖使用了預先投影的內部紋理,這是當時的PPT。

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

這個方式是比較好的,具有很高的創作性,易於使用,並且展示的結果僅比完整的立方體貼圖差一點。

因為是基於一張投影圖做的對映,所以只有在該圖片原先渲染的角度才能獲得最完美的效果。 換到其他角度或者隨意改變深度對映什麼的,都會造成些微的扭曲和失真

而且它可以在每個建築物的基礎上構建大量的室內圖集,隨機選擇,達到僅使用一個

紋理資源

,建築物就可以保持具有隨機變化的內部場景風格。

只能說和cubemap的方法各有利弊吧

3。2。3 預投影2D貼圖——《極限競速:地平線4》

同樣的,Playground Games的技術美術總監Gareth Harwood在The Gamasutra Deep Dives的一篇訪談中也提到,《極限競速:地平線4》中也使用了預投影2D貼圖來實現室內對映,製作街景的窗戶

案例學習——Interior Mapping 室內對映(假室內效果)

訪談提到有幾個重要部分:

窗戶+窗框+內部形成三層紋理;地圖集的使用;夜間與白天的紋理切換;小角度的處理;內部紋理

建模

的注意事項;規則化擺放內部紋理;效能最佳化……

下面摘錄一些訪談的內容,比較具有參考意義,翻譯了一下可以瞭解瞭解

建立逼真的世界一直是《極限競速:地平線》系列遊戲的一項優勢,而我們實現這一目標的一個方面就是在建築物中增加室內裝飾。

在《極限競速:地平線4》中,我們知道愛丁堡是如此的密集和細緻,以至於物理建模的內部空間將超出我們的預算,因此我們研究了另一種稱為

視差紋理

的技術。

這給我們帶來了巨大的優勢,可以創造出一種內飾,因為將其烘焙成一種紋理,而不受多邊形數或材料複雜性的限制。 由於渲染這些物體比建立幾何體便宜,因此我們可以在遊戲中擁有更多的內飾。 以前,由於預算問題可能需要關閉窗簾或讓窗戶變暗,現在我們有了完整的視差內部。

案例學習——Interior Mapping 室內對映(假室內效果)

視差內部材料由三層組成,藝術家可以為每個視窗獨立選擇這些層。 由於每個圖層都有幾個不同的選項,因此即使僅使用少量的

Atlas紋理

,這也為每個視窗提供了數百種可能的視覺效果。

第一層很簡單,包含窗框和玻璃。 這增加了細微差別,例如窗玻璃的細節或窗戶上的窗框,這在較舊的英國建築中尤為突出。該層具有漫反射,阿爾法,粗糙度,金屬性和法線。 法線透過在每個玻璃窗格中包含變化,以增加玻璃反射角的真實變化,這在我們的古董窗格中更為明顯。

案例學習——Interior Mapping 室內對映(假室內效果)

第二層是窗簾或百葉窗。 這些具有漫反射和Alpha,但還具有透射紋理,該紋理在晚上用於顯示窗簾的厚度。 我們有不讓光透過的厚實的窗簾,半透明的百葉窗,甚至是華麗的窗簾,我們用花邊部分製成的窗簾允許一些光線透過。

最後一層是著色器產生神奇效果的地方,因為這是模擬3D內部的平面紋理。

首先使用統一的比例尺以3D模型對內部進行建模,然後將其渲染為室內對映著色器支援的獨特紋理格式。 像其他圖層一樣,我們建立了地圖集,該地圖集包含多達八個相同樣式的不同內部裝飾,以減少繪製呼叫。

我們有一些農村地區的地圖集,它們在農村家庭中共享,城鎮房屋的地圖集以及我們的商店,飯店和典型的英式酒吧中的一些商業地圖集。 我們也有兩個主要的內部深度。 一種是用於標準間,另一種是用於淺窗陳列。 當對每個視窗設定材質屬性時,藝術家可以為建築物的每個視窗選擇所需的地圖集的一部分。

案例學習——Interior Mapping 室內對映(假室內效果)

著色器計算了您在房間中看到的角度,並將調整UV,以便您只能看到從您的視角可見的內部區域。 該技術有一些我們要解決的侷限性。

首先,著色器的trick在非常淺的角度變得明顯。在這些邊緣情況下,我們增加了玻璃的

自然菲涅爾效果

,以顯示比室內更多的反射影象。

其次,隨著角度的增加,房間中央的細節開始彎曲。我們透過將興趣吸引到牆壁和角落以及使地板變暗來減少這種情況。 與現實生活中一樣,透過使線條匯聚到達某個點,可以幫助實現視差效果。

在我們的影象中,我們儘可能地使用了平行線來進行遊戲:平鋪,書架,木地板和帶圖案的牆紙。 每個內部紋理在夜間也具有輔助光紋理,再次烘焙紋理的好處是可以根據需要使用盡可能多的光,因為它們最終都將被合併為一個紋理。 我們為每個建築物都有開和關的時間,以增加開燈時的變化。

案例學習——Interior Mapping 室內對映(假室內效果)

藝術家可以自由選擇他們喜歡的任何型別的窗戶,窗簾和室內組合。 但是他們遵循一些簡單的規則:臥室通常出現在較高的樓層,我們不會在每個窗戶上重複相同的房間,也不會在樓梯上放上上升的樓梯頂樓。

我們還研究了每個房間的可行性。 例如,我們不可能有一個小型的兩居室小屋,其中有十個或更多的窗戶可以看到不同的房間,因此藝術家設定了可以看到同一房間不同部分的窗戶。

最後,出於效能原因;當玩家離開建築物時,我們不僅需要降低網格的複雜性,而且還需要降低著色器和材質的複雜性。 為此,我們淡化了

視差效果

,並用平面圖像替換了視窗,該影象是一張具有視窗,窗簾和內部的圖片。 當視窗在螢幕上很小且播放器不注意到時,就會發生這種情況。

關於《極限競速:地平線4》所用到的高畫質圖,可以去製作者的A站這個連結下載

案例學習——Interior Mapping 室內對映(假室內效果)

4 動手試試

實現上,借用了Unity商城裡的免費資源Fake Interiors FREE裡面的模型和部分貼圖,並參考了其中的

shader結構

,以及參考了Unity論壇的討論,colin大大的實現

核心的思想就是,如何取樣虛擬的房間

因為我們是在窗戶(朝著我們的這個面上)執行shader,那麼從窗戶看進去的一根光線會打中後面虛擬房間box的哪個點呢?

也就是光線與AABB(軸對齊包圍盒)的相交問題

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

一個包圍盒有6個矩形面,把兩個互相平行的矩形看成一塊板,那麼問題就轉化為求射線與互相垂直的3塊板的相交

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

另外,射線與各種形狀的相交演算法可以看看這個網頁

4。1 立方體貼圖

首先實現一個立方體貼圖的方法 比如用這麼個cubemap

案例學習——Interior Mapping 室內對映(假室內效果)

4。1。1 ObjectSpace的方法

我們先不管切線空間,先從在object空間實現的方法開始, 為避免分散注意,先只展示shader的主幹思想

v2f

vert

appdata

v

{

v2f

o

o

pos

=

UnityObjectToClipPos

v

vertex

);

// slight scaling adjustment to work around “noisy wall”

// when frac() returns a 0 on surface

o

uvw

=

v

vertex

*

_RoomCube_ST

xyx

*

0。999

+

_RoomCube_ST

zwz

// get object space camera vector

float4

objCam

=

mul

unity_WorldToObject

float4

_WorldSpaceCameraPos

1。0

));

o

viewDir

=

v

vertex

xyz

-

objCam

xyz

// adjust for tiling

o

viewDir

*=

_RoomCube_ST

xyx

return

o

}

o。UVW也就是該畫素在模型空間的位置,我們之後會用來取樣 這裡配合tilling的影響加上了ST的引數,以及為了避免極值,乘了一下0。999,調整一下UVW值 根據相機和當前位置,計算視線方向(也就是公式推導裡的$\vec{d}$)

相機位置,永遠是在平面的上面,可以看到平面的正面(反面就被剔除了)

fixed4

frag

v2f

i

SV_Target

{

// room uvws

float3

roomUVW

=

frac

i

uvw

);

// raytrace box from object view dir

// transform object space uvw( min max corner = (0,0,0) & (+1,+1,+1))

// to normalized box space(min max corner = (-1,-1,-1) & (+1,+1,+1))

float3

pos

=

roomUVW

*

2。0

-

1。0

// for axis aligned box Intersection,we need to know the zoom level

float3

id

=

1。0

/

i

viewDir

// k means normalized box space depth hit per x/y/z plane seperated

// (we dont care about near hit result here, we only want far hit result)

float3

k

=

abs

id

-

pos

*

id

// kmin = normalized box space real hit ray length

float

kMin

=

min

min

k

x

k

y

),

k

z

);

// normalized box Space real hit pos = rayOrigin + kmin * rayDir。

pos

+=

kMin

*

i

viewDir

// randomly flip & rotate cube map for some variety

float3

flooredUV

=

floor

i

uvw

);

float3

r

=

rand3

flooredUV

x

+

flooredUV

y

+

flooredUV

z

);

float2

cubeflip

=

floor

r

xy

*

2。0

*

2。0

-

1。0

pos

xz

*=

cubeflip

pos

xz

=

r

z

>

0。5

pos

xz

pos

zx

// sample room cube map

fixed4

room

=

texCUBE

_RoomCube

pos

xyz

);

return

fixed4

room

rgb

1。0

);

}

roomUVW = frac(i。uvw); 擷取小數部分當做取樣的UV值

對虛擬房間進行標準化,原本(0,0,0) ~ (+1,+1,+1)的UVW進行*2-1之後,變為(-1,-1,-1) ~ (+1,+1,+1)

在這裡再回憶一下公式

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

因為三個軸的計算方法都是一樣的,所以對於出點,我們可以得到程式碼中的 float kMin = min(min(k。x, k。y), k。z);

然後再根據射線公式,我們就能得到交點的位置,(後面做了一些隨機旋轉和選擇的操作,可以不管)然後就可以用它來取樣CubeMap了

可以發現,能在內部看到box

但是對於ObjectSpace的方法,內部房間只能嚴格按照軸對齊排列,在曲面上顯得很奇怪

案例學習——Interior Mapping 室內對映(假室內效果)

4。1。2 TangentSpace的方法

為了讓box在曲面也能表現良好,我們到切線空間中進行求交,程式碼整體上差不多

v2f

vert

appdata

v

{

v2f

o

o

pos

=

UnityObjectToClipPos

v

vertex

);

// uvs

o

uv

=

TRANSFORM_TEX

v

uv

_RoomCube

);

// get tangent space camera vector

float4

objCam

=

mul

unity_WorldToObject

float4

_WorldSpaceCameraPos

1。0

));

float3

viewDir

=

v

vertex

xyz

-

objCam

xyz

float

tangentSign

=

v

tangent

w

*

unity_WorldTransformParams

w

float3

bitangent

=

cross

v

normal

xyz

v

tangent

xyz

*

tangentSign

o

viewDir

=

float3

dot

viewDir

v

tangent

xyz

),

dot

viewDir

bitangent

),

dot

viewDir

v

normal

);

// adjust for tiling

o

viewDir

*=

_RoomCube_ST

xyx

return

o

}

TRANSFORM_TEX方法就是將模型頂點的uv和Tiling、Offset兩個變數進行運算,計算出實際顯示用的uv

切線空間的轉換,做一下視線乘以TBN矩陣就可以

fixed4

frag

v2f

i

SV_Target

{

// room uvs

float2

roomUV

=

frac

i

uv

);

// raytrace box from tangent view dir

float3

pos

=

float3

roomUV

*

2。0

-

1。0

1。0

);

float3

id

=

1。0

/

i

viewDir

float3

k

=

abs

id

-

pos

*

id

float

kMin

=

min

min

k

x

k

y

),

k

z

);

pos

+=

kMin

*

i

viewDir

// randomly flip & rotate cube map for some variety

float2

flooredUV

=

floor

i

uv

);

float3

r

=

rand3

flooredUV

x

+

1。0

+

flooredUV

y

*

flooredUV

x

+

1

));

float2

cubeflip

=

floor

r

xy

*

2。0

*

2。0

-

1。0

pos

xz

*=

cubeflip

pos

xz

=

r

z

>

0。5

pos

xz

pos

zx

#endif

// sample room cube map

fixed4

room

=

texCUBE

_RoomCube

pos

xyz

);

return

fixed4

room

rgb

1。0

);

}

片元著色器大同小異,基本一樣 在定義pos的時候,讓Z=1(Z也就是TBN裡的N),朝內取樣 float3 pos = float3(roomUV * 2。0 - 1。0, 1。0);

得到效果,在曲面上表現良好

案例學習——Interior Mapping 室內對映(假室內效果)

4。1。3 深度

深度如何實現呢?

我們指定一個深度值,然後在片元著色器前面加上深度對映的程式碼,我們把深度乘上視線的Z

// Specify depth manually

fixed

farFrac

=

_RoomDepth

//remap [0,1] to [+inf,0]

//->if input _RoomDepth = 0 -> depthScale = 0 (inf depth room)

//->if input _RoomDepth = 0。5 -> depthScale = 1

//->if input _RoomDepth = 1 -> depthScale = +inf (0 volume room)

float

depthScale

=

1。0

/

1。0

-

farFrac

-

1。0

i

viewDir

z

*=

depthScale

我們如果在原本的基礎上放大了視線Z的倍率,也就是

視線向量

更加朝著遠處走了,取樣的時候就好像更深了

案例學習——Interior Mapping 室內對映(假室內效果)

上面的做法是根據z移動的距離來進行在取樣上的縮放,會導致四周的貼圖失真

也可以用偏移來表達房間的深度,也就是整體前後移動,由此造成能偏移的深度就很有限了,只能是box的深度

可以看這位大大的實現

案例學習——Interior Mapping 室內對映(假室內效果)

4。1。4 程式碼

整合起來就是這樣

// Upgrade NOTE: replaced ‘mul(UNITY_MATRIX_MVP,*)’ with ‘UnityObjectToClipPos(*)’

Shader

“MyShaders/InteriorMapping_CubeMap”

{

Properties

{

_RoomCube

“Room Cube Map”

Cube

=

“white”

{}

Toggle

_USEOBJECTSPACE

)]

_UseObjectSpace

“Use Object Space”

Float

=

0。0

_RoomDepth

“Room Depth”

range

0。001

0。999

))

=

0。5

}

SubShader

{

Tags

{

“RenderType”

=

“Opaque”

}

LOD

100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#pragma shader_feature _USEOBJECTSPACE

#include

“UnityCG。cginc”

struct

appdata

{

float4

vertex

POSITION

float2

uv

TEXCOORD0

float3

normal

NORMAL

float4

tangent

TANGENT

};

struct

v2f

{

float4

pos

SV_POSITION

#ifdef _USEOBJECTSPACE

float3

uvw

TEXCOORD0

#else

float2

uv

TEXCOORD0

#endif

float3

viewDir

TEXCOORD1

};

samplerCUBE

_RoomCube

float4

_RoomCube_ST

float

_RoomDepth

// psuedo random 偽隨機

float3

rand3

float

co

{

return

frac

sin

co

*

float3

12。9898

78。233

43。2316

))

*

43758。5453

);

}

v2f

vert

appdata

v

{

v2f

o

o

pos

=

UnityObjectToClipPos

v

vertex

);

#ifdef _USEOBJECTSPACE

// slight scaling adjustment to work around “noisy wall” when frac() returns a 0 on surface

o

uvw

=

v

vertex

*

_RoomCube_ST

xyx

*

0。999

+

_RoomCube_ST

zwz

// get object space camera vector

float4

objCam

=

mul

unity_WorldToObject

float4

_WorldSpaceCameraPos

1。0

));

o

viewDir

=

v

vertex

xyz

-

objCam

xyz

// adjust for tiling

o

viewDir

*=

_RoomCube_ST

xyx

#else

// uvs

o

uv

=

TRANSFORM_TEX

v

uv

_RoomCube

);

// get tangent space camera vector

float4

objCam

=

mul

unity_WorldToObject

float4

_WorldSpaceCameraPos

1。0

));

float3

viewDir

=

v

vertex

xyz

-

objCam

xyz

float

tangentSign

=

v

tangent

w

*

unity_WorldTransformParams

w

float3

bitangent

=

cross

v

normal

xyz

v

tangent

xyz

*

tangentSign

o

viewDir

=

float3

dot

viewDir

v

tangent

xyz

),

dot

viewDir

bitangent

),

dot

viewDir

v

normal

);

// adjust for tiling

o

viewDir

*=

_RoomCube_ST

xyx

#endif

return

o

}

fixed4

frag

v2f

i

SV_Target

{

// Specify depth manually

fixed

farFrac

=

_RoomDepth

//remap [0,1] to [+inf,0]

//->if input _RoomDepth = 0 -> depthScale = 0 (inf depth room)

//->if input _RoomDepth = 0。5 -> depthScale = 1

//->if input _RoomDepth = 1 -> depthScale = +inf (0 volume room)

float

depthScale

=

1。0

/

1。0

-

farFrac

-

1。0

i

viewDir

z

*=

depthScale

#ifdef _USEOBJECTSPACE

// room uvws

float3

roomUVW

=

frac

i

uvw

);

// raytrace box from object view dir

// transform object space uvw( min max corner = (0,0,0) & (+1,+1,+1))

// to normalized box space(min max corner = (-1,-1,-1) & (+1,+1,+1))

float3

pos

=

roomUVW

*

2。0

-

1。0

// for axis aligned box Intersection,we need to know the zoom level

float3

id

=

1。0

/

i

viewDir

// k means normalized box space depth hit per x/y/z plane seperated

// (we dont care about near hit result here, we only want far hit result)

float3

k

=

abs

id

-

pos

*

id

// kmin = normalized box space real hit ray length

float

kMin

=

min

min

k

x

k

y

),

k

z

);

// normalized box Space real hit pos = rayOrigin + kmin * rayDir。

pos

+=

kMin

*

i

viewDir

// randomly flip & rotate cube map for some variety

float3

flooredUV

=

floor

i

uvw

);

float3

r

=

rand3

flooredUV

x

+

flooredUV

y

+

flooredUV

z

);

float2

cubeflip

=

floor

r

xy

*

2。0

*

2。0

-

1。0

pos

xz

*=

cubeflip

pos

xz

=

r

z

>

0。5

pos

xz

pos

zx

#else

// room uvs

float2

roomUV

=

frac

i

uv

);

// raytrace box from tangent view dir

float3

pos

=

float3

roomUV

*

2。0

-

1。0

1。0

);

float3

id

=

1。0

/

i

viewDir

float3

k

=

abs

id

-

pos

*

id

float

kMin

=

min

min

k

x

k

y

),

k

z

);

pos

+=

kMin

*

i

viewDir

// randomly flip & rotate cube map for some variety

float2

flooredUV

=

floor

i

uv

);

float3

r

=

rand3

flooredUV

x

+

1。0

+

flooredUV

y

*

flooredUV

x

+

1

));

float2

cubeflip

=

floor

r

xy

*

2。0

*

2。0

-

1。0

pos

xz

*=

cubeflip

pos

xz

=

r

z

>

0。5

pos

xz

pos

zx

#endif

// sample room cube map

fixed4

room

=

texCUBE

_RoomCube

pos

xyz

);

return

fixed4

room

rgb

1。0

);

}

ENDCG

}

}

}

4。2 預投影2d貼圖

對於立方體形狀的房間,若後壁的大小為可見瓷磚的1/2

此時如果將它們渲染出來,要使用

水平FOV

為53。13度從開口向後的攝像機。

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

我們於是可以規定這種情況下的深度單位為

標準深度

,也就是_RoomDepth = 0。5 → depthScale = 1

比如下面這個圖,就是用這種情況下渲染出來的

房間深度

可以儲存在圖集紋理的

Alpha通道

中(標準深度也就是alpha通道為128)

案例學習——Interior Mapping 室內對映(假室內效果)

也可以做很多圖集

案例學習——Interior Mapping 室內對映(假室內效果)

當然也可以不管alpha通道,手動來自己調整深度,這兩種指定方式程式碼會說明

也可以去地平線4製作者的A站連結下載圖集

案例學習——Interior Mapping 室內對映(假室內效果)

我們的整體思路不變,先在軸對齊包圍盒中求交點,然後我們要把原本要取樣cubemap的三維空間的交點,對映到2d圖片的UV上,取樣預投影2d圖片

4。2。1 實現

頂點著色器功能和之前一樣,圍繞切線空間進行

v2f

vert

appdata

v

{

v2f

o

o

pos

=

UnityObjectToClipPos

v

vertex

);

o

uv

=

TRANSFORM_TEX

v

uv

_RoomTex

);

// get tangent space camera vector

float4

objCam

=

mul

unity_WorldToObject

float4

_WorldSpaceCameraPos

1。0

));

float3

viewDir

=

v

vertex

xyz

-

objCam

xyz

float

tangentSign

=

v

tangent

w

*

unity_WorldTransformParams

w

float3

bitangent

=

cross

v

normal

xyz

v

tangent

xyz

*

tangentSign

o

tangentViewDir

=

float3

dot

viewDir

v

tangent

xyz

),

dot

viewDir

bitangent

),

dot

viewDir

v

normal

);

o

tangentViewDir

*=

_RoomTex_ST

xyx

return

o

}

片元著色器程式碼如下

// psuedo random

float2

rand2

float

co

{

return

frac

sin

co

*

float2

12。9898

78。233

))

*

43758。5453

);

}

fixed4

frag

v2f

i

SV_Target

{

// room uvs

float2

roomUV

=

frac

i

uv

);

float2

roomIndexUV

=

floor

i

uv

);

// randomize the room

float2

n

=

floor

rand2

roomIndexUV

x

+

roomIndexUV

y

*

roomIndexUV

x

+

1

))

*

_Rooms

xy

);

//float2 n = floor(_Rooms。xy);

roomIndexUV

+=

n

// get room depth from room atlas alpha

// fixed farFrac = tex2D(_RoomTex, (roomIndexUV + 0。5) / _Rooms)。a;

// Specify depth manually

fixed

farFrac

=

_RoomDepth

//remap [0,1] to [+inf,0]

//->if input _RoomDepth = 0 -> depthScale = 0 (inf depth room)

//->if input _RoomDepth = 0。5 -> depthScale = 1

//->if input _RoomDepth = 1 -> depthScale = +inf (0 volume room)

float

depthScale

=

1。0

/

1。0

-

farFrac

-

1。0

// raytrace box from view dir

// normalized box space‘s ray start pos is on trinagle surface, where z = -1

float3

pos

=

float3

roomUV

*

2

-

1

-

1

);

// transform input ray dir from tangent space to normalized box space

i

tangentViewDir

z

*=

-

depthScale

float3

id

=

1。0

/

i

tangentViewDir

float3

k

=

abs

id

-

pos

*

id

float

kMin

=

min

min

k

x

k

y

),

k

z

);

pos

+=

kMin

*

i

tangentViewDir

// remap from [-1,1] to [0,1] room depth

float

interp

=

pos

z

*

0。5

+

0。5

// account for perspective in “room” textures

// assumes camera with an fov of 53。13 degrees (atan(0。5))

// visual result = transform nonlinear depth back to linear

float

realZ

=

saturate

interp

/

depthScale

+

1

interp

=

1。0

-

1。0

/

realZ

);

interp

*=

depthScale

+

1。0

// iterpolate from wall back to near wall

float2

interiorUV

=

pos

xy

*

lerp

1。0

farFrac

interp

);

interiorUV

=

interiorUV

*

0。5

+

0。5

// sample room atlas texture

fixed4

room

=

tex2D

_RoomTex

roomIndexUV

+

interiorUV

xy

/

_Rooms

);

return

fixed4

room

rgb

1。0

);

}

選擇房間UV和隨機的部分很正常

深度值可以從圖的alpha通道里獲得也可以手動指定,指定depthScale時,把0 ~ 1的深度輸入對映到0 ~ +inf

從貼圖中獲取深度的程式碼也就是註釋的那一小段

// fixed farFrac = tex2D(_RoomTex, (roomIndexUV + 0。5) / _Rooms)。a;

因為背面剔除的關係,能看到的相機永遠在平面的正面,所以視線方向永遠是指向平面內的

之前cubemap的切線空間裡,我們pos的Z值指定為1,該平面位於標準化正方體的頂面,所以視線方向不用調整

現在我們把pos的Z值指定為-1

float3 pos = float3(roomUV * 2 - 1, -1);

該平面位於標準化正方體的底面 所以要把

i。tangentViewDir。z *= -depthScale;

,把視線的Z反轉一下

那為什麼這次要把pos的Z指定為-1呢?這和平面到立體的對映有關,也就是這一段程式碼做的事情

// remap from [-1,1] to [0,1] room depth

float

interp

=

pos

z

*

0。5

+

0。5

// account for perspective in “room” textures

// assumes camera with an fov of 53。13 degrees (atan(0。5))

// visual result = transform nonlinear depth back to linear

float

realZ

=

saturate

interp

/

depthScale

+

1

interp

=

1。0

-

1。0

/

realZ

);

interp

*=

depthScale

+

1。0

// iterpolate from wall back to near wall

float2

interiorUV

=

pos

xy

*

lerp

1。0

farFrac

interp

);

interiorUV

=

interiorUV

*

0。5

+

0。5

因為有參考論壇的程式碼實現,但不理解,於是畫圖走了一遍程式碼,把程式碼每一步的效果都標了出來,看看它做了什麼 (從結果倒回去推了半天,依然不甚明白是怎麼得到這樣做的方法,只是懂個意境了)

案例學習——Interior Mapping 室內對映(假室內效果)

結果如圖

案例學習——Interior Mapping 室內對映(假室內效果)

4。2。2 程式碼

該部分著色器如下

// Upgrade NOTE: replaced ’mul(UNITY_MATRIX_MVP,*)‘ with ’UnityObjectToClipPos(*)‘

Shader

“MyShaders/InteriorMapping_2D”

{

Properties

{

_RoomTex

“Room Atlas RGB (A - back wall fraction)”

2

D

=

“white”

{}

_Rooms

“Room Atlas Rows&Cols (XY)”

Vector

=

1

1

0

0

_RoomDepth

“Room Depth”

range

0。001

0。999

))

=

0。5

}

SubShader

{

Tags

{

“RenderType”

=

“Opaque”

}

LOD

100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#include

“UnityCG。cginc”

struct

appdata

{

float4

vertex

POSITION

float2

uv

TEXCOORD0

float3

normal

NORMAL

float4

tangent

TANGENT

};

struct

v2f

{

float4

pos

SV_POSITION

float2

uv

TEXCOORD0

float3

tangentViewDir

TEXCOORD1

};

sampler2D

_RoomTex

float4

_RoomTex_ST

float2

_Rooms

float

_RoomDepth

v2f

vert

appdata

v

{

v2f

o

o

pos

=

UnityObjectToClipPos

v

vertex

);

o

uv

=

TRANSFORM_TEX

v

uv

_RoomTex

);

// get tangent space camera vector

float4

objCam

=

mul

unity_WorldToObject

float4

_WorldSpaceCameraPos

1。0

));

float3

viewDir

=

v

vertex

xyz

-

objCam

xyz

float

tangentSign

=

v

tangent

w

*

unity_WorldTransformParams

w

float3

bitangent

=

cross

v

normal

xyz

v

tangent

xyz

*

tangentSign

o

tangentViewDir

=

float3

dot

viewDir

v

tangent

xyz

),

dot

viewDir

bitangent

),

dot

viewDir

v

normal

);

o

tangentViewDir

*=

_RoomTex_ST

xyx

return

o

}

// psuedo random

float2

rand2

float

co

{

return

frac

sin

co

*

float2

12。9898

78。233

))

*

43758。5453

);

}

fixed4

frag

v2f

i

SV_Target

{

// room uvs

float2

roomUV

=

frac

i

uv

);

float2

roomIndexUV

=

floor

i

uv

);

// randomize the room

float2

n

=

floor

rand2

roomIndexUV

x

+

roomIndexUV

y

*

roomIndexUV

x

+

1

))

*

_Rooms

xy

);

//float2 n = floor(_Rooms。xy);

roomIndexUV

+=

n

// get room depth from room atlas alpha

// fixed farFrac = tex2D(_RoomTex, (roomIndexUV + 0。5) / _Rooms)。a;

// Specify depth manually

fixed

farFrac

=

_RoomDepth

//remap [0,1] to [+inf,0]

//->if input _RoomDepth = 0 -> depthScale = 0 (inf depth room)

//->if input _RoomDepth = 0。5 -> depthScale = 1

//->if input _RoomDepth = 1 -> depthScale = +inf (0 volume room)

float

depthScale

=

1。0

/

1。0

-

farFrac

-

1。0

// raytrace box from view dir

// normalized box space’s ray start pos is on trinagle surface, where z = -1

float3

pos

=

float3

roomUV

*

2

-

1

-

1

);

// transform input ray dir from tangent space to normalized box space

i

tangentViewDir

z

*=

-

depthScale

float3

id

=

1。0

/

i

tangentViewDir

float3

k

=

abs

id

-

pos

*

id

float

kMin

=

min

min

k

x

k

y

),

k

z

);

pos

+=

kMin

*

i

tangentViewDir

// remap from [-1,1] to [0,1] room depth

float

interp

=

pos

z

*

0。5

+

0。5

// account for perspective in “room” textures

// assumes camera with an fov of 53。13 degrees (atan(0。5))

// visual result = transform nonlinear depth back to linear

float

realZ

=

saturate

interp

/

depthScale

+

1

interp

=

1。0

-

1。0

/

realZ

);

interp

*=

depthScale

+

1。0

// iterpolate from wall back to near wall

float2

interiorUV

=

pos

xy

*

lerp

1。0

farFrac

interp

);

interiorUV

=

interiorUV

*

0。5

+

0。5

// sample room atlas texture

fixed4

room

=

tex2D

_RoomTex

roomIndexUV

+

interiorUV

xy

/

_Rooms

);

return

room

}

ENDCG

}

}

FallBack

“Diffuse”

}

5 豐富效果

基礎技術就如上所示,自行拓展一下可以得到一些結果

下圖用2d投影圖為基底,用alpha值存深度&手動指定深度,在圖集中隨機生成,以及加上窗框,菲涅爾效應,窗戶汙垢等等

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

案例學習——Interior Mapping 室內對映(假室內效果)

另外也找到一些其他效果,比如這篇文章(Interior Mapping – Part 3)中,其用窗框的SDF圖模擬光照效果

總之還有蠻有意思的一個案例(中間那段根據深度變換UV的程式碼思路,希望能有大神指教了,想不明白Orz)

標簽: POS  room  viewDir  XYZ  box