從0開(kāi)始的OpenGL學(xué)習(xí)(三十三)-視差貼圖

本文主要解決一個(gè)問(wèn)題:

如何使用視差貼圖得到更好的表面凹凸效果?

引言

學(xué)完法線貼圖之后,我們的工具箱中又多了一樣有用的工具。工具嘛,都有自己的長(zhǎng)處和短處。法線貼圖的優(yōu)點(diǎn)是可以通過(guò)變化表面光的散射方向來(lái)增強(qiáng)表面的凹凸細(xì)節(jié),缺點(diǎn)是無(wú)法表現(xiàn)凹凸落差大的表面,比如下面這種:


凹凸落差大的表面

上面這張圖的凹凸落差可以說(shuō)是非常清晰了。不僅有巨大的落差,還有明顯的遮擋效果,這種渲染效果不是單純的法線貼圖能實(shí)現(xiàn)的了。

為了彌補(bǔ)法線貼圖的不足,為了渲染落差更大的表面,我們就需要另外一種工具,這就是我們今天要學(xué)的視差貼圖(parallax mapping)。不過(guò),視差貼圖需要和法線貼圖一起使用才能有上圖的效果,單純的視差貼圖顯示的效果非常詭異,沒(méi)有什么實(shí)際價(jià)值。

只有視差貼圖的渲染效果慘不忍睹,像是一幅墨跡散開(kāi)的畫(huà)一樣,我就不拿出來(lái)亮瞎你的X眼了

有了視差貼圖后,配合上法線貼圖,我們就能輕松實(shí)現(xiàn)上面的效果。而且,視差貼圖的原理也很簡(jiǎn)單,如果你理解了切線空間的原理,就能很輕松的將這個(gè)工具收入到你的工具箱中。

先來(lái)看一幅對(duì)比圖:


法線貼圖與視差貼圖的對(duì)比

感受到不同沒(méi),右邊的圖就是我們要實(shí)現(xiàn)的效果。好,收拾一下凌亂的思緒,我們就要開(kāi)始學(xué)習(xí)之旅了!

視差貼圖

我們?yōu)槭裁匆靡暡钯N圖?
說(shuō)白了,就是由于計(jì)算機(jī)性能的原因,沒(méi)法計(jì)算那么多面片的渲染信息。但是呢,我們又想要有逼真的顯示效果,從而發(fā)明出來(lái)的一種欺騙眼球的手段。

視差貼圖,本質(zhì)上,是一種位移貼圖(Displacement Mapping)。它的原理,是根據(jù)視線方向,對(duì)當(dāng)前看到的像素位置進(jìn)行一定的坐標(biāo)偏移,顯示偏移過(guò)后的像素顏色。這樣,就能有產(chǎn)生這種凹凸落差很大的視覺(jué)效果。光是文字說(shuō)明不太好理解,不要緊,來(lái)看這張圖:


根據(jù)視線偏移效果

如果我們是在觀察一個(gè)真實(shí)世界中的凹凸表面(比如磚墻,紅色表示磚塊部分),我們從眼睛的位置看過(guò)去,能看到的是圖上的點(diǎn)B。這很容易理解。但在計(jì)算機(jī)的圖形世界里,因?yàn)槲覀儾粫?huì)把磚墻這種沒(méi)什么用的模型做的很精細(xì),我們實(shí)際的磚墻模型可能就是個(gè)長(zhǎng)方體。這樣,我們的磚墻表面就是一個(gè)非常平整表面,我們能看到的點(diǎn)就是點(diǎn)A。于是,我們就需要通過(guò)某種計(jì)算方法,將真正看到的點(diǎn)A計(jì)算到應(yīng)該看到的點(diǎn)B,對(duì)點(diǎn)B的紋理采樣顯示,這樣,我們才能得到一種真實(shí)世界中的磚墻效果。

