用280字符建模世界:探索代码、艺术与约束的微型着色器创作

本文深入探讨了如何用不超过280字符的GLSL代码创作出精美的实时着色器效果,涵盖了代码高尔夫技巧、着色器基础、创作动机与流程,并展示了多个微型着色器实例及其数学与艺术结合的实现方法。

用280字符建模世界

作为图形程序员,我的工作本质上是使用数学公式让像素变得更美观。我为游戏和软件中的动画背景处理光照、反射、后处理等视频效果。闲暇时,我喜欢编写能嵌入“推文”(280字符或更少)的紧凑着色器程序来放松身心。你可能在X/Twitter上见过一些这样的作品。将代码压缩同时保持功能的过程被称为“代码高尔夫”。

以下是我用仅197字符的GLSL代码编写的动画星系:

1
"Milky" #つぶやきGLSLfor(float i=-fract(t/.1),j;i++<1e2;o+=(cos((j=round(i+t/.1))*j+vec4(0,1,2,3))+1.)*exp(cos(j*j/.1)/.6)*min(1e3-i/.1+9.,i)/5e4/length((FC.xy-r*.5)/r.y+.05*cos(j*j/F4+vec2(0,5))*sqrt(i)));o=tanh(o*o);

这段代码实时为屏幕上的每个像素运行,通过一些复杂的数学和逻辑生成独特的输出颜色。我使用名为Twigl.app的在线着色器编辑器构建这些演示,该工具专为分享微型着色器设计,能轻松导出视频,并在“极客”模式下处理通用头代码并缩短内置变量名。

我甚至将体素DDA光线追踪与边缘检测压缩到仅190字符:

1
Down to 190 characters! #つぶやきGLSLvec3 p=vec3(t/.1,cos(t+r)),d=r.x/(r.xxy-round(FC.zxy)*2.),a;for(;o.w++<9e2&&snoise3D(a=ceil(p)/28.)+1.>length(a.yz);p+=min(a=fract(-p*sign(d))*abs(d)+1e-4,min(a.y,a.z)).x/d);o.rgb=fwidth(p);

动机

我编写这些微型着色器的原因有多方面:

  • 好奇与热情:新想法涌现时,我喜欢在Twigl上涂鸦,降低期望值,避免过度规划。
  • 学习与发现:约束条件下优化代码大小迫使我以不同方式思考问题,常找到简化或近似方法,并学会充分利用每个字节。
  • 挑战:编写微小代码既刺激又具挑战性,保持大脑敏锐,意外学到大量数学知识。
  • 社区:通过分享作品,我结识了艺术家、设计师、数学爱好者、游戏开发者等众多有趣人群。

简言之,这有趣、发人深省,是激发图形编程兴趣的好方法。

着色器简介

着色器是在GPU(图形处理单元)而非CPU(中央处理单元)上运行的程序。CPU擅长复杂或分支操作,按顺序计算;GPU设计用于并行处理每秒数十亿或数万亿的可预测操作。4K屏幕以60帧/秒输出近5亿像素/秒,每个像素可能涉及数百或数千次操作。

着色器有多种类型:顶点着色器、片段着色器、计算着色器等。这些推文着色器特指片段着色器(又称“像素着色器”),它们在每个像素上运行。片段着色器输入片段坐标(FC),输出颜色和透明度(RGBA)。输出颜色“o”有4个分量(红、绿、蓝、alpha),范围0.0到1.0。例如,(1.0, 1.0, 1.0, 1.0)为纯白,(0.0, 0.0, 0.0, 1.0)为不透明黑。

核心输入包括:

  • “o”:输出颜色
  • “FC”:片段坐标
  • 统一输入(所有像素共享):
    • “r”:屏幕分辨率(像素)
    • “t”:时间(秒)
    • “m”:鼠标位置(较少使用)
    • “b”:后缓冲纹理

从此处开始,通过大量数学和逻辑控制输出颜色,生成酷炫图像。

我的流程

