unity二叉樹 在shader內實現二叉樹

2021-10-16 23:34:13 字數 4244 閱讀 9982

研究動機

由於某種原因 ,參與的超大場景專案放棄了unity自帶的烘焙。單討論平行光陰影來說 lightmask,和lightprobe都沒有。所以遠處的陰影內物體會變亮,這邏輯上正常因為再也沒有描述遮擋關係的資訊了。

解決方案一般是三種實時陰影設定很遠比如2000公尺,然後分7個級聯,第一幀渲染1234,第二幀渲染1235,然後1236,1237,1234.聽說原神是這樣處理。

靜態shadowmap.就是離線擺放好和平行光乙個朝向的燈光相機拍下整個場景深度,執行時在渲染計算陰影時在ps用當前位置 轉到燈光相機矩陣去對比深度判斷是否在陰影內。這種方式我之前實現過乙個demo 專案也用上了。jackie2009/scrollingstaticshadowmap。如果超大場景用需要拍很多張,但是這裡不做近處陰影只代替遠處shadowmask的話一公里1024也夠用了。

緊湊型體素陰影 原理和2 差不多,但不是存深度資訊,而是直接存是否在陰影內(因為不是深度 只能每個單位立方體都存 無法只存平面然後比較深度了)。因為這是個bool資料所以不是1就是0.這樣的資料有個特點特別適合大量壓縮。至少有2種壓縮方式1種就是乙個rgb24 可以存3x8 24個單位。但由於場景上大量是非陰影區域所以這種連續的儲存依然很浪費。該方案最重要的就是 用八叉樹儲存這些資訊。這個實現就是這篇的起因。

題外話:很多專案都用方案2代替靜態物體(主要是場景物件)的實時shadowmap可以大量減少drawcall節省cpu渲染執行緒的壓力。我附帶乙個測試圖可以看到 普通shadowmap是這種方案3倍drawcall

為什麼要用二叉樹/四叉樹/八叉樹

大部分場景相關的渲染技術需要的都是四叉樹或八叉樹,但是為了方便講解 我用二叉樹舉例,原理完全一致的。不了解二叉樹的建議先搜尋下看幾分鐘基礎介紹,不用看演算法 不要看平衡二叉樹(勸退專用演算法)。

想象一下這是一條100公尺長的路,假設陰影最小單位是1公尺,場景預計算結果是 在5,7,37,38,39,40,52,54,56,58,60,62,64,66,95 處有15處陰影。那麼問shader如何渲染出這15出黑色(如圖);直接做出1x100的圖 這樣shader只需要取樣一次和世界座標對應的uv就可以了 最簡單也最常用。這就是lightmap或shadowmask的做法。但這個缺點就是太費記憶體了 為了15個資料需要準備100個資料,特別遇到 海面等區域 沒有陰影非常浪費。雖然前面說可以8位壓縮到乙個顏色的乙個通道上,但一旦真實實用3維空間8位也只能多存一倍體積不是8倍。所以這個太費視訊記憶體小場景常用但大場景不理想。

把這15個資料存入一張1x15的貼圖,然後每個畫素最多取樣15次判斷自己是否和其中第乙個相同 如果是就顯示黑色。這種做法空間最省 但太費計算量了。放到到1公里 幾百次取樣根本跑不動,這種思路只能做10個以下的,比如簡單草的互動可以只傳主角和寵物座標來比對頂點搖擺。也放棄

我們需要一種 和2一樣的節省空間 又不需要取樣太多次的資料結構。這就是二叉樹了。二叉樹有致命缺陷是建立節點的順序。這個資料如果是動態的是建議用複雜許多的平衡二叉樹來做,但我們資料是靜態的所以我實現了一種排序滿足手動中分平衡。也就是只要控制排序 就能把普通的簡單的二叉樹做成超級平衡的樹。

完美的排序填充完他長這樣 ,每個節點都有資料且每個節點左右子以及子的子(孫?)數量都一樣多。

這樣我們就完成一半了!只用了2一樣多的15個單位儲存,但卻不用取樣15次。最多只要4次,從o(n) 降到o(log(n)) ,也就是1204次變到10次.我們看下他是如何查詢的,比如渲染的當前座標x 是7,那麼他先取樣54 發現比他小就再取樣38發現還比他小就取樣7發現一樣渲染成黑色。一共3次取樣。如果是陰影外的資料 會這樣取樣4次後都沒有相同的節點就渲染成白色。一共4次。

二叉樹**實現線上很多不單獨說明了。這裡說下我自己實現的這種中心平分填充演算法。