那么,我們?cè)撛趺从?jì)算點(diǎn)B的位置呢?一個(gè)令人沮喪的消息是,我們無(wú)法精確計(jì)算出點(diǎn)B的坐標(biāo)。但是,我們可以計(jì)算出一個(gè)靠近點(diǎn)B的坐標(biāo)來(lái)代替,如果這個(gè)替代點(diǎn)離點(diǎn)B足夠近,我們也能得到非常真實(shí)的效果。

計(jì)算原理

如圖所示,假設(shè)點(diǎn)A的高度為H(A),點(diǎn)B的高度為H(B)(圖中未標(biāo)明H(B))。要計(jì)算多少的偏移量才能使A偏移到B呢?在2D平面中,這個(gè)偏移量無(wú)法精確的計(jì)算出來(lái),一個(gè)比較靠譜的近似方法是:從點(diǎn)A開(kāi)始的觀察向量P,將其長(zhǎng)度截?cái)嗟紿(A)大小,向量P終點(diǎn)的位置就是我們要采樣的位置,即圖中的H(P)。

運(yùn)氣好的話,H(P)正好是H(B),運(yùn)氣不好的話就不是。現(xiàn)實(shí)情況是,絕大多數(shù)情況下,我們都不會(huì)有那么好的運(yùn)氣,采樣點(diǎn)P要么比點(diǎn)B近,要么比點(diǎn)B遠(yuǎn),不過(guò)不必?fù)?dān)心,除了這種采樣方法,我們還有其他更精確的采樣方法。

明確原理之后,我們來(lái)看看如何計(jì)算。先計(jì)算向量P的長(zhǎng)度表達(dá)式。根據(jù)向量乘法規(guī)則,假設(shè)點(diǎn)A的法向量Na為(0,0,1),向量P為(Px, Py, Pz),計(jì)算兩個(gè)向量的點(diǎn)積得到的結(jié)果是:

Na * P = |Na| * |P| * cos(x)。

假設(shè)x是Na與向量P的夾角。由此展開(kāi),我們可以得到:

0 * Px + 0 * Py + 1 * Pz = 1 * 1 * cos(x)

計(jì)算角度時(shí),向量P可以轉(zhuǎn)換成單位向量。我們就得到一個(gè)最終的表達(dá)式:

Pz = cos(x)

接著,列出三角表達(dá)式:

cos(x) = Length(Na) / Length(P) => Length(P) = Length(Na) / cos(x) = Length(Na) / Pz

將表達(dá)式中的Na換成H(A),向量P的長(zhǎng)度與H(A)一樣,就得到了最終的表達(dá)式:

Length(P) = H(A) / Pz

要注意的是,之所以能將Na假設(shè)成(0,0,1),是因?yàn)槲覀儺?dāng)前的計(jì)算空間是切線空間。這個(gè)知識(shí)點(diǎn)在上一篇法線貼圖的文章里有詳細(xì)介紹,出于新手保護(hù)的目的,我們來(lái)復(fù)習(xí)一下:

所謂切線空間,就是以表面法向量為z軸,紋理坐標(biāo)UV為系統(tǒng)x軸和y軸所組成的空間。這個(gè)空間存在的唯一目的是在這個(gè)空間中進(jìn)行法線貼圖、視差貼圖等等的工作十分方便。

深度圖vs高度圖

在實(shí)際的應(yīng)用中,我們通常會(huì)用深度圖來(lái)代替高度圖進(jìn)行位移計(jì)算。因?yàn)樵谄矫嫔希疃缺雀叨雀袑?shí)際意義。這點(diǎn)小改變對(duì)我們上述的原理不會(huì)造成任何影響,只是在最后的偏移計(jì)算時(shí),需要把+向量的操作改成-向量。這點(diǎn)細(xì)節(jié)部分我會(huì)在代碼中注明,所以不用擔(dān)心。


深度圖