人们常问我是否从一开始就以紧凑形式编写着色器,还是先扩展后压缩。答案是前者。通过大量代码高尔夫练习,我更容易以紧凑形式原型化想法,且不会在微小着色器中迷失。代码高尔夫着色器需在代码大小、渲染性能、艺术吸引力、设计和数学函数间找到平衡,这挑战了我的左右脑。

开始时需有一个想法。例如编写“Milky”恒星着色器时,我知道想创建某种星系,这是初始火花。我的着色器通常从居中和缩放开始,以适应不同分辨率和宽高比。对于恒星,我循环100个绕中心旋转的点光源。发光效果很容易创建,只需知道当前像素到光源的距离,并使用倒数作为像素亮度(近像素更亮,远像素更暗)。

我使用三角函数调整粒子位置,并给圆盘轻微倾斜。着色时,我喜欢对RGB通道使用带相移的正弦波。正弦波也可用于选择伪随机数,从而为每个恒星选择颜色。最终,我使用了左侧第二个调色板的轻微变体,具有不错的温度和亮度范围。我还添加了恒星亮度变化,使图像更有趣。

最后,我应用双曲正切函数进行色调映射,防止颜色通道达到最大亮度值时的过度曝光和色调偏移。任何具有高动态范围光照的着色器都应应用色调映射,推文着色器也不例外!我还添加了动画,最终选择了收缩效果,并创建循环使新恒星在旧恒星到达中心时淡入。

代码高尔夫

在压缩代码的过程中,我开发了数百种小技巧:

  • 缩减名称:习惯单字母变量和函数名,迫使重读代码并找到更好写法。
  • 缩减数字:例如1.0 == 1.1000.0 == 1e3。向量构造函数中可使用任何数据类型作为输入(如vec4(1))。
  • 最小化初始化:共享数据类型,避免float/int转换。
  • 避免if语句:使用三元运算符替代,更短且功能强大。
  • for循环优于while:for循环有初始化和小步进槽位,可免去分号。避免使用break,改用条件位。

此外,我还使用函数替换进一步减少代码。每个着色器都不同,需使用不同技巧、近似和概念,这正是乐趣所在!

问答

  • 最喜欢的技巧? 目前是“湍流”,可用于魔法效果、云或火。
  • 如何培养数学直觉? 需要时间和耐心,小块学习、休息和睡眠有帮助。
  • 编写方式? 以代码高尔夫模式编写,因直觉已发展,原型制作更快。喜欢Twigl.app和ShaderToy。
  • 如何开始? 始于对游戏开发的兴趣,着色器在游戏图形中有大量应用。
  • 牺牲可读性后悔吗? 不,更关心导致代码变慢的大小优化,不介意不可读代码。

我的故事

从小对视频游戏感兴趣,尤其是“花哨”的3D图形。10岁左右朋友展示了GameMaker工具,学习了拖放编程、变量和条件基础。后来在GM中试验3D图形,尽管它主要是2D游戏引擎。这足以学习3D渲染和渲染管道基础。GameMaker引入“着色器”后,我开始在论坛上发布作品并获得反馈(感谢“xygthop3”的有用示例!)。

游戏开发是学习着色器的好地方,因有性能约束(不想游戏卡顿),并在上下文中学习整个渲染过程。2014年开始发布最早着色器教程,2015年探索ShaderToy后技能真正发展。2021年为GameMaker推出GLSL 1.00入门教程系列。现在发布更通用的图形教程,涵盖数学、艺术、设计、代码等。

感谢帮助过我的人:xygthop3(免费示例极大帮助)、Inigo Quilez(ShaderToy作者、射线行进之王)、Fabrice Neyret(最佳着色器代码高尔夫选手)、Yonatan “zozuar”(激励我尝试代码高尔夫)、Yohei Nishitsuji(微型分形传奇)。

武器库

以下是一些最喜爱的推文着色器:

  • “Quantum”:量子效果
  • 未命名分形着色器
  • “Origami”:折纸效果
  • 195字符抽象效果
  • “Vortex”:漩涡效果

更多作品可见个人网站、X、Bluesky或Instagram。学习着色器可尝试我的教程,定制工作可联系我。

感谢阅读!祝愉快!

  • Xor
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计