[程式] 超新手 shader language 教學文 (六)
[程式] 超新手 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 上例子很多。
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 61.228.80.32 (臺灣)
※ 文章網址: https://www.ptt.cc/bbs/GameDesign/M.1655472597.A.837.html
※ 編輯: meowyih (61.228.80.32 臺灣), 06/17/2022 21:31:02
※ 編輯: meowyih (61.228.80.32 臺灣), 06/17/2022 21:36:01
推
06/17 21:43,
2年前
, 1F
06/17 21:43, 1F
→
06/17 21:43,
2年前
, 2F
06/17 21:43, 2F
→
06/17 21:50,
2年前
, 3F
06/17 21:50, 3F
推
06/17 22:07,
2年前
, 4F
06/17 22:07, 4F
推
06/17 22:09,
2年前
, 5F
06/17 22:09, 5F
推
06/17 22:21,
2年前
, 6F
06/17 22:21, 6F
推
06/17 22:28,
2年前
, 7F
06/17 22:28, 7F
推
06/17 23:57,
2年前
, 8F
06/17 23:57, 8F
推
06/18 00:18,
2年前
, 9F
06/18 00:18, 9F
推
06/18 01:22,
2年前
, 10F
06/18 01:22, 10F
推
06/18 15:36,
2年前
, 11F
06/18 15:36, 11F
推
06/18 15:37,
2年前
, 12F
06/18 15:37, 12F
推
06/20 01:24,
2年前
, 13F
06/20 01:24, 13F
推
06/20 11:54,
2年前
, 14F
06/20 11:54, 14F
推
06/20 19:13,
2年前
, 15F
06/20 19:13, 15F
推
06/21 00:59,
2年前
, 16F
06/21 00:59, 16F
GameDesign 近期熱門文章
PTT遊戲區 即時熱門文章
40
145