我們計(jì)算的向量P現(xiàn)在反過(guò)來(lái)了,如果是高度圖,我們的紋理坐標(biāo)計(jì)算方法是:點(diǎn)A坐標(biāo)+向量P。現(xiàn)在變成深度圖之后,我們的計(jì)算方法是:點(diǎn)A坐標(biāo)-向量P。其余的不變,這點(diǎn)不難理解吧?

代碼實(shí)現(xiàn)

先到這里下載紋理圖法線貼圖深度圖

和上一章的法線貼圖計(jì)算方式相比,最大的不同就是我們不在世界空間中計(jì)算了。所以,我們采用的方法是將觀察位置、光源位置、片元位置轉(zhuǎn)換到切線空間,在切線空間中進(jìn)行視差的計(jì)算。講到這里,你應(yīng)該猜到了,沒(méi)錯(cuò),我們就是在上一章中方法2的基礎(chǔ)上進(jìn)行修改。

頂點(diǎn)著色器代碼不需要改變,直接復(fù)制過(guò)來(lái)就行,取一個(gè)新文件名,例如:
withParallaxMap.vs

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 aTangent;
layout (location = 4) in vec3 aBitangent;

out VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} vs_out;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;

uniform vec3 lightPos;
uniform vec3 viewPos;

void main()
{
    vs_out.FragPos = vec3(model * vec4(aPos, 1.0));   
    vs_out.TexCoords = aTexCoords;
    
    mat3 normalMatrix = transpose(inverse(mat3(model)));
    vec3 T = normalize(normalMatrix * aTangent);
    vec3 N = normalize(normalMatrix * aNormal);
    T = normalize(T - dot(T, N) * N);
    vec3 B = cross(N, T);
    
    mat3 TBN = transpose(mat3(T, B, N));    
    vs_out.TangentLightPos = TBN * lightPos;
    vs_out.TangentViewPos  = TBN * viewPos;
    vs_out.TangentFragPos  = TBN * vs_out.FragPos;
        
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

主要工作集中在片元著色器中。先把上一章中方法二片元著色器復(fù)制過(guò)來(lái),改名成withParallaxMap.fs。緊接著,添上接收深度圖的代碼:

uniform sampler2D depthMap;

計(jì)算紋理偏移時(shí),我們將計(jì)算的方法封裝到一個(gè)函數(shù)中,取名ParallaxMapping。這函數(shù)接收兩個(gè)參數(shù)作為輸入,分別是紋理坐標(biāo)和是視向量,計(jì)算完成后,將偏移后的紋理坐標(biāo)作為返回值輸出:

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{ 
    float depth =  texture(depthMap, texCoords).r;
    vec2 p = viewDir.xy * depth / viewDir.z * 0.1;
    return texCoords - p;    
} 

最關(guān)鍵的是中間一行計(jì)算p向量的代碼,其中:viewDir.xy表示視向量的紋理坐標(biāo),depth/viewDir.z就是上面的H(A)/Pz,0.1是一個(gè)修正值。因?yàn)槲覀兊挠?jì)算都是近似計(jì)算,所以必須要有一個(gè)修正值來(lái)進(jìn)行調(diào)整,調(diào)整到最合適的修正值。這過(guò)程可能很繁瑣,你可以用一個(gè)uniform變量來(lái)代替,在主函數(shù)中接受鍵盤(pán)的輸入來(lái)調(diào)整這個(gè)數(shù)值,看看最終效果如何。

關(guān)于修正值,有些文獻(xiàn)中會(huì)對(duì)采樣后的深度值進(jìn)行一次修正,一種修正的方程是h = h * s + b。s表示縮放因子,b表示修正值。這里我直接用了一個(gè)數(shù)字來(lái)0.1來(lái)代替。因?yàn)榉凑呀?jīng)是修正值了,具體數(shù)據(jù)無(wú)法精確計(jì)算,還不如直接進(jìn)行調(diào)整找到最好的數(shù)據(jù)。事實(shí)上,連除以viewDIr.z這個(gè)操作都可以省略掉,直接乘上修正值,調(diào)整修正值到最佳效果就好了。

