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

看板GameDesign (遊戲設計)作者 (meowyih)時間2年前 (2022/06/17 21:29), 2年前編輯推噓14(1402)
留言16則, 13人參與, 2年前最新討論串1/1
[程式] 超新手 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
別擔心啦XD
06/17 21:43, 1F

06/17 21:43, 2年前 , 2F
寫這種文就是為了遲早有一天真的需要的人能蒐尋得到
06/17 21:43, 2F

06/17 21:50, 2年前 , 3F
我也是2015開始潛水到兩年前才敢發言(?
06/17 21:50, 3F

06/17 22:07, 2年前 , 4F
我不懂,不過未看先推 XD
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
文章代碼(AID): #1Yh87LWt (GameDesign)
文章代碼(AID): #1Yh87LWt (GameDesign)