[程式] 超新手 shader language 教学文 (八)

楼主: meowyih (meowyih)   2022-06-20 12:50:32
这篇是 Ray Marching 相关系列的最后一篇,
接着看缘份吧,因为越写越不‘超’新手了。
今天我为这篇文章写的 shader 在这:
https://youtu.be/P4wCIKMwYlE
没错,有颜色了,这次的主题就是光,
与如何让物件叠加的方法。
(画面里面有‘四个’球)
其实要我写‘光’的教学文,我感觉超心虚的。
我微积分学的不好,很多东西我只会抄,
一堆相关公式文章wiki根本看不懂在说三只小猫的。
别说我自己写了,
我还希望有哪个大师写给我看勒。Orz
所以... 这篇真的是新手文囉。
[基本知识]
这篇‘不会’说到像是用 Game Engine 或是 Blender,
先做几个多边形,然后摆个光源调个强度,
再改一改材质就做会出来的效果。
这篇是新手文,不是大学电脑绘图课。:>
这篇会讲的是体积光 (Volumetric Light)。
体积光是什么?体积光也有人叫他 God Light,长这样:
https://i.ebayimg.com/images/g/mggAAOSwShpZc31P/s-l500.jpg
早期的电脑绘图,光影效果只出现在实际存在的钢体上。
像是实心的球表面有光暗反射散射等等的效果,
影子也只会出现在实体的地面上,
那些也就是我上面说的大学电脑绘图课会教的东西。
但体积光,就像上面那个天使要降临前的光束,
是在空无一物的空气中出现的。
这种东西就像让光有体积,或是把光当作物体一样看待,
所以就叫体积光。
我们之前二个 shader,
不都有假定世界任何一个点都有密度吗?
只要有密度,就能被光照到,
而且密度不是 1,光就能透过去,
并照到下一个点 (ray marching)。
这概念其实就是体积光。
再换个说法,现实中看的到体积光,是因为空气中有粉尘,
粉尘多寡就代表了该点为中心的某个范围的密度,
所以是很符合物理现实的。
除了体积光,还需要知道一些光的基本常识:
1. 反射 (Reflection)
就国中物理的入射光反射光那些东西,
这篇 shader 没用到,再用 GPU 都要烧起来了。
2 吸收 (Absortion)
光射到一个物体,某些波长会被吸收变成热量之类的,
没被吸收然后反射的部分就是颜色。
3. 透射 (Transmittance)
光可以穿过透明物体,不过会有耗损 (被反射或被吸收了)
所以,Reflection + Absortion + Transmittance = 1.0
透射就是等等程式里的密度,
程式中的光的颜色则是代表了吸收的概念。
大家国中物理一定都学得很好,
所以我们开始看程式吧!(笑)
[程式]
程式在这:
https://www.shadertoy.com/view/fdycD3
基本逻辑都是继承前二篇的,所以看不懂可以先翻前面的看。
[世界构成]
func 跟之前长的一样
void world( vec3 pos, out distance, out density )
这次世界有四个球,ball_1 ~ ball_4。
虽然前一个范例也有 sky/river 二个物体,
但二者没有任何交集,颜色只是加起来而已。
这是世界的四个球是有重叠的,
这种情况,密度和最短距离要用正确的方法算。
最短距离:
先分别算 pos 对四颗球的最短距离,
然后在四个数字中取最小的。
min( min( min( dis1, dis2 ), dis3 ), dis4 );
密度:
二种算法,
一种是四个球的密度取最大值。
一种是把四个值加起来。
没有好坏,是叠加处的感觉不同。
我的感觉是透明度越低的,越适合最大值,反之是叠加值。
二种在程式中都有写,可以试试。
[用 ray marching 算颜色]
raymarch() 现在不是回传该方向的总密度,
而是直接回传该方向的总颜色了。
vec4 raymarch( in vec3 ro, in vec3 rd )
要怎么算某个方向的总颜色呢?
总颜色跟总密度一样,
是 raymarch 的路径上所有的采样点的颜色的总和,
密度高的点颜色占的比例高,密度低的比例低,
比例的高底是放在 color.a 的透明度里面。
计算方法跟总密度的算法几乎一样。
color.a *= 0.4; // 跟范例 (六) 把密度调低在加的意思一样
color.rgb *= color.a; // 照透明度调淡颜色
color_sum += color * (1.0 - color_sum.a);
那要怎么算出各别的采样点的颜色呢?
假设采样点位置是 pos,
首先,我们有个光源位置:
LIGHT_POS = vec3( -2.1, -2.6, 1.2 ),
接着我们可以算出采样点到光源的方向向量。
然后从采样点出发,
往光源方向 ray marching 前进某距离,
再算出 pos 到光源的该距离的总密度 t。
t 代表 Transmittance (透射率),
密度高的路线,光比较不容易射到 pos,
密度低的路线,光比较容易射到 pos,
若 t = 0,光线可以没有任何耗损直接射到 pos。
也就是说,
t 越高,光的影响越低,
t 越低,光的影响越高。
假设光的颜色是 light_color,
我们就依照透射率比例,加光的颜色加在原本该点的颜色上,
如此就是下面 code 的意思:
t = lightmarch( pos, LIGHT_POS );
vec3 light_color = vec3(1.0,0.6,0.3);
vec3 ambient_color = vec3(0.91,0.98,1.05);
color.rgb = color.rgb * (light_color*(1.0-t) + ambient_color);
那... ambient color 是什么?
如果有用 Blender 或任何的游戏引擎做过 3D 游戏都应该用过,
这是 "如果没有任何光源时,物体的基本自体发光"。
你可以把 ambient_color = vec3(0.0),
然后就会发现光照不到的地方都变全黑了。
在做 3D 游戏时,
有 ambient color 其实不太好,
因为不好控制光源效果。
比较好的做法是多放几个光源,
像 direction light 之类的,
但这只是 shader 范例,所以别在意这个了。
[最佳化]
我们一直都没讨论最佳化的问题,
但当你在做光影时,最佳化的问题是避免不了的。
像是我这个范例,在浏览器的 WebGL 跑,
若你拿几千块的手机或是平板,
fps 我猜就 10 上下吧。XD
那要怎么办呢?
首先,我们真的需要对画面上所有的方向做 ray marching 吗?
像前一个例子,做天空时,画面下半部的点可以通通不做 ray march,
做河流时,画面上半部也是一样,马上就可以减少一半的计算。
而以这个四球例子,一般要先算 ro/rd 是否有和任何物体相交,
有相交再做 ray marching。
什么?
怎么知道一条 ro/rd 的光线有没有和四颗球相交?
wiki 公式和证明:
https://bit.ly/39AgCMz
看不懂在写三只小猫?颗颗,写成 code 是这样:
// i remember its from Duke in shadertoy,
// but i can't find the origin post, sorry
bool inter(v3 ro,v3 rd,v3 pos,f radius,v3 pt)
{
v3 pos2ro = (ro - pos);
float b = dot(pos2ro, rd);
float c = dot(pos2ro, pos2ro) - radius*radius;
float d = b*b - c;
pt = ro + rd*(-b - sqrt(d));//第一个交点
return d >= 0.0;
}
不是我写的,给你参考。
还有,光源的 ray marching 采样
可是直接让计算次数以倍数成长的,
到底采样几次,才会达到你预期的效果?
还是只采样最远的那个采样点,
然后照比例算透射率就好?
然后,我们真的需要这么逼真的表面吗?
不用的话,减少 ray marching 采样次数,
然后调整 noise 参数找到可接受的就好,
像前一个大河的例子,
从 40 次降到 20 次其实也还行。
最后,如果这不是游戏的主要重点,
我们是不是该手动降低 fps,
把资源留给游戏前景呢?
作法只是不用每次都在 main 里叫 ray marching,
加个 timer 减少画面的更新。
[结语]
连写了三篇 ray marching,算是把基本的都讲完了。
靠这三篇的基础,也可以在游戏中做出很炫的东西了。
美丽的夜空、磅礡的大雨、
深海的景象、宇宙、爆炸、通通都做得出来。
但做得出来跟做的漂不漂亮是二回事就是了,
美感是很吃天份的 (我就没有),
请多看看其他神人的作品吧。
作者: kyushu (苏打绿吓倒我了)   2022-06-20 19:15:00
感谢分享,收藏ing
作者: NciscalA   2022-06-21 09:36:00
推推
作者: kindamark (ㄇㄗ)   2022-06-21 12:46:00
作者: eugenelinrmx   2022-06-21 22:59:00
推分享

Links booklink

Contact Us: admin [ a t ] ucptt.com