Elevated代碼解析

效果大致如下(本來(lái)想上傳GIF圖的,可惜大于10M),還有下面的筆記是直接從MD復(fù)制過(guò)來(lái)的,有點(diǎn)丑,感興趣的可以看看我的MD版,顏值高多了,附鏈接https://jmx-paper.oss-cn-beijing.aliyuncs.com/ShaderToy%E4%BC%98%E7%A7%80%E4%BB%A3%E7%A0%81%E9%98%85%E8%AF%BB/Elevated%E4%BB%A3%E7%A0%81%E8%A7%A3%E6%9E%90.md

效果圖

Elevated代碼解析

作者:iq,網(wǎng)址:https://www.shadertoy.com/view/MdX3Rr

標(biāo)簽:procedural, 3d, raymarching, distancefield, terrain, motionblur

總共兩個(gè)部分:Image,Buffer A

Image

voidmainImage(outvec4fragColor,invec2fragCoord)

{

vec2uv=fragCoord/iResolution.xy;

vec4data=texture(iChannel0,uv);

?

vec3col=vec3(0.0);

if(data.w<0.0)

?? {

col=data.xyz;

?? }

else

?? {

// decompress velocity vector

floatss=mod(data.w,256.0)/255.0;

floatst=floor(data.w/256.0)/255.0;

?

// motion blur (linear blur across velocity vectors

vec2dir=(-1.0+2.0*vec2(ss,st))*0.25;

col=vec3(0.0);

for(inti=0;i<32;i++)

? ? ?? {

floath=float(i)/31.0;

vec2pos=uv+dir*h;

col+=texture(iChannel0,pos).xyz;

? ? ?? }

col/=32.0;

?? }


// vignetting?

? ? col*=0.5+0.5*pow(16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y),0.1);

?

col=clamp(col,0.0,1.0);

col=col*0.6+0.4*col*col*(3.0-2.0*col)+vec3(0.0,0.0,0.04);


?


fragColor=vec4(col,1.0);

}

讀取BufferA的計(jì)算結(jié)果,據(jù)此來(lái)說(shuō),xyz分量存儲(chǔ)的是最終計(jì)算結(jié)果(color),w存的是速度向量。

vec4data=texture(iChannel0,uv);

如果速度小于0,則說(shuō)明場(chǎng)景靜止,直接取xyz分量,否則進(jìn)行運(yùn)動(dòng)模糊(motion blur)

進(jìn)行運(yùn)動(dòng)模糊時(shí),首先進(jìn)行對(duì)速度矢量進(jìn)行解壓縮。

floatss=mod(data.w,256.0)/255.0;

floatst=floor(data.w/256.0)/255.0;

第一個(gè)是用w分量對(duì)256求模,然后除以255,第二個(gè)是用w分量除以246,取整后除以255,為什么這樣解碼,估計(jì)答案在Buffer A里面。

利用解碼得到的ss,st計(jì)算速度向量,區(qū)間重映射為[-0.25,0.25],這里為什么是0.25?我在測(cè)試中改為[-1,1]后,運(yùn)動(dòng)模糊效果過(guò)于眼中,場(chǎng)景明顯有條紋以及暈眩感,這里可能是調(diào)節(jié)的結(jié)果

vec2dir=(-1.0+2.0*vec2(ss,st))*0.25;

接下來(lái)是簡(jiǎn)單的運(yùn)動(dòng)模糊,累加32次后平均

for(inti=0;i<32;i++)

{

floath=float(i)/31.0;

vec2pos=uv+dir*h;

col+=texture(iChannel0,pos).xyz;

}

col/=32.0;

然后是Vignetting效果(漸暈;光暈,光損失;暗角)

col*=0.5+0.5*pow(16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y),0.1);

這個(gè)效果是這樣的

這行代碼的呈現(xiàn)結(jié)果是:場(chǎng)景由黃,變白亮,有點(diǎn)像黃昏和白天的區(qū)別。

col=col*0.5+0.5*col*col*(3.0-2.0*col)+vec3(0.0,0.0,0.04);

