学习一个马斯克转发的Grok效果(glsl)
写在开头
最近在学习glsl,在网上找相关开源代码效果学习时看到了这个被「马斯克」转发,并且只用四行就完成的效果!自我学习之后分享出来,因为本人也是一个glsl新手,有错误的地方欢迎大家斧正! 本篇文章只会涉及到「片段着色器」,下面代码如果不特殊提醒均为「片段着色器」
效果原作者 Xor
效果欣赏!🔑
入门glsl
首先我们应该了解到之前我们使用的canvas 2D绘制和glsl绘制的一些差异性:
Canvas 2D 的绘制方式:
类似传统绘画,按顺序一笔一笔画 从左上角 (0,0) 开始,y 轴向下 命令式编程:moveTo(), lineTo(), fill() 时间顺序执行,后画的覆盖先画的
GLSL 的绘制方式:
每个像素点同时计算,高度并行 坐标系通常以中心为 (0,0),y 轴向上 函数式编程:给定坐标,返回颜色 所有像素 "瞬间" 同时渲染
这是学习glsl初期相当让人不适应的一点,glsl非常类似中国古代的活字印刷[1]技术,所有像素点同时渲染,几何形状需要依靠数学计算来排列组合!下面举得例子中都要谨记这个概念「瞬间 / 同时渲染」
void main() {gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}
gl_FragColor 是 GLSL 片元着色器 (Fragment Shader) 中的一个内置输出变量,它决定了当前像素最终显示的颜色。
gl_FragColor 的核心概念
本质:它是每个像素的 "最终答案" - 告诉 GPU 这个像素应该显示什么颜色。
vec4 的四个分量含义
gl_FragColor = vec4(r, g, b, a);// ↑ ↑ ↑ ↑// 红 绿 蓝 透明度
r (Red):红色分量,范围 0.0-1.0 g (Green):绿色分量,范围 0.0-1.0 b (Blue):蓝色分量,范围 0.0-1.0 a (Alpha):透明度,0.0 完全透明,1.0 完全不透明
void main() {gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}
因为 r=1.0 所以我们这个代码的效果是一个红色的填充。
工作原理
GPU 为屏幕上的每个像素都调用一次这个片元着色器 每次调用时,main() 函数执行 gl_FragColor 被设置为红色 所有像素都得到相同的红色值 最终屏幕显示为一片红色
就像每个像素都调用了 main 方法得到的命令是都给我渲染成_红色_,结果就是整个画布展示为红色。
更多例子
void main() {vec2 pos = gl_FragCoord.xy / u_resolution.xy;gl_FragColor = vec4(pos.x, 0.0, 0.0, 1.0);}
gl_FragCoord - 像素的绝对坐标
gl_FragCoord 是 GLSL 的内置变量,表示当前像素在屏幕上的绝对位置(以像素为单位)。
// 假设画布是 800x600 像素,不考虑采样精度// gl_FragCoord.xy 的取值范围是:// x: 0 到 800// y: 0 到 600
u_resolution - 画布的总尺寸
u_resolution 是一个变量,表示画布的宽高。
归一化坐标的计算
vec2 pos = gl_FragCoord.xy / u_resolution.xy;这行代码将绝对像素坐标转换为归一化坐标(0.0 到 1.0):
举例:画布800x600,当前像素在(400, 300)pos.x = 400 / 800 = 0.5 (画布中央)pos.y = 300 / 600 = 0.5 (画布中央)
渐变效果的实现
gl_FragColor = vec4(pos.x, 0.0, 0.0, 1.0);// ↑// 红色分量由x坐标决定
最左边:pos.x = 0.0 → 黑色 (0, 0, 0, 1) 最右边:pos.x = 1.0 → 纯红 (1, 0, 0, 1) 中间:pos.x = 0.5 → 半红 (0.5, 0, 0, 1)
这个效果是纵向渐变,我们举一反三实现一个垂直渐变
void main() {vec2 pos = gl_FragCoord.xy / u_resolution.xy;gl_FragColor = vec4(pos.y, 0.0, 0.0, 1.0);}
从这两个渐变效果我们就能了解到glsl坐标系是「从左到右」 / 「从下到上」
0,1 - - - - - 1,1| || |0,0 - - - - - 1,0
径向渐变
线性渐变我们已经掌握了接下来我们来挑战一下径向渐变!
void main() {vec2 pos = gl_FragCoord.xy / u_resolution.xy;pos = pos * 2.0 - 1.0;float dist = length(pos);gl_FragColor = vec4(dist, dist, dist, 1.0);}
pos = pos * 2.0 - 1.0;在于把中心点变换到[0,0],vec2 pos = gl_FragCoord.xy / u_resolution.xy; 是归一化坐标也就是坐标范围此时为[0,1]此时坐标中心点是[0.5,0.5], 经过 pos * 2.0 - 1.0;之后我们的坐标范围变成了[-1,1],此时的坐标中心点就是[0,0]啦。
length() 是 GLSL 中的内置数学函数,用来计算向量的欧几里得长度[2], 也就是我们熟知的勾股定理,对于对于 2D 向量vec2(x, y)来说!
length(vec2(x, y)) = sqrt(x*x + y*y)这就是我们为什么需要把作用中心点转换到[0,0]的原因!我们的dist的值靠近中心点就约等于 0,越靠近外层就约等于 1
发光的圆环
voidmain() {vec2 pos = gl_FragCoord.xy / u_resolution.xy;pos = pos * 2.0 - 1.0;float dist = length(pos);// 计算与圆环的距离float ring = abs(dist - 0.5);// 距离越近越亮float brightness = 0.1 / ring;gl_FragColor = vec4(brightness, brightness, brightness, 1.0);}
OK Fine,现在我们提升一点难度,开始我最不喜欢的数学!首先是dist可以产生一个径向渐变中心是黑色往外扩散白色,但是我们的目的是一个圆环,也就是中心要是 黑色->白色->黑色,通过float ring = abs(dist - 0.5)就可以产生一个明显的分层效果!
假设:dist = 0; ring = 0.5;dist = 0.5; ring = 0;dist = 1; ring = 0.5;
float brightness = 0.1 / ring;的作用就是提亮!
❝数学规律:当分母越接近 0,结果越接近无穷大! 这就是为什么圆环会发光 - 在半径 0.5 的位置,分母最小,亮度最大!
❞
完成 Grok 效果
看完上述几个例子后,我们已经可以完成一个发亮的圆环,现在距离最终的效果只差一个对角线!
🎯 对角线的数学定义
对角线是所有满足 x = y 的点
在这些点上:p.x - p.y = x - x = 0
数值分布
左上角:p.x < p.y,所以 p.x - p.y < 0(负值)
右下角:p.x > p.y,所以 p.x - p.y > 0(正值)
对角线:p.x = p.y,所以 p.x - p.y = 0(零值)
除法放大
0.01 / (p.x - p.y)
当 p.x - p.y 接近 0 时(对角线附近)
除法结果趋向无穷大 → 产生极亮的线
我们加上这个对角线后为了让对角线在圆环中不发光需要反转亮度,想想我们之前的数学规律当分母越接近0,结果越接近无穷大! , 反过来如果分母越大结果自然也就越小!!
结语
glsl实在是太有意思了!同时也太难啦!!!
参考资料
Xor:www.shadertoy.com/user/Xor
The Book of Shaders:thebookofshaders.com/
AI编程资讯AI Coding专区指南:https://aicoding.juejin.cn/aicoding