news 2026/5/15 10:21:55

Shadertoy实战:用SDF画2D图形,从圆形到胶囊体,手把手教你写GLSL代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Shadertoy实战:用SDF画2D图形,从圆形到胶囊体,手把手教你写GLSL代码

Shadertoy实战:从零构建2D图形SDF渲染器

开启GLSL图形编程之旅

在Shadertoy这个神奇的实时着色器平台上,每个像素都能成为你创造力的画布。想象一下,只需几行代码就能让简单的数学公式转化为屏幕上跃动的视觉艺术——这正是符号距离函数(SDF)的魅力所在。对于刚接触GLSL的开发者来说,SDF就像图形编程的"乐高积木",通过基础形状的组合与变换,可以构建出令人惊叹的复杂效果。

不同于传统图形API需要处理顶点缓冲区和渲染管线,SDF让我们用纯数学函数定义图形。这种声明式的编程方式特别适合Shadertoy的即时反馈环境:每次代码修改都能在0.5秒内看到渲染更新。我们将从最基础的圆形开始,逐步构建线段、矩形等基本图形,最终组合出胶囊体等复杂形状。整个过程就像搭积木一样直观有趣,而你需要准备的只是一款现代浏览器和对图形学的热情。

1. 搭建Shadertoy开发环境

在开始编写SDF代码前,我们需要配置好Shadertoy的工作环境。访问Shadertoy官网并点击"New"按钮创建一个空白项目,你会看到默认生成的代码模板:

void mainImage(out vec4 fragColor, in vec2 fragCoord) { // 规范化像素坐标到[0,1]范围 vec2 uv = fragCoord/iResolution.xy; // 输出纯色作为初始测试 fragColor = vec4(uv, 0.5, 1.0); }

这个简单示例展示了Shadertoy的核心工作机制:mainImage函数会为屏幕上的每个像素(通过fragCoord传入)计算颜色值。我们的第一个任务是建立适合SDF工作的坐标系系统:

void mainImage(out vec4 fragColor, in vec2 fragCoord) { // 将坐标中心移到屏幕中央,y轴向上,范围[-1,1] vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; // 初始化距离值为最大值 float d = 1e5; // 这里将放置SDF计算代码 // 根据距离值渲染(白色表示图形内部,黑色外部) fragColor = vec4(vec3(1.0-smoothstep(0.0,0.02,d)),1.0); }

这段代码建立了几个重要约定:

  1. 坐标系原点在屏幕中心
  2. y轴向上,x轴向右
  3. 垂直方向范围固定为[-1,1],水平方向按屏幕比例缩放
  4. 使用smoothstep实现边缘抗锯齿

提示:在Shadertoy中按Alt+Enter可以全屏预览效果,Ctrl+Enter强制重新编译着色器。

2. 圆形SDF:图形学的"Hello World"

圆形是最简单的2D图形,它的SDF也直观体现了距离函数的本质:

float sdCircle(vec2 p, float r) { return length(p) - r; }

这个简洁的函数完成了三件事:

  1. 计算点p到原点的距离length(p)
  2. 减去半径r得到符号距离
  3. 正值表示点在圆外,负值表示圆内,零值恰好在边界

让我们在Shadertoy中实现并可视化这个圆形:

void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; float d = sdCircle(p, 0.5); // 彩色可视化:内部红色,外部渐变蓝 vec3 col = mix(vec3(0.8,0.2,0.2), vec3(0.2,0.4,0.8), smoothstep(-0.1,0.1,d)); // 添加白色边界线 col = mix(col, vec3(1.0), 1.0-smoothstep(0.0,0.01,abs(d))); fragColor = vec4(col,1.0); }

通过这个例子,我们可以观察到SDF的几个关键特性:

  • 等距线:修改代码将abs(d)直接作为颜色输出,会看到一系列同心圆环
  • 平滑过渡smoothstep函数创造了边缘的渐变效果
  • 灵活着色:距离值d可以作为各种视觉效果的控制参数

尝试交互式修改半径值,实时观察圆形变化:

// 添加时间动态效果 float r = 0.3 + 0.2*sin(iTime); float d = sdCircle(p, r);

3. 线段SDF:构建图形的基础模块

线段是构建复杂图形的基础元素,其SDF实现展示了向量投影的巧妙应用:

float sdSegment(vec2 p, vec2 a, vec2 b) { vec2 pa = p-a, ba = b-a; float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0); return length(pa - ba*h); }

这个函数的几何原理是:

  1. 计算点p到线段端点a的向量pa
  2. 计算线段方向向量ba
  3. 通过点积求出pa在ba上的投影比例h
  4. 用clamp限制h在[0,1]范围内
  5. 最终距离就是p点到投影点的长度

在Shadertoy中可视化线段:

// 定义线段端点 vec2 a = vec2(-0.5, -0.2); vec2 b = vec2(0.5, 0.3); float d = sdSegment(p, a, b); // 可视化线段和端点 vec3 col = vec3(1.0-smoothstep(0.0,0.01,abs(d))); col = mix(col, vec3(1,0,0), 1.0-smoothstep(0.0,0.02,length(p-a))); col = mix(col, vec3(0,1,0), 1.0-smoothstep(0.0,0.02,length(p-b)));

线段SDF的一个神奇特性是:当我们将距离减去一个常数,就能得到胶囊体效果:

// 胶囊体效果 float capsule = sdSegment(p,a,b) - 0.1;

4. 矩形SDF:对称性与区域划分

轴对称矩形的SDF展示了如何利用对称性和区域划分优化计算:

float sdBox(vec2 p, vec2 b) { vec2 d = abs(p)-b; return length(max(d,0.0)) + min(max(d.x,d.y),0.0); }

这个看似简洁的函数实际上处理了三种情况:

  1. 点在矩形外部右侧/上方:max(d,0.0)选择正分量
  2. 点在矩形外部但靠近角:length(max(d,0.0))计算到角的距离
  3. 点在矩形内部:min(max(d.x,d.y),0.0)处理内部符号

在Shadertoy中实现交互式矩形:

// 动态变化的矩形尺寸 vec2 size = vec2(0.4, 0.6) * (1.0 + 0.2*cos(iTime)); float d = sdBox(p, size); // 添加圆角效果 float rounded = d - 0.05;

矩形SDF的扩展性很强,我们可以轻松实现:

  • 圆角矩形:SDF结果减去圆角半径
  • 边框效果:使用abs(d)-thickness
  • 挖空效果:组合多个矩形SDF

5. 胶囊体SDF:组合艺术的典范

胶囊体可以看作线段加圆的组合,其SDF完美展示了SDF系统的组合特性:

float sdCapsule(vec2 p, vec2 a, vec2 b, float r) { return sdSegment(p,a,b) - r; }

这种"减法"操作在SDF中称为"扩张"(offset),是构建复杂形状的基础技术之一。让我们创建一个动态胶囊体:

// 动态端点 vec2 a = vec2(-0.6, 0.1*sin(iTime)); vec2 b = vec2(0.6, -0.1*cos(iTime)); // 脉动半径 float radius = 0.1 * (1.0 + 0.3*sin(iTime*2.0)); float d = sdCapsule(p, a, b, radius);

胶囊体的可视化可以做得非常生动:

// 渐变色根据距离变化 vec3 col = mix(vec3(0.2,0.5,0.8), vec3(0.8,0.3,0.2), smoothstep(-0.5,0.5, sin(30.0*d))); // 添加发光效果 col += vec3(0.3,0.5,0.8) * exp(-10.0*abs(d));

6. SDF组合与变换

SDF真正的威力在于它们的组合性。GLSL提供了多种运算符来组合SDF:

// 并集 float unionSDF(float a, float b) { return min(a, b); } // 交集 float intersectSDF(float a, float b) { return max(a, b); } // 差集 float differenceSDF(float a, float b) { return max(a, -b); }

几何变换也同样重要:

// 平移 float translatedSDF(vec2 p, vec2 offset) { return sdCircle(p - offset, 0.3); } // 旋转 float rotatedSDF(vec2 p, float angle) { float c = cos(angle), s = sin(angle); vec2 q = vec2(c*p.x - s*p.y, s*p.x + c*p.y); return sdBox(q, vec2(0.4,0.2)); } // 缩放(均匀) float scaledSDF(vec2 p, float scale) { return sdBox(p/scale, vec2(0.3,0.5)) * scale; }

让我们创建一个组合示例:

// 基础圆形 float circle = sdCircle(p, 0.4); // 旋转矩形 float box = rotatedSDF(p, iTime*0.5); // 组合效果 float d = differenceSDF(circle, box); // 添加边框 float border = abs(d) - 0.02;

7. 高级技巧与性能优化

随着场景复杂度增加,我们需要考虑SDF的性能优化:

距离场抗锯齿

// 传统硬边缘 float edge = step(0.0, d); // 平滑边缘(推荐) float smoothEdge = smoothstep(0.0, fwidth(d), d);

空间划分加速

// 粗略距离测试 float boundingCircle = length(p) - 1.0; if(boundingCircle > 0.1) { // 快速跳过远距离区域 fragColor = vec4(0.0,0.0,0.0,1.0); return; }

SDF缓存技巧

// 复用中间计算结果 vec2 q = p * 2.0; float d1 = sdCircle(q, 0.5); float d2 = sdBox(q, vec2(0.3));

距离场着色技巧

// 法线估计(用于光照) vec3 estimateNormal(vec2 p) { float eps = 0.001; return normalize(vec3( sdCircle(p + vec2(eps,0.0)) - sdCircle(p - vec2(eps,0.0)), sdCircle(p + vec2(0.0,eps)) - sdCircle(p - vec2(0.0,eps)), eps )); } // 简单光照 vec3 n = estimateNormal(p); float diff = max(0.0, dot(n, normalize(vec3(0.8,0.6,0.0))));

8. 实战项目:构建2D SDF渲染引擎

让我们整合所学知识,创建一个可交互的SDF渲染系统:

// SDF场景定义 float sceneSDF(vec2 p) { // 背景网格 float grid = min(mod(p.x,0.1), mod(p.y,0.1)) - 0.005; // 动态元素 float circle = sdCircle(p-vec2(0.3,0.2), 0.2); float box = sdBox(p-vec2(-0.3,0.1), vec2(0.2,0.3)); float capsule = sdCapsule(p, vec2(-0.4,-0.3), vec2(0.4,0.3), 0.1); // 组合场景 return min(grid, min(circle, min(box, capsule))); } void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; // 计算场景SDF float d = sceneSDF(p); // 高级着色 vec3 col = vec3(0.0); // 距离场基础色 col = mix(vec3(0.2,0.5,0.8), vec3(0.8,0.3,0.2), smoothstep(-0.5,0.5,d)); // 添加等距线 col *= 0.7 + 0.3*cos(6.2831*d*5.0); // 边缘高光 col += vec3(1.0) * exp(-100.0*abs(d)); // 鼠标交互 vec2 mouse = (2.0*iMouse.xy-iResolution.xy)/iResolution.y; if(length(mouse) > 0.0) { float mouseDist = sceneSDF(mouse); float influence = exp(-50.0*length(p-mouse)); col = mix(col, vec3(1.0,1.0,0.0), influence); } fragColor = vec4(col,1.0); }

这个完整示例展示了:

  1. 模块化的SDF场景构建
  2. 多层次着色技术
  3. 交互式元素
  4. 视觉效果增强技巧

9. 创意延伸:SDF的无限可能

掌握了SDF基础后,你可以探索更多创意方向:

动态变形效果

// 波纹变形 float ripple = 0.02 * sin(p.x * 20.0 + iTime * 3.0); d += ripple;

布尔操作进阶

// 平滑并集 float smoothUnion(float a, float b, float k) { float h = clamp(0.5 + 0.5*(b-a)/k, 0.0, 1.0); return mix(b, a, h) - k*h*(1.0-h); }

纹理集成

// 基于距离场的纹理映射 float pattern = sin(d * 30.0) * 0.5 + 0.5; col *= texture(iChannel0, p).rgb * pattern;

2D到3D的延伸

// 简单的3D扩展 float sdSphere(vec3 p, float r) { return length(p) - r; }

在Shadertoy社区中,艺术家们已经用SDF创造了无数令人惊叹的作品。从抽象的视觉艺术到逼真的场景渲染,SDF都展现出惊人的表现力。记住,每个复杂效果都是从一个简单的圆形或线段开始的——你现在已经掌握了这些基础构建模块,接下来就是发挥创意的时候了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 10:21:25

AI智能体记忆管理:Mem0开源库原理、实战与优化指南

1. 项目概述:从记忆到智能体,Mem0的定位与价值最近在探索AI智能体开发时,我反复遇到一个核心瓶颈:如何让智能体记住过去?无论是构建一个能进行多轮深度对话的客服助手,还是一个能长期管理项目进度的个人助理…

作者头像 李华
网站建设 2026/5/15 10:20:16

Arduino TFT触摸屏扩展板:从SPI/I2C原理到图形界面实战

1. 项目概述与核心价值如果你正在为你的Arduino项目寻找一个“开箱即用”的图形交互界面解决方案,那么Adafruit的这款2.8寸TFT触摸屏扩展板绝对值得你花时间深入了解。它不是一块简单的显示屏,而是一个集成了显示、触摸输入、存储扩展甚至快速传感器接口…

作者头像 李华
网站建设 2026/5/15 10:19:12

为什么需要纯前端OFD解析?ofd.js完整解决方案揭秘

为什么需要纯前端OFD解析?ofd.js完整解决方案揭秘 【免费下载链接】ofd.js OFD板式文件html渲染方案及组件 项目地址: https://gitcode.com/gh_mirrors/of/ofd.js 在数字化办公和电子政务快速发展的今天,OFD(Open Fixed-layout Docume…

作者头像 李华
网站建设 2026/5/15 10:18:08

Arduino IDE配置Adafruit SAMD开发板:从环境搭建到代码优化全解析

1. Arduino IDE与Adafruit SAMD开发板:从零开始的嵌入式开发环境搭建如果你刚拿到一块Adafruit的Feather M0、Metro M4或者Circuit Playground Express,第一件事肯定是想让它“跑”起来。对于习惯了传统AVR架构Arduino(比如Uno、Nano&#xf…

作者头像 李华
网站建设 2026/5/15 10:17:04

PUBG罗技鼠标宏压枪脚本:计算机视觉与硬件控制的完美结合

PUBG罗技鼠标宏压枪脚本:计算机视觉与硬件控制的完美结合 【免费下载链接】PUBG-Logitech PUBG罗技鼠标宏自动识别压枪 项目地址: https://gitcode.com/gh_mirrors/pu/PUBG-Logitech PUBG-Logitech是一款基于罗技鼠标宏与计算机视觉识别技术的绝地求生压枪辅…

作者头像 李华