總體來(lái)說(shuō),Image是對(duì)Buffer A的結(jié)果進(jìn)行運(yùn)動(dòng)模糊,以及色彩調(diào)節(jié)等處理,比較簡(jiǎn)單,我們可以參考的是 簡(jiǎn)單的運(yùn)動(dòng)模糊 和 vignetting 效果。

Buffer A

這里的代碼是主要功能的實(shí)現(xiàn),有幾百行,就不在開(kāi)頭貼出來(lái)了,我們從主函數(shù)開(kāi)始分析。

一開(kāi)始,對(duì)運(yùn)行時(shí)間進(jìn)行處理,用作之后的相機(jī)移動(dòng),這里還用了鼠標(biāo)輸入,用作加速移動(dòng)(可以理解為瞬移)

floattime=iTime*0.1-0.1+0.3+4.0*iMouse.x/iResolution.x;

照相機(jī)的處理

然后我們進(jìn)入了moveCamera函數(shù),參數(shù)為:時(shí)間,觀看位置,觀看方向,cr和fl不知道是什么,然后其中使用的全局變量 SC 為 250.0

voidmoveCamera(floattime,outvec3oRo,outvec3oTa,outfloatoCr,outfloatoFl)

{

? ? vec3ro=camPath(time);

? ? vec3ta=camPath(time+3.0);

? ? ro.y=terrainL(ro.xz)+22.0*SC;

? ? ta.y=ro.y-20.0*SC;

? ? floatcr=0.2*cos(0.1*time);

oRo=ro;

oTa=ta;

oCr=cr;

oFl=3.0;

}

vec3camPath(floattime)

{

? ? returnSC*1100.0*vec3(cos(0.0+0.23*time),0.0,cos(1.5+0.21*time) );

}

對(duì)于camPath,明顯是計(jì)算相機(jī)的x,z位置,這里的問(wèn)題的常量SC和1100為什么這么大,暫且不知。推測(cè)原因是采樣擴(kuò)大,坐標(biāo)范圍極大,畢竟我們這里顯示的是無(wú)邊的地形。此外,SC是全局變量,是有很多意義的,但調(diào)節(jié)的效果無(wú)法歸納其作用,暫時(shí)可以理解為某個(gè)值的單位變量,而1100這個(gè)常量根據(jù)調(diào)整的結(jié)果,可以理解為相機(jī)的移動(dòng)速度。

然后根據(jù)terrainL計(jì)算此時(shí)相機(jī)xz位置對(duì)應(yīng)的地形高度。(這里的算法就不做介紹了,個(gè)人估計(jì)是額外的地形生成算法),然后往上面做一個(gè)偏移,求出觀察位置的Y(高度)以及視線向量的Y。有三個(gè)函數(shù),本質(zhì)是相同的,后綴L,M,H分別對(duì)應(yīng)地形生成檢測(cè)的精度等級(jí)。

floatterrainL(invec2x)

{

? ? vec2p=x*0.003/SC;

floata=0.0;

floatb=1.0;

? ? vec2d=vec2(0.0);

for(inti=0;i<3;i++)

?? {

vec3n=noised(p);

d+=n.yz;

a+=b*n.x/(1.0+dot(d,d));

? ? ? ? b*=0.5;

p=m2*p*2.0;

?? }

?

? ? returnSC*120.0*a;

}

回到主函數(shù),得到了幾個(gè)相機(jī)采數(shù)之后,就是設(shè)置相機(jī),獲得反V矩陣。比較簡(jiǎn)單和常見(jiàn),就是通過(guò)叉乘進(jìn)行計(jì)算求值。然后cr的作用就出現(xiàn)了。

mat3setCamera(invec3ro,invec3ta,infloatcr)

{

? ? vec3cw=normalize(ta-ro);

? ? vec3cp=vec3(sin(cr),cos(cr),0.0);

? ? vec3cu=normalize(cross(cw,cp) );

? ? vec3cv=normalize(cross(cu,cw) );

returnmat3(cu,cv,cw);

}

