[程式] 超新手 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