日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

视差贴图(Parallax Mapping)

發布時間:2023/12/29 综合教程 47 生活家
生活随笔 收集整理的這篇文章主要介紹了 视差贴图(Parallax Mapping) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

使用頂點光照的模型,當模型的面數很少的時候,光照效果會顯得很奇怪,因為只有頂點上的光照是正確計算出來的,三角面上的光照都是通過硬件插值得到,所以難免會出現問題。基于像素的光照可以很好的改善這個問題。如果想要表現出模型表面凹凸不平,那就需要很高的面數制作出凹凸的模型。

然后就出現法線貼圖。法線貼圖可以在低面數的模型上表現出高面數模型的很多細節。法線貼圖并不是把模型的面數提高了,而是使用法線貼圖中的法線來計算光照,通過明暗效果作假,讓觀察者誤以為模型有凹凸。法線貼圖只能在明暗效果上作假(模擬凹凸),無法控制表面的凹凸程度。即使我們使用圖像軟件強制調出一個凹凸非常明顯的法線貼圖,通過仔細觀察,會法線效果也是有問題的。

上面的是一張用了法線貼圖的地面,在紅色圓圈的地方是有問題的。我們使用一張平面圖來分析下。

這是一張凸起磚塊的截面圖,綠色箭頭表示視線的方向,白色線條表示磚塊的橫截面。按照常識來看,我們能看到磚塊的最遠的一點是藍色點,因為藍色點后面(紅色線條部分)的磚塊由于高度較低,被前面擋住了。但是從上面那張使用了法線貼圖的地面效果圖上可以看到,藍色點后的磚塊并沒有被擋住,甚至能夠看到黃色點的位置,這種效果顯然是不正確的。而這是法線貼圖無法避免的問題,因為上文已經說過了,法線貼圖只能模擬明暗,也就是說最多只能將紅色線條部分變暗(以此來模擬背光)。這就是視差貼圖可以解決的一個問題,它可以讓背面被遮擋住的部分完全不顯示出來,除此之外還能在一定范圍內調整磚塊凹凸的程度。視差貼圖也只是模擬作假,并沒有真的改變模型表面,下面就開始分析視差貼圖吧。

如圖所示,視線 e 落點是點 a,但是因為模型并不是真的有凹凸,而是一個平面,所以真實的落點是在點 b。這樣就變成了如何將點 a 糾正到點 b 的問題了。讓我們再在參考圖上加上一些輔助參數。

需要說明的是我們在分析視差貼圖的時候使用的是切線空間,這和法線貼圖是一樣的,切線空間中的切線和副切線是與紋理坐標 uv 對齊的,上圖中只顯示了 u 方向上的情況,在 v 方向上是一樣的。當前實際的落點是點 b,u 坐標是 ub,而理想的落點是在點 a,u坐標是 ua。如果能有一個 delta 量,把 ub 加上 delta 等于 ua,似乎就可以了。但是還有個問題是,因為視線的方向是一直在變化的,這就導致了 delta 量不可能是一個固定的值。所以暫且沒有什么好的辦法求出 delta,那么就把問題想簡單點。這里不要求精確的 delta,只要近似的就可以。于是有了一張稱為高度圖的紋理,它存儲了點 b 在切線空間的真實凹凸表面的凹陷或凸起程度。黑色(0)表示不凸起,白色(1)表示完全凸起。我們可以試著使用這個值來最大可能的近似模擬出 delta 值。

// 計算 uv 的偏移 delta
inline float2 ParallaxUvDelta(v2f i)
{
	// 高度圖中描述的高度數據
	half h = tex2D(_ParallaxMap, i.uvMain).r;
	// 切線空間中的視線方向
	float3 viewDir = normalize(i.viewDir);
	// 將三維的視線向量投影到二維的 uv 平面,乘以高度數據
	// _ParallaxScale 是一個用戶可調節的值,根據效果需要進行調節,數值太大造成視覺上的嚴重錯誤
	float2 delta = viewDir.xy / viewDir.z * h * _ParallaxScale;
	return delta;
}

float2 uvDelta = ParallaxUvDelta(i);
i.uvMain += uvDelta;
i.uvBump += uvDelta;

