用280字符建模世界
一位图形程序员对创作微型、富有表现力的着色器的心态、方法和动机的探索,这些着色器结合了代码、艺术和约束。
引言
我是Xor。作为一名图形程序员,我的工作本质上是使用数学公式让像素变得更漂亮。我为游戏和软件中的动画背景处理视频效果,如光照、反射和后处理等。
为了娱乐,我喜欢通过编写能放入“推文”(280字符或更少)的紧凑小着色器程序来放松。你可能在X/Twitter上看到过一些这样的作品。在保持功能的同时缩小代码的过程被称为“代码高尔夫”。
以下是我用仅197个字符的GLSL代码编写的动画星系:
|
|
这个小代码片段对屏幕上的每个像素实时运行,并使用一些花哨的数学和逻辑生成独特的输出颜色。我使用一个名为Twigl.app的工具构建这些演示,这是一个专为分享迷你着色器而设计的在线着色器编辑器。它使导出视频变得超级容易,在其“极客”模式下,它还处理通用头代码并缩短内置变量名。
我甚至成功地将一个体素DDA光线追踪器与边缘检测压缩到仅190个字符:
|
|
今天,我想解释为什么我制作这些,分享我的创作过程,并展示如果你有兴趣如何自己尝试。让我们从“为什么”开始。
动机
我为什么写这些?嗯,有几个因素。既然我喜欢列表,我就按相关性顺序呈现它们:
- 好奇心和热情:有时我会被一个新想法击中,只想玩玩。我喜欢Twigl,因为它有助于降低我的期望,让我开始涂鸦。过度规划的空间更少,而且超级容易上手。
- 学习和发现:在约束下工作迫使我以不同的方式思考问题。通过优化代码大小,我经常找到简化或近似的方法。它并不总是导致更高性能的代码(但经常如此),我学会了如何榨取每个字节的最大价值。拥有非常少的代码使得实验公式和变体变得更容易,而不会感到不知所措。
- 挑战:编写微小代码既具有挑战性又刺激。它让我的大脑保持敏锐,我不断开发新技能。这基本上成了我的游戏。在尝试解决这些技术问题时,我意外地学到了大量数学知识。
- 社区:通过这个过程,我联系了许多有趣的人——艺术家、设计师、数学爱好者、游戏开发者、工程师、技术爱好者等等。分享我的作品导致了一些令人兴奋的相遇。(稍后更多关于一些著名人物!)
所以,简而言之,它有趣、发人深省、引人入胜,并且是激发对图形编程兴趣的好方法。现在,什么是着色器?
着色器介绍
如果你还没听说过着色器,它们是在GPU(图形处理单元)而不是CPU(中央处理单元)上运行的程序。CPU擅长复杂或分支操作,这些操作按顺序一次计算一个(我在这里简化了)。GPU设计用于每秒并行处理数十亿或数万亿的可预测操作。这听起来很多,但一个4K屏幕以60帧每秒输出近5亿像素每秒。每个像素可能有100或1000次操作,更不用说GPU可能用于的其他任何事情。
有几种不同类型的着色器:顶点着色器、片段着色器、计算着色器等,但这些推文着色器 specifically是片段着色器,也称为“像素着色器”,因为它们运行在每个像素上。本质上,片段着色器接受输入片段坐标并输出颜色和不透明度(或alpha)。片段坐标给你屏幕上每个像素中心的位置,所以(0.5, 0.5)是左下(或左上)。向右一个像素是(1.5, 0.5),依此类推到(宽度 – 0.5, 高度 – 0.5)。坐标变量在Twigl中称为“FC”。输出颜色“o”有4个RGBA分量:红、绿、蓝和alpha,每个范围从0.0到1.0。
(1.0, 1.0, 1.0, 1.0)
是纯白色,(0.0, 0.0, 0.0, 1.0)
是不透明黑色,(1.0, 0.0, 0.0, 1.0)
是RGBA颜色格式中的纯红色。从这里,你已经可以制作简单的颜色渐变:
|
|
记住,这在每个像素上运行,所以每个像素都有一个唯一的片段坐标。该公式制作一个简单的渐变,在屏幕底部(FC.y = 0.0)开始为黑色,当FC.y达到100.0时,绿色输出值达到1.0。
所以你有一个输出颜色“o”,输入片段坐标“FC”,和四个“uniform”输入,它们在所有像素之间共享:“r”是着色器屏幕分辨率(像素),“t”是时间(秒),还有较少使用的鼠标位置“m”和后缓冲纹理“b”。这就是核心!从那里,是大量的数学和逻辑来控制输出颜色并生成酷炫的图像。
我要跳过一点,但如果你有兴趣了解更多,尝试从这里开始!
我的过程
人们经常问我是否从一开始就以紧凑形式编写着色器,或者是否先编写扩展版本然后减少代码。答案是前者。我练习代码高尔夫这么多,以至于我发现以紧凑形式原型化想法更容易,而且我倾向于不在微小着色器中迷失。代码高尔夫着色器需要在代码大小、渲染性能、艺术吸引力、设计和数学函数之间找到正确的平衡。这是一个微妙的平衡,绝对挑战我大脑的两侧。通过编写这些,我学到了大量关于数学、艺术和设计的知识!
开始一个,你需要一个想法。当编写“Milky”星星着色器时,我知道我想创建某种星系,所以那是我最初的灵感。我的着色器通常从居中和缩放开始,以便它们在各种分辨率和纵横比下看起来都好。对于星星,我循环了100个点光源围绕中心旋转。我喜欢发光效果,而且它们很容易创建。你只需要知道当前像素到光源的距离,并使用逆距离作为像素亮度(近像素更亮,远像素更暗)。
我使用一些三角函数玩弄粒子的位置,并给圆盘一个轻微的倾斜。对于着色,我喜欢使用一些带有相位偏移的正弦波用于RGB通道。正弦波也用于选择伪随机数,所以我这样选择每个星星的颜色。使用正弦公式,你可以得到这样的调色板:
我最终使用了左边第二个的轻微变体。它有一个很好的温度和亮度范围。我还添加了一些星星亮度的变化,这使得图像看起来更有趣。接下来,我应用了一些色调映射与双曲正切函数以控制大小。色调映射防止当颜色通道达到其最大亮度值时发生的 harsh 过度曝光和色调偏移(左边是原始,右边是带色调映射):
任何具有高动态范围光照的好着色器都应该应用一些色调映射,推文着色器也不例外!最后,我玩弄了动画。它本可以旋转或扭曲,但最终,我最喜欢收缩效果。我还创建了一个循环,以便当旧星星到达中心时新星星淡入。你可以在这里更详细地阅读我的设计过程!
代码高尔夫
正如你可以想象的,在缩小代码的过程中,我开发了(并继续发现)数百个小技巧,但我可以给你简略版本!我的通用代码高尔夫过程可以如下列出:
- 减少名称:最初可能具有挑战性,但你可以习惯单字母变量和函数名。你有时可能忘记变量的用途,但这实际上有助于代码高尔夫。它迫使你重读代码,你经常会在这样做时找到更好的编写方式。像其他任何事情一样,你的记忆会随着练习改善,随着时间的推移,你会建立一些标准(对我来说:p = 位置,c = 颜色,O = 片段输出,I = 输入,等等)。
- 减少数字:这很自解释。
1.0 == 1.
,1000.0 == 1e3
。不要忘记,使用向量构造函数,你可以使用任何数据类型作为输入,并且它被转换(“转换”)为新类型:vec4(1.0, 1.0, 1.0, 1.0) == vec4(1)
。如果你乘以10.0
,你可以改为除以.1
。 - 最小化初始化:如果你有两个浮点数“x”和“y”,尝试像这样一起初始化它们:
float x = 0., y = 1.;
寻找共享数据类型的机会。如果你有一个颜色vec3和一个vec4,使它们都是vec4。避免float/int转换。 - 避免if:GLSL中的if语句占用一些空间,特别是如果你需要
else if
。尝试使用三元运算符代替。例如:if (x>y) O = vec4(1,0,0,1); else O = vec4(0,1,0,1);
变成O = x>y ? vec4(1,0,0,1) : vec4(0,1,0,1);
。短得多,而且你可以用它做很多事情。你甚至可以在?
和:
之间设置多个变量。 - for(;;) > while():
for
和while
使用相同数量的字符,但for
有一个用于初始化(在第一个分号之前)和一个用于每次迭代后的最终步骤(在最后一个分号之后)的位置。这些是免费槽,可以用于否则必须以分号结束的行。另外,避免使用break
,并使用条件点代替!你也可以移除括号,如果每行以逗号结束(所以它不适用于嵌套for
循环)。
除此之外,我使用一些函数替换来进一步减少代码。更多关于这里!
我整理了一个ShaderToy演示,带有一些额外的变量、格式化和注释以清晰。每个着色器都不同,需要使用不同的技术、近似和概念,但这正是让它对我如此有趣的原因!我几乎每天都在学习新东西!
问答
以下是我在X上被问到的一些问题。
你有最喜欢的“技巧”或“技术”吗?如果有,是什么?
我经历阶段。我喜欢Bokeh DoF,然后是体积阴影和分形,但目前,我的最爱必须是“湍流”。它可以用于一些 awesome 魔法效果、云或火。
你是如何发展相关数学的直觉的?
这需要很多时间和耐心。我不得不 push through 许多次当我认为一个主题超出我的理解时。如果你以小片段学习,休息,并睡在上面,你可以学到很多!我写了一些我多年来 pick up 的概念化技术。那可能节省你一些时间!
你开始编写着色器时是代码高尔夫模式,还是一个过程直到你达到最优化代码?哪个是正常着色器和代码高尔夫着色器的最佳编辑器?
是的,我以代码高尔夫模式编写,因为我已经 developed 了一种直觉,而且在这点上原型化感觉更快。当我找到一个我喜欢的外观时,我仍然需要精炼代码。我是Twigl.app的 big fan,但ShaderToy也很棒。ShaderToy最适合其社区和知识财富。我尝试在解释我的推文着色器时使用它。
你是如何开始编写酷炫着色器的,你用什么学习它?
嗯,我稍后会解释更多关于我的背景,但它始于对游戏开发的兴趣。着色器在视频游戏图形中有大量应用——那激发了我学习的好奇心。
你对牺牲可读性有遗憾吗?
没有。我更关心导致较慢代码的大小优化,但我不介意不可读的代码。对我来说,那是它的魔力的一部分。
你的背景是什么,让你能够有效地学习材料?
是故事时间…
我的故事
成长过程中,我对视频游戏感兴趣,尤其是那些带有“花哨”3D图形的游戏。当我大约10岁时,我的朋友向我展示了一个名为GameMaker的工具。我摆弄它并学习了一些拖放编程、变量和条件的基础知识。
随着时间的推移,我开始在GM中实验3D图形,即使它(现在仍然是) primarily 一个2D游戏引擎。它足以学习3D渲染如何工作和渲染管道的基础知识。后来,GameMaker引入了这个称为“着色器”的东西,允许开发者创建更高级的效果。当时,没有太多资源可用,所以我花了一些时间才 pick it up。我开始在GameMaker论坛上发布我的着色器,并从社区得到了一些有用的反馈(向“xygthop3”喊叫他的有用例子)!
游戏开发是学习着色器的好地方,因为你有性能约束(你不希望游戏 stutter),而且你在那个上下文中学习大量关于整个渲染过程的知识。2014年,我开始发布我最早的着色器教程,分享我学习的技术。早期的教程不是很好,但我很高兴我写了它们。2015年,我开始探索ShaderToy,那是我技能真正发展的地方。
有太多伟大的例子可以学习,而且是一个获得反馈的好地方。2021年,我为GameMaker与GLSL 1.00启动了一个新的入门教程系列。现在我发布更通用的教程,涵盖各种图形主题,从数学到艺术到设计到代码等等。这绝对是我最好的系列 yet,它们继续变得更好。如果你对视频游戏和图形感兴趣,我高度推荐从GameMaker或Godot开始。它们相对容易学习,同时仍然足够强大教你 ropes。如果软件或Web开发更是你的菜,你不能错 with ShaderToy或compute.toys。
以下是一些帮助过我的人,直接或间接, along the way:
- xygthop3 – 这个家伙的免费着色器例子可能是 along the way 最大的帮助。他的例子是我理解各种图形技术的关键点,所以谢谢,Michael!
- Inigo Quilez – Inigo是ShaderToy的作者和光线步进的国王。他的 Signed Distance Field 函数至今仍然是基础。一个绝对的传奇!
- Fabrice Neyret – Fabrice可能是最好的着色器代码高尔夫球手,许多着色器受他的工作启发。他多年来教了我这么多技术。
- Yonatan “zozuar” – 另一个对我的主要灵感。Yonatan的工作说服我尝试在Twitter上真正代码高尔夫,他的大脑惊人。
- Yohei Nishitsuji – 这个家伙在微小分形方面是传奇。爱他的工作。Yohei还在Codrops上写了Rendering the Simulation Theory: Exploring Fractals, GLSL, and the Nature of Reality。
我肯定还有许多人 whose names are eluding me at the moment,但我想感谢整个着色器社区的反馈和鼓励。
武器库
我将以一些我最喜欢的推文着色器结束:
|
|
|
|
|
|
|
|
|
|