在主函數(shù)中,我們的代碼就變成:

void main()
{           
    //采樣視差貼圖
    vec3 viewDir   = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
    vec2 texCoords = ParallaxMapping(fs_in.TexCoords,  viewDir);

     // 采樣法線貼圖
    vec3 normal = texture(normalMap, texCoords).rgb;
    // 轉(zhuǎn)換法向量到[-1,1]范圍
    normal = normalize(normal * 2.0 - 1.0);  // 此向量是切線空間中的向量
...
    // 采樣漫反射
    vec3 color = texture(diffuseMap, texCoords).rgb;//fs_in.TexCoords).rgb;
...
    // 鏡面高光
    //vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
...
}

在main.cpp中加上一些邊緣代碼后,編譯運(yùn)行,你就能看到這樣的效果:


運(yùn)行效果

非常好,這就是我們上面的效果,不過(guò)還有點(diǎn)瑕疵,就在邊緣的部分,請(qǐng)看:


邊緣瑕疵

超出紋理坐標(biāo)范圍的坐標(biāo)我們可以直接丟棄,還記得丟棄的代碼嗎?沒(méi)錯(cuò),就是discard:

    if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0)
        discard;

將上面的代碼添加到調(diào)用ParallaxMapping函數(shù)之后,再次運(yùn)行程序,現(xiàn)在的效果好多了。


運(yùn)行效果

如果顯示不正確,請(qǐng)參考這里的源碼進(jìn)行修改。

等等,這就完了嗎?當(dāng)然不是,這只是最基本的方法,這種方法在我們現(xiàn)在的角度觀察是沒(méi)問(wèn)題,換一個(gè)平一點(diǎn)的角度問(wèn)題就大了。


普通視差貼圖的問(wèn)題

看到?jīng)],在一個(gè)比較水平的角度上看,或者高度圖的落差一大,表面的顯示一塌糊涂。出現(xiàn)這種情況的原因是我們的采樣方式太粗糙了,只是計(jì)算了一個(gè)采樣點(diǎn)就采樣了。這種粗糙的采樣方式在高度變化劇烈或者角度刁鉆的情況下就崩盤(pán)了。解決的方法也很簡(jiǎn)單,既然采樣一次太少,我們多采樣幾次不就行了?這種多次采樣的方式被稱(chēng)作陡峭視差貼圖。

陡峭視差貼圖(Steep Parallax Map)

陡峭視差貼圖的原理,是設(shè)定一個(gè)采樣層數(shù),每一層都對(duì)相應(yīng)紋理采樣,當(dāng)采樣到小于當(dāng)前層深度的紋理坐標(biāo)時(shí),返回此紋理坐標(biāo)。如圖:


陡峭視差貼圖原理

研究一下上面的圖:我們把整個(gè)深度分成了5層,分別是:0.2、0.4、0.6、0.8和1.0。陡峭視差貼圖是這樣進(jìn)行計(jì)算的,首先從第一層,也就是深度值為0.2的那一層開(kāi)始,采樣當(dāng)前的深度值為1.0,這個(gè)值大于當(dāng)前的層深度0.2,所以需要繼續(xù)采樣。然后,采樣第二層(0.4),這層的深度值為0.73,它也大于層深度,所以這個(gè)深度值也不能用,需要繼續(xù)采樣。于是到了第三層(0.6)這次采樣的深度值是0.37,它小于當(dāng)前層深度,所以,這個(gè)紋理坐標(biāo)就會(huì)被采用。