以上就是如何利用高度圖計算 uv 的偏移量的代碼了。需要注意,因為這只是近似模擬,所以能否得到完美的效果完全取決于各個參數調整的是否合理。其實上面的代碼只是一個框架,在此基礎上可以試著對 hviewDir.z 這些參數進行一定的偏移,或許能夠得到更好的效果。完成后的效果圖如下,可以看到磚塊就像真的凸起了一樣,上文法線貼圖紅圈指出的問題也沒有了。

不足之處是你無法讓磚塊無限制的凸起,當到了某個臨界值后,效果就完全穿幫了。

由于 uv 的 delta 偏移量是一個估計值,并不是精確值,所以才會出現這樣的情況。下面我們的目標就是讓 delta uv 盡可能的精確。上面的方法中,我們只對高度圖進行了一次采樣,很難一下就找準落點,顯然一次是不夠的,因此需要對其進行改進,我們將使用逐步逼近的方式,來獲得一個更精確的 delta uv。

從圖中可以看出:最上層的高度值為1,最下層的高度值為0,對中間值劃分為四等分(劃分得越細,最終計算出來的精度就越高,效果也就越好,當然計算量也越大),這些值和高度圖中的值是對應的。視線 e 會和等分線產生交點(紅點),直接使用交點的 uv 對高度圖進行采樣,會得到對應的幾個高度值(藍點)。最理想的情況下計算出來的結果是正好在黃點上,觀察下紅點和藍點,在黃點左邊的藍點高于紅點,在黃點右邊的紅點高于藍點,我們可以通過這個規律找到位于黃點兩邊最近的兩個紅點和藍點。這樣就可以確定黃點就在這兩個紅點的中間。最后,沿著 e 的方向,在這兩個紅點之間進行插值,即可獲得黃點的位置了,而插值需要用到 h1 和 h2 這兩個線段的長度(紅藍兩點的間距)。差值的精確度和一開始劃分的精細度有關。這就是原理描述了,下面解釋下代碼。

inline float2 ParallaxUvDelta(Input i)
{
	float3 viewDir = normalize(i.viewDir);
	
	// 細分的層數
    const float numLayers = 20;

    // 單層步進的高度
    float layerHeight = 1.0 / numLayers;
    // 最高的高度值
    float currentLayerHeight = 1.0;
    // delta 最大值
    float2 P = viewDir.xy * _ParallaxScale; 
    // delta 單步逼近值
    float2 deltaTexCoords = P / numLayers;
    
    // 開始一步步逼近,直到找到合適的紅點
    float2 currentTexCoords = i.uv_MainTex;
	float currentDepthMapValue = tex2D(_ParallaxMap, currentTexCoords).r;
	while(currentLayerHeight > currentDepthMapValue)
	{
	    currentTexCoords -= deltaTexCoords;
	    currentDepthMapValue = tex2D(_ParallaxMap, currentTexCoords).r;  
	    currentLayerHeight -= layerHeight;  
	}
	
	// 計算 h1 和 h2
	float2 prevTexCoords = currentTexCoords + deltaTexCoords;
	float afterHeight  = currentDepthMapValue - currentLayerHeight;
	float beforeHeight = currentLayerHeight + layerHeight - tex2D(_ParallaxMap, prevTexCoords).r;
	// 利用 h1 h2 得到權重,在兩個紅點間使用權重進行差值
	float weight = afterHeight / (afterHeight + beforeHeight);
	float2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
	
	return finalTexCoords - i.uv_MainTex;
}

float2 uvDelta = ParallaxUvDelta(i);
i.uvMain += uvDelta;
i.uvBump += uvDelta;

效果圖。Parallax Mapping 從性能上來說消耗是很大的,所有的操作都是像素級別的,并且其中包含大量的紋理采樣,根據需要使用。

最后,關于 Shader 中編寫循環指令的代碼,并且循環指令無法在編譯期間展開,比如 while(condition) for(condition),使用 cg 語言編寫的話,是無法通過編譯的,因為被編譯出的中間 ARB VP/FP 不包含循環指令。所以需要添加上 ‘#pragma glsl’,讓編譯器直接編譯成 glsl。

goto blog

總結

以上是生活随笔為你收集整理的视差贴图(Parallax Mapping)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。