[程式] 超新手 shader language 教學文 (六)

作者: meowyih (meowyih)   2022-06-17 21:29:54
[程式] 超新手 shader language 教學文 (六)
這次要講的是簡單的 ray marching 概念。
為了這篇,我還花時間寫了下面這個 shader,
連我自己都覺得超有誠意的。
https://www.youtube.com/watch?v=65ubUM14VyY
可惜,這版人少到爆就算了,
來看文章的的好像都是些早就懂得人,
真讓人沮喪...
回到 ray marching。
對古早的 3D 繪圖來說,
因為受限運算速度,
大家只在乎 "光線碰到某個實體物體表面" 的位置,
換言之,那類運算的假設是光線是不會穿過物體。
但是要畫出像雲,霧,水,煙,火等等這些東西,
光線是會穿過去的,而且顏色是會疊加的,
拿畫一顆球或是立方體的方法畫那類物體,
很難做出真實感。
所以 ray marching 的技術就發展出來了。
[準備]
這次的 code 我已經寫好,放在:
https://www.shadertoy.com/view/7syyWG
前半部是別人 (iq大大) 的 3d noise,
float noise( vec3 x ) 是給個位置,回傳 0~1 的 noise。
float fbm( vec3 x ) 也是一樣,只是會傳回更複雜 (碎形) 的 noise。
用下面的 main func 可以畫出 fbm 出來看。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.y;
float n = fbm(vec3(uv,1.));
fragColor = vec4(vec3(n), 1.);
}
[建立3D世界模組]
有二個常用的觀點可以用來看 3d noise 的意義。
1. pos.xy 是 2d 世界 (x,y) 的一個點,而 pos.z 是時間軸。
2. pos.xyz 是存在在 3d 世界 (x,y,z) 的一個點。
我們這次用的是後者的觀點。
對後者來說,
世界上的每一個點都是一個 0~1 的數字,
如果把數字這當密度來看,
世界就是一團團不均勻的 "煙霧"。
下面的程式是在這團煙霧裡開一個隧道。
float world( vec3 pos )
{
return fbm(pos * 4.)
- clamp(0.8 - length(pos.xy), 0., 1.);
}
- pos*4: 讓 fbm 縮小 25%。
- 0.8 - length(pos.xy): 如果離中心點 (0,0) 的距離超過 0.8,
該值就是負的。
- clamp: 讓負數變 0。
當 pos 越靠近隧道中心,值就越小。
如果 pos 離中心超過 0.8 以後,值就不受影響,
這樣就像是在煙霧中開了一個隧道了。
[觀察者的位置,與每一個uv對應的光線向量]
請搭配程式看以下說明。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
觀察者位置其實就是攝影機的位置,
想像攝影機要取得任一個 pixel 的顏色,
就需要往那方向射出一道光線 (物理上是相反的就是了)
在 main 裡,觀察者是 ro,一開始在 (0,0,0)
但會隨時間往前行進 i.e. (0,0,-Time*2)
每個 pixel 的光線向量是 rd,
本來應該是 (uv.x, uv.y, 1.)
但我想讓畫面 (或說攝影機) 一直轉,
所以多了乘 rotateZ (對 Z 軸轉不停),
用 matrix 旋轉、縮放、扭曲、移動向量是數學,
看不懂請翻書。:>
接著我們射出一條光線,取得光線方向的噪音,
也就是:
float density = raymarch( ro, rd );
最後隨便上色,就完成了,簡單吧。:)
[ray marching]
接下來講光線射出去是要怎麼計算。
請搭配著程式看以下說明。
float raymarch(ro,rd)
因為光線會透過物體,
我們可以每隔一段距離,就取得一個值,
然後走了很多次後,把每個值加起來。
這就跟光一樣,
穿過濃一點的霧,顏色就顯現的明顯些。
穿過淡一點的霧,可以透過去的部分就多一些。
我們要做的是算出某段距離的霧的密度,
然後也就代表 pixel 的霧的總密度了。
回到 code,一開始密度 (density) 為 0,
光線預計採樣 20 次,
每次採樣點距離 0.1,
光線起點是 ro,
每走一步就是 ro + ra*0.1 (往光線方向前進0.1)
如果該點密度太低 (<0.01),
我們就當該點沒東西,讓畫面不要髒髒的。
因為每一點都是 0~1 之間,
隨便加一下就超過 1 了,
所以要 *0.2 讓他加慢一點。
加完回傳就是密度總值了。
[結語]
把 world 改成
return fbm(pos * 4.) - pos.y;
調一下 density 從 *0.2 變 *0.1
最後一行改成
fragColor = vec4(density) *
vec4(sin(uv.x), cos(uv.y),
0.1/length(uv), 1.) * 5.; // 1.0 變 0.1
就會變成在大霧中追著藍色頭燈的人 XD
如果要畫太陽或是火星,
也是把 world 改成某一點距離 r 以外密度都是 0 (圓球公式)
密度的遞減不能是常數,
要用 smoothstep,不然靠外面的密度會太低不好看,
有興趣就試試吧,shadertoy 上例子很多。
作者: dklassic (DK)   2022-06-17 21:43:00
別擔心啦XD寫這種文就是為了遲早有一天真的需要的人能蒐尋得到我也是2015開始潛水到兩年前才敢發言(?
作者: coolrobin (泳圈)   2022-06-17 22:07:00
我不懂,不過未看先推 XD
作者: kindamark (ㄇㄗ)   2022-06-17 22:09:00
作者: ctrlbreak   2022-06-17 22:21:00
你的影片可以偷偷置入按讚訂閱加分享按讚訂閱加.....暈
作者: chchwy (mat)   2022-06-17 22:28:00
不會啦 我就不懂啊 看到這系列很開心
作者: Lhmstu (lhmstu)   2022-06-17 23:57:00
不會阿,我就不懂這個
作者: iLeyaSin365 (伊雷雅鑫)   2022-06-18 00:18:00
我不懂 也看不懂 對
作者: LuMya   2022-06-18 01:22:00
感謝分享 挺有用的
作者: SecondRun (雨夜琴聲)   2022-06-18 15:36:00
不錯啊 雖然這個好像有人寫過了不過可以系統學習很棒喇
作者: louisalflame (louisalflame)   2022-06-20 01:24:00
潛水推
作者: LayerZ (無法如願)   2022-06-20 11:54:00
推推
作者: kyushu (蘇打綠嚇倒我了)   2022-06-20 19:13:00
我也不懂,感謝分享!
作者: wangm4a1 (水兵)   2022-06-21 00:59:00

Links booklink

Contact Us: admin [ a t ] ucptt.com