讓我們用代碼來(lái)實(shí)現(xiàn):

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{ 
    //將整個(gè)深度區(qū)域分為10層
    const float numLayers = 10;
    //每一層的深度
    float layerDepth = 1.0 / numLayers;
    //當(dāng)前層深度
    float currentLayerDepth = 0.0;
    //紋理坐標(biāo)的完整范圍
    vec2 p = viewDir.xy * 0.1;
    //每一層紋理的變化值
    vec2 deltaTexCoords = p / numLayers;

    vec2 currentTexCoords = texCoords;  //起始紋理坐標(biāo)
    float currentDepthMapValue = texture(parallaxMap, currentTexCoords).r;  //起始深度

    while(currentLayerDepth < currentDepthMapValue)
    {
        currentTexCoords -= deltaTexCoords;     //我們的p向量是指向眼睛的,而紋理值需要往反方向變化,所以這里是-deltaTexCoords
        currentDepthMapValue = texture(parallaxMap, currentTexCoords).r;  
        currentLayerDepth += layerDepth;  
    }

    return currentTexCoords;   
} 

在我們的實(shí)現(xiàn)中,我們將整個(gè)深度范圍分成了10層。紋理范圍就直接用了0.1這個(gè)修正值,因?yàn)橐呀?jīng)不需要height這個(gè)值了。具體的計(jì)算流程和之前講的原理一樣,就是一層層地采樣比較深度值,如果采樣的深度值小于當(dāng)前的層深度,那么采樣結(jié)束,如果大于,就繼續(xù)采樣。要注意的是紋理坐標(biāo)的變化是減去當(dāng)前的向量變化量,因?yàn)橄蛄縋是指向眼睛的,我們需要往它的反方向增加。

等等,先下載這次運(yùn)行要用的圖片:紋理圖法線貼圖視差貼圖

現(xiàn)在可以編譯運(yùn)行了,趕緊動(dòng)手,你會(huì)看到這樣的效果:


運(yùn)行效果

這就明顯沒(méi)有上圖中的塌陷效果了,但是出現(xiàn)了另一個(gè)問(wèn)題:鋸齒,非常明顯的鋸齒。出現(xiàn)鋸齒的原因想必我不說(shuō)你也知道,就是采樣數(shù)量不夠,如果我們把層數(shù)改成100,會(huì)是什么效果:


運(yùn)行效果

看吧,鋸齒效果很明顯就消失了,整個(gè)場(chǎng)景看上去都不錯(cuò)。但是!你確定要分100層嗎?每個(gè)坐標(biāo)都來(lái)100層的采樣我們的電腦基本上也就壽終正寢了。我們當(dāng)然不能讓這種事情發(fā)生,于是,聰明的前輩先驅(qū)們想出了2種方法,分別是:視差遮蔽貼圖(Parallax Occlusion Mapping)和浮雕視差貼圖(Relief Parallax Mapping)。這兩種方法都能以相對(duì)100層采樣較小的代價(jià)來(lái)實(shí)現(xiàn)平滑表面的效果,比較來(lái)說(shuō),浮雕視差貼圖的效果更好,但是消耗更大,視差遮蔽貼圖的效果不如浮雕視差貼圖,但它的消耗小。具體的需要就看你的選擇了。出于學(xué)習(xí)目的,這兩種方法我們都要來(lái)實(shí)現(xiàn)一次。先來(lái)看視差遮蔽貼圖。

視差遮蔽貼圖(Parallax Occlusion Mapping)

視差遮蔽貼圖的原理,就是取與交點(diǎn)相鄰的兩個(gè)層的層深度和采樣深度,計(jì)算出兩個(gè)深度值的權(quán)重,根據(jù)權(quán)重采樣兩個(gè)紋理坐標(biāo)之間某個(gè)位置的紋理坐標(biāo),如下圖:


視差遮蔽貼圖原理

與交點(diǎn)相鄰的層深度和采樣深度分別是0.6(層深度)、0.37(采樣深度)和0.4(層深度)、0.73(采樣深度)。最終的紋理坐標(biāo)也必定在這兩個(gè)坐標(biāo)之間,只是到底取哪個(gè)坐標(biāo)呢?我們的方法,就是取圖中的P點(diǎn)坐標(biāo)。先計(jì)算H(T2)和H(T3)的值,分別是0.33和0.23,T3的權(quán)重就是0.23 / (0.33+0.23)=0.41,然后用T3的紋理坐標(biāo)乘上(1-0.41),T2的紋理坐標(biāo)乘上0.41,兩個(gè)坐標(biāo)值相加,就得到了我們想要的紋理坐標(biāo)。

說(shuō)了這么多,趕緊來(lái)修改陡峭視差貼圖中的源碼,實(shí)現(xiàn)視差遮蔽貼圖的效果吧:

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{ 
    ...  //陡峭視差貼圖部分代碼
    //前一個(gè)紋理坐標(biāo)點(diǎn)
    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

    //當(dāng)前深度差
    float afterDepth = currentLayerDepth - currentDepthMapValue;
    //前一個(gè)坐標(biāo)點(diǎn)深度差
    float beforeDepth = texture(parallaxMap, prevTexCoords).r - (currentLayerDepth - layerDepth);
    //當(dāng)前坐標(biāo)點(diǎn)的深度值權(quán)重
    float weight = afterDepth / (afterDepth + beforeDepth);
    vec2 finalTexCoords = prevTexCoords * weight +   currentTexCoords * (1.0 - weight);

    return finalTexCoords;   
} 

前面的代碼不需要改,在后面加上視差遮蔽效果的實(shí)現(xiàn)。先算出前一個(gè)點(diǎn)的紋理坐標(biāo),計(jì)算出兩個(gè)點(diǎn)的深度差,根據(jù)我們之前的原理,計(jì)算出權(quán)重,最后計(jì)算出最終紋理坐標(biāo)。代碼應(yīng)該非常直觀,講這么多已屬?gòu)U話連篇,趕緊編譯運(yùn)行:


運(yùn)行效果

怎么樣,效果不錯(cuò)吧,跟100層采樣效果比也有一拼。

浮雕視差貼圖(Relief Parallax Mapping)

浮雕視差貼圖的原理和陡峭視差貼圖類(lèi)似,不過(guò)它更聰明。它是在獲取到了交點(diǎn)左右兩個(gè)相鄰點(diǎn)的紋理坐標(biāo)和深度信息之后(這點(diǎn)和視差遮蔽貼圖類(lèi)似),再對(duì)其進(jìn)行逼近,采用的方法是2分漸進(jìn)。就是確定了左右兩個(gè)坐標(biāo)點(diǎn)之后,取兩坐標(biāo)的中點(diǎn)位置,用這個(gè)坐標(biāo)來(lái)采樣深度信息,如果這個(gè)深度信息小于層深度,那么這個(gè)中點(diǎn)坐標(biāo)就取代原有的左坐標(biāo)點(diǎn);如果這個(gè)深度信息大于層深度,那么這個(gè)中點(diǎn)坐標(biāo)就取代原有的右坐標(biāo)點(diǎn)。然后繼續(xù)取中點(diǎn),再做比較,如此往復(fù)一定次數(shù)之后,采樣到的紋理坐標(biāo)就非常接近真實(shí)坐標(biāo)了。像這樣:


原理

把原理翻譯成代碼就是:

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{ 
    ... //陡峭視差貼圖的源碼

    vec2 dtex = deltaTexCoords / 2;    //紋理步長(zhǎng)取半
    float deltaLayerDepth = layerDepth / 2;  //深度步長(zhǎng)取半

    //計(jì)算當(dāng)前的紋理和層深度
    currentTexCoords += dtex;
    currentLayerDepth -= deltaLayerDepth;

    const int numSearches = 10;    //進(jìn)行10次2分漸進(jìn)
    for (int i = 0; i < numSearches; ++i) {
        //每次紋理步長(zhǎng)和深度步長(zhǎng)都會(huì)減半
        dtex /= 2;
        deltaLayerDepth /= 2;

        //采樣當(dāng)前紋理
        currentDepthMapValue = texture(parallaxMap, currentTexCoords).r; 
        if (currentDepthMapValue > currentLayerDepth) {
            //如果當(dāng)前深度大于層深度,往左逼近
            currentTexCoords  -= dtex;
            currentLayerDepth += deltaLayerDepth;
        }
        else {
            //如果當(dāng)前深度小于層深度,往右逼近
            currentTexCoords  += dtex;
            currentLayerDepth -= deltaLayerDepth;
        }
    }

    return currentTexCoords;   
} 

代碼非常直觀易懂,不多解釋?zhuān)苯泳幾g運(yùn)行:


運(yùn)行效果

效果不錯(cuò),和100層陡峭視差貼圖效果類(lèi)似,和視差遮蔽貼圖的效果比就沒(méi)啥優(yōu)勢(shì)了,因?yàn)槲覀冎贿M(jìn)行了10次2分漸進(jìn),如果多進(jìn)行幾次,比如50次,就能從細(xì)微處看到區(qū)別了。只是,這個(gè)代價(jià)跟100層采樣區(qū)別也不大,所以還是推薦視差遮蔽貼圖,效果又好速度又快。


視差遮蔽貼圖和50次漸近的浮雕視差貼圖效果比較

最后,老規(guī)矩,上傳全部源碼以供參考。

嘮嘮嗑

在學(xué)習(xí)視差貼圖的時(shí)候,我遇到的最大困難就是,看到一段給出的代碼,里面計(jì)算方法跟前面講的原理差了十萬(wàn)八千里,完全不知道為什么要進(jìn)行那一步計(jì)算。就比如說(shuō)除以viewDir.z這個(gè)操作,為啥呢?里面的推導(dǎo)過(guò)程呢?完全沒(méi)有,導(dǎo)致我在這一個(gè)問(wèn)題上卡了3天,真糾結(jié)。所幸最后還是被我腦補(bǔ)出原理來(lái),不得不說(shuō)真是幸運(yùn)。還有一個(gè)問(wèn)題就是計(jì)算偏移的公式不同的資料中不一樣,都能得到準(zhǔn)確的效果,就是實(shí)現(xiàn)的方式每個(gè)作者都有自己的理解。這點(diǎn),在文章中我也給出了自己的見(jiàn)解,算是對(duì)自己有個(gè)交代。如果我的理解有誤,歡迎各位讀者嚴(yán)厲指正。

另外,還有一個(gè)問(wèn)題沒(méi)有解決,就是在進(jìn)行視差遮蔽貼圖計(jì)算的時(shí)候,為什么是前一個(gè)紋理坐標(biāo)乘上當(dāng)前的權(quán)重,當(dāng)前坐標(biāo)卻乘上(1-當(dāng)前權(quán)重)?百思不得其解,還希望路過(guò)的高手可以解答一下,萬(wàn)分感謝!

總結(jié)

回顧一下這一章中學(xué)的內(nèi)容。這一章我們就是用視差貼圖來(lái)實(shí)現(xiàn)法線貼圖不能實(shí)現(xiàn)的凹凸效果明顯的表面。原理是在當(dāng)前看到的位置上進(jìn)行一個(gè)坐標(biāo)偏移,偏移到現(xiàn)實(shí)中應(yīng)該落在的坐標(biāo)上,然后對(duì)這個(gè)坐標(biāo)進(jìn)行紋理采樣,獲得逼真的效果。具體的實(shí)現(xiàn)方式是:

  • 采用當(dāng)前紋理深度作為偏移長(zhǎng)度進(jìn)行偏移
  • 采用多層漸近的方式獲取最合適的偏移
  • 采用多層漸近,再加上線性插值的方式來(lái)獲取最合適的偏移
  • 采用多層漸近,再加上2分漸近的方式來(lái)獲取最合適的偏移

并沒(méi)有什么神奇的地方,好了,學(xué)完收工,我們放松一下!

下一篇
目錄
上一篇

參考資料

learnopengl
帶偏移限制的視差貼圖
Parallax Occlusion Mapping in GLSL

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。