然后進(jìn)入抗鋸齒的循環(huán)之中,將屏幕坐標(biāo)p重映射回裁剪空間,然后使用fl和得到的相機(jī)矩陣,計(jì)算射線在世界空間的值。因此fl可以理解為裁剪平面的位置。

vec3rd=cam*normalize(vec3(s,fl));

天空,雪和山地的處理

在之后,進(jìn)入渲染的總函數(shù)Render中

vec4res=render(ro,rd);

t=min(t,res.w);

這一部分是進(jìn)行加速,縮小[tmin,tmax]的區(qū)間范圍

floatmaxh=300.0*SC;

floattp=(maxh-ro.y)/rd.y;

if(tp>0.0)

{

if(ro.y>maxh)tmin=max(tmin,tp);

elsetmax=min(tmax,tp);

}

然后分析interesct函數(shù),這里也是進(jìn)行了常規(guī)的相交測(cè)試,或者說(shuō)距離場(chǎng)測(cè)試,返回射線移動(dòng)的距離。關(guān)于terrainM函數(shù),和之前一樣暫不討論。

floatinteresct(invec3ro,invec3rd,infloattmin,infloattmax)

{

floatt=tmin;

? ? for(inti=0;i<300;i++)

? ? {

//RayMarching

vec3pos=ro+t*rd;

//計(jì)算高度插值

? ? ? ? floath=pos.y-terrainM(pos.xz);

//0.0015*t起到一個(gè)優(yōu)化加速的效果

? ? ? ? if(abs(h)<(0.0015*t)||t>tmax)break;

? ? ? ? t+=0.4*h;

? ? }

?

returnt;

}


? 如果返回結(jié)果大于tmax,這說(shuō)明沒(méi)有擊中地形,我們要==渲染天空,這里的天空渲染實(shí)在巧妙,可以借鑒==