private void filldata(list randomvalues, int min, int max, list randomvalues2)

if (min + 1 >= max) return;

if (randomvalues.count == randomvalues2.count) return;

int mid = (min + max) / 2;

randomvalues2.add(randomvalues[mid]);

filldata(randomvalues, min, mid, randomvalues2);

filldata(randomvalues, mid,max, randomvalues2);

利用randomvalues 為排序好的list,公升序降序都可以。min max是list 劃分的區域。比如第一次 min是-1,max=15(比能取的值往區域外擴1位,這是為了有相加平均值在區域端點,比如 如果min是0 永遠取不到乙個 mid為0的機會);第一次mid是 7,所以 randomvalues2會把randomvalues[7]排入第乙個也就是整個陣列大小在中心的那個元素。然後遞迴2個子節點 不斷劃分到 min+1=max為止。這樣就得到了上圖了。第一張圖就是用這份資料渲染出來的。

現在開始寫入顏色到貼圖

for (int i = 0; i < nodes.count; i++)

nodes[i].index = i;

for (int i = 0; i < nodes.count; i++)

print(nodes[i].item);

colors[i].r = nodes[i].item;

colors[i].g = nodes[i].leftchild!=null? nodes[i].leftchild.index:-1;

tex.setpixels(colors);

shader.setglobaltexture("_testbtreetex", tex);

shader.setglobalint("_testbtreenodecount", nodes.count);

我們存了2個顏色通道 r存節點數值,g存第乙個子物件在貼圖中索引(第幾個畫素),這樣儲存當然浪費了一倍空間但方便理解。省空間的做法是 確保二叉樹是完全二叉樹,不存子節點索引根據自己在第幾層第乙個推算子節點位置,因為這種樹關係是固定的每一行數量是固定的1 2 4 8...可以實現推算。

對應的shader**是這段

int2 gettreevalue(int index)

//插入節點 public bool insert(int data)

node newnode = new node(data);

if (_root == null)

subshader

tags

lod 100

pass

cgprogram

#pragma vertex vert

#pragma fragment frag

#include "unitycg.cginc"

float4 vertex : position;

float2 uv : texcoord0;

struct v2f

float2 uv : texcoord0;

float3 wpos : texcoord1;

float4 vertex : sv_position;

sampler2d _maintex;

uniformsampler2d _testbtreetex;

uniform int _testbtreenodecount;

float4 _maintex_st;

v2f o;

o.vertex = unityobjecttoclippos(v.vertex);

o.uv = transform_tex(v.uv, _maintex);

o.wpos =mul( unity_objecttoworld,v.vertex);

return o;

int2 gettreevalue(int index) {

returntex2d(_testbtreetex, half2(index /(half)max(_testbtreenodecount,1), 0)).rg;

fixed4 frag (v2f i) : sv_target

int x = i.wpos.x+0.5;

int index = 0;

[unroll(10)]

while(1){

int2 node = gettreevalue(index);

if (x == node.x) return 0;

if (node.y < 0)return 1;

index = x < node.x ? node.y : node.y + 1;

return 1;

endcg

二叉樹 二叉樹

題目描述 如上所示,由正整數1,2,3 組成了一顆特殊二叉樹。我們已知這個二叉樹的最後乙個結點是n。現在的問題是,結點m所在的子樹中一共包括多少個結點。比如,n 12,m 3那麼上圖中的結點13,14,15以及後面的結點都是不存在的,結點m所在子樹中包括的結點有3,6,7,12,因此結點m的所在子樹...

樹 二叉樹 滿二叉樹 完全二叉樹 完滿二叉樹

目錄名稱作用根 樹的頂端結點 孩子當遠離根 root 的時候,直接連線到另外乙個結點的結點被稱之為孩子 child 雙親相應地,另外乙個結點稱為孩子 child 的雙親 parent 兄弟具有同乙個雙親 parent 的孩子 child 之間互稱為兄弟 sibling 祖先結點的祖先 ancesto...

二叉樹,完全二叉樹,滿二叉樹

二叉樹 是n n 0 個結點的有限集合,它或者是空樹 n 0 或者是由乙個根結點及兩顆互不相交的 分別稱為左子樹和右子樹的二叉樹所組成。滿二叉樹 一顆深度為k且有2 k 1個結點的二叉樹稱為滿二叉樹。說明 除葉子結點外的所有結點均有兩個子結點。所有葉子結點必須在同一層上。完全二叉樹 若設二叉樹的深度...