? ```c#

// sky? ? 根據(jù)Y的坐標(biāo)模擬天空漸變的藍(lán)色 ,第二行和海平面處理近似,但變化沒(méi)有那么急劇,效果相對(duì)于給藍(lán)天套了一層由下至上逐漸稀釋的白霧

? col = vec3(0.3,0.5,0.85) - rd.y*rd.y*0.5;

? col = mix( col, 0.85*vec3(0.7,0.75,0.85), pow( 1.0-max(rd.y,0.0), 4.0 ) );

? // sun 增光來(lái)達(dá)到模擬太陽(yáng)光暈的效果,有點(diǎn)像經(jīng)典高光的計(jì)算

? col += 0.25*vec3(1.0,0.7,0.4)*pow( sundot,5.0 );

? col += 0.25*vec3(1.0,0.8,0.6)*pow( sundot,64.0 );

? col += 0.2*vec3(1.0,0.8,0.6)*pow( sundot,512.0 );

? // clouds 不太懂的云模擬

? vec2 sc = ro.xz + rd.xz*(SC*1000.0-ro.y)/rd.y;

? col = mix( col, vec3(1.0,0.95,1.0), 0.5*smoothstep(0.5,0.8,fbm(0.0005*sc/SC)) );

? // horizon 邏輯簡(jiǎn)單但適用的地平線模擬,在海平面0處附近生效

col = mix( col, 0.68*vec3(0.4,0.65,1.0), pow( 1.0-max(rd.y,0.0), 16.0 ) );

? t = -1.0;

? 其中,fbm代表分?jǐn)?shù)布朗運(yùn)動(dòng)

? float fbm( vec2 p )

? {

? ? ? float f = 0.0;

? ? ? f += 0.5000*texture( iChannel0, p/256.0 ).x; p = m2*p*2.02;

? ? ? f += 0.2500*texture( iChannel0, p/256.0 ).x; p = m2*p*2.03;

? ? ? f += 0.1250*texture( iChannel0, p/256.0 ).x; p = m2*p*2.01;

? ? ? f += 0.0625*texture( iChannel0, p/256.0 ).x;

? ? return f/0.9375;

? }

? 如果返回結(jié)果小于tmax,則開(kāi)始渲染地面,首先簡(jiǎn)單的計(jì)算擊中點(diǎn)的法線,這是地形計(jì)算法線的版本,具體法線計(jì)算的各種情況可見(jiàn)IQ6

? vec3 calcNormal( in vec3 pos, float t )

? {

? ? ? vec2? eps = vec2( 0.001*t, 0.0 );

? ? ? return normalize( vec3( terrainH(pos.xz-eps.xy) - terrainH(pos.xz+eps.xy),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.0*eps.x,

? ? ? ? ? ? ? ? ? ? ? ? ? ? terrainH(pos.xz-eps.yx) - terrainH(pos.xz+eps.yx) ) );

? }

? 然后,計(jì)算巖石的顏色,這里的核心代碼是第一行和第二行,后續(xù)都是一些優(yōu)化和增加隨機(jī)性,但是確實(shí)看不太懂,最后一行是添加細(xì)節(jié)的顏色變化。

? float r = texture( iChannel0, (7.0/SC)*pos.xz/256.0 ).x;

? col = (r*0.25+0.75)*0.9*mix( vec3(0.08,0.05,0.03), vec3(0.10,0.09,0.08), texture(iChannel0,0.00007*vec2(pos.x,pos.y*48.0)/SC).x );

? //在效果上體現(xiàn)為:增加后,顏色由泛白變得正常

col = mix( col, 0.20*vec3(0.45,.30,0.15)*(0.50+0.50*r),smoothstep(0.70,0.9,nor.y) );

? //無(wú)明顯效果

col = mix( col, 0.15*vec3(0.30,.30,0.10)*(0.25+0.75*r),smoothstep(0.95,1.0,nor.y) );

? //相當(dāng)于細(xì)節(jié)貼圖

? col *= 0.1+1.8*sqrt(fbm(pos.xz*0.04)*fbm(pos.xz*0.005));

? 雪的計(jì)算。首先,對(duì)于參數(shù)h,我們知道的是他跟地形的高度有關(guān),除以單位值SC得到高度的無(wú)符號(hào)數(shù)值,然后加上一個(gè)分?jǐn)?shù)布朗的相關(guān)隨機(jī)值,關(guān)于參數(shù)內(nèi)部除以SC,這個(gè)是無(wú)所謂的,對(duì)于效果沒(méi)有影響,竊以為是統(tǒng)一格式,畢竟前面除了??偨Y(jié)來(lái)說(shuō),這個(gè)參數(shù)決定了海拔越高,越容易被雪覆蓋的真實(shí)場(chǎng)景特性。對(duì)于參數(shù)e,則是和地形的法向量相關(guān),當(dāng)然還會(huì)有高度的影響:這里的規(guī)則是,海拔越高,出現(xiàn)雪所要求的地形法向量范圍越大——海拔高的情況,除非是峭壁,不然都有很大概率被雪覆蓋,而在海拔低的地區(qū),則很難出現(xiàn)雪,除非法向量無(wú)限接近(0,1,0)的平地,而這里,據(jù)我觀察,會(huì)有一個(gè)問(wèn)題,那就是會(huì)導(dǎo)致零星雪(海拔低但完全平行的點(diǎn)會(huì)出現(xiàn)雪,但是因?yàn)榈匦问请S機(jī)生成的,它的周圍的點(diǎn)大概率不會(huì)平行,那么就不會(huì)被雪覆蓋。這樣就會(huì)很奇怪)。對(duì)于參數(shù)o,就公式而言,和法向量的x分量和海拔高度有關(guān)(正相關(guān)),就效果而言,有無(wú),雪的分布基本無(wú)變化。但是仔細(xì)分析會(huì)有這樣的想法:場(chǎng)景中,太陽(yáng)的x坐標(biāo)是-0.8,那么nor.x是負(fù)值的情況下,則說(shuō)明該點(diǎn)所在坡是正對(duì)著太陽(yáng)的,那么很明顯,這種雪的覆蓋率應(yīng)該會(huì)降低,在通過(guò)海拔進(jìn)行修正(只要海拔夠高,管你有沒(méi)有對(duì)著太陽(yáng),當(dāng)然,峭壁除外)。最后,這三個(gè)參數(shù)進(jìn)行相乘,決定該點(diǎn)是否被雪覆蓋。

float h = smoothstep(55.0,80.0,pos.y/SC + 25.0*fbm(0.01*pos.xz/SC) );

? float e = smoothstep(1.0-0.5*h,1.0-0.1*h,nor.y);

float o = 0.3 + 0.7*smoothstep(0.0,0.1,nor.x+h*h);

? float s = h*e*o;

? col = mix( col, 0.29*vec3(0.62,0.65,0.7), smoothstep( 0.1, 0.9, s ) );

光照計(jì)算

//環(huán)境光:越水平,環(huán)境光的強(qiáng)度越強(qiáng)

float amb = clamp(0.5+0.5*nor.y,0.0,1.0);

//漫反射

float dif = clamp( dot( light1, nor ), 0.0, 1.0 );

//

float bac = clamp( 0.2 + 0.8*dot( normalize( vec3(-light1.x, 0.0, light1.z ) ), nor ), 0.0, 1.0 );

//陰影參數(shù)計(jì)算

float sh = 1.0;

if( dif>=0.0001 ) sh = softShadow(pos+light1*SC*0.05,light1);

首先,計(jì)算環(huán)境光,這里簡(jiǎn)單的進(jìn)行了模擬:越水平,環(huán)境光越強(qiáng)。然后計(jì)算漫反射,比較簡(jiǎn)單。然后對(duì)參數(shù)bac,待定,暫時(shí)不知道其含義。之后,計(jì)算陰影,具體函數(shù)如下:明顯是RayMarching中比較常見(jiàn)的柔和陰影計(jì)算,沒(méi)有什么意料之外的操作。(這一點(diǎn),在IQ博客系列閱讀中有過(guò)分析和介紹)

float softShadow(in vec3 ro, in vec3 rd )

{

? ? float res = 1.0;

? ? float t = 0.001;

for( int i=0; i<80; i++ )

{

? ? vec3? p = ro + t*rd;

? ? ? ? float h = p.y - terrainM( p.xz );

res = min( res, 16.0*h/t );

t += h;

if( res<0.001 ||p.y>(SC*200.0) ) break;

}

return clamp( res, 0.0, 1.0 );

}

在之后,是光強(qiáng)lin的具體計(jì)算,依次計(jì)算了實(shí)際具體的環(huán)境光,漫反射(當(dāng)然,陰影參數(shù)應(yīng)該在這里使用到),還有bac,最后和col相乘。這里比較意外的是,在陰影參數(shù)的使用上,對(duì)RGB三個(gè)通道進(jìn)行了不同的變化——R通道的衰減速度是要慢于G,B通道,雖然這個(gè)處理對(duì)于整個(gè)場(chǎng)景的表現(xiàn)沒(méi)有明顯影響,但還是要注意。此外,關(guān)于bac,其有無(wú)同樣對(duì)于場(chǎng)景表現(xiàn)無(wú)影響。

vec3 lin? = vec3(0.0);

lin += dif*vec3(8.00,5.00,3.00)*1.3*vec3( sh, sh*sh*0.5+0.5*sh, sh*sh*0.8+0.2*sh );

lin += amb*vec3(0.40,0.60,1.00)*1.2;

lin += bac*vec3(0.40,0.50,0.60);

col *= lin;

下面兩行公式的意義不知。在效果上,增刪與否對(duì)于表現(xiàn)無(wú)明顯影響。參數(shù)s的再次使用,應(yīng)該是讓雪和山地的光照計(jì)算產(chǎn)生一定的差異,畢竟是不同的物質(zhì),雪的光吸收應(yīng)該弱于山地,所以雪覆蓋的地方,是1,而山地則是0.7。

col += (0.7+0.3*s)*(0.04+0.96*pow(clamp(1.0+dot(hal,rd),0.0,1.0),5.0))*

? ? ? ? ? ? ? vec3(7.0,5.0,3.0)*dif*sh*

? ? ? ? ? ? ? pow( clamp(dot(nor,hal), 0.0, 1.0),16.0);


col += s*0.65*pow(fre,4.0)*vec3(0.3,0.5,0.6)*smoothstep(0.0,0.6,ref.y);

霧的計(jì)算。比較簡(jiǎn)單,比較常規(guī)的霧的冪計(jì)算方法。注釋的地方是讓霧的顏色和太陽(yáng)位置掛鉤。

float fo = 1.0-exp(-pow(0.001*t/SC,1.5) );

vec3 fco = 0.65*vec3(0.4,0.65,1.0);// + 0.1*vec3(1.0,0.8,0.5)*pow( sundot, 4.0 );

col = mix( col, fco, fo );

最后,映射回伽馬空間,返回最終Color和射線步進(jìn)的距離。

// sun scatter

col += 0.3*vec3(1.0,0.7,0.3)*pow( sundot, 8.0 );

// gamma

col = sqrt(col);


return vec4( col, t );

運(yùn)動(dòng)模糊的處理

// old camera position

float oldTime = time - 0.1 * 1.0/24.0; // 1/24 of a second blur

vec3 oldRo, oldTa; float oldCr, oldFl;

moveCamera( oldTime, oldRo, oldTa, oldCr, oldFl );

mat3 oldCam = setCamera( oldRo, oldTa, oldCr );

// world space

#if AA>1

vec3 rd = cam * normalize(vec3(p,fl));

#endif

vec3 wpos = ro + rd*t;

// camera space

vec3 cpos = vec3( dot( wpos - oldRo, oldCam[0] ),

? ? ? ? ? ? ? ? ? dot( wpos - oldRo, oldCam[1] ),

? ? ? ? ? ? ? ? ? dot( wpos - oldRo, oldCam[2] ) );

// ndc space

vec2 npos = oldFl * cpos.xy / cpos.z;

// screen space

vec2 spos = 0.5 + 0.5*npos*vec2(iResolution.y/iResolution.x,1.0);

// compress velocity vector in a single float

vec2 uv = fragCoord/iResolution.xy;

spos = clamp( 0.5 + 0.5*(spos - uv)/0.25, 0.0, 1.0 );

vel = floor(spos.x*255.0) + floor(spos.y*255.0)*256.0;

首先,時(shí)間time減去1/24,然后依據(jù)之前說(shuō)明的相機(jī)相關(guān)函數(shù),得到坐標(biāo)系變化矩陣。

// old camera position

float oldTime = time - 0.1 * 1.0/24.0; // 1/24 of a second blur

vec3 oldRo, oldTa; float oldCr, oldFl;

moveCamera( oldTime, oldRo, oldTa, oldCr, oldFl );

mat3 oldCam = setCamera( oldRo, oldTa, oldCr );

然后,依靠t得到當(dāng)前點(diǎn)的世界坐標(biāo)wpos,在依據(jù)常規(guī)流程計(jì)算出該點(diǎn)在舊時(shí)間的屏幕空間坐標(biāo)

// camera space

vec3 cpos = vec3( dot( wpos - oldRo, oldCam[0] ),

? ? ? ? ? ? ? ? ? dot( wpos - oldRo, oldCam[1] ),

? ? ? ? ? ? ? ? ? dot( wpos - oldRo, oldCam[2] ) );

// ndc space

vec2 npos = oldFl * cpos.xy / cpos.z;

// screen space

vec2 spos = 0.5 + 0.5*npos*vec2(iResolution.y/iResolution.x,1.0);

最后,壓縮速度

// compress velocity vector in a single float

vec2 uv = fragCoord/iResolution.xy;

spos = clamp( 0.5 + 0.5*(spos - uv)/0.25, 0.0, 1.0 );

vel = floor(spos.x*255.0) + floor(spos.y*255.0)*256.0;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容