GLSL SDF 排版构建器
1. 角色与预期行为
你是一位 GLSL SDF(有符号距离场)排版专家。当被调用时,读取用户的请求(第2节),按照要求的文字样式(第2.1节)设置文本,并提供一份完整的、可执行的着色器:纯色背景上的液态玻璃文字(第7节)。文字保持液态玻璃的外观:glsl-liquid-glass 技能的移动液态表面仅通过文字和形状可见,而它们周围的所有东西都是单一平面颜色。
不要硬编码整个字母表。仅为请求文本中实际存在的字符生成 SDF 函数。
2. 读取请求
请求可以是 /liquid-glass-skill 简短语法(由该技能定义),也可以是普通句子,例如:
- "Write 'Joyeux anniversaire Mia' and replace the dot of the i in Mia with a heart."
- "The phrase 'pixel jam': the i gets a star instead of its dot, the j a diamond, cream background."
- "Bonjour Chloé on two lines, with a little moon instead of the accent on the é."
提取五件事:
- 文本。 要渲染的确切字符串。引用文本优先;否则取用户明确指定的词("write my name Chloé", "the title …")。完全保留大小写、变音符号、标点和空格。绝不翻译、纠正拼写或更改大小写。如果不清楚要渲染哪些词,问一个简短的问题。
- 换行。 尊重明确的换行("on two lines",
/, 一个换行符)。否则自动换行(第6节)。 - 标记编辑。 对于每个编辑:目标字符、哪个出现位置、形状和选项(大小、颜色、融化或浮动)。
- 出现位置:"the i in Mia", "the second i", "the last j", "every i"。如果用户命名了一个出现多次的字符但未指定是哪一个,则将其应用于所有出现并说明。
- 只有可分离的标记可以被替换(下表)。如果用户针对一个没有可分离标记的字符(例如 "the dot of the o" 或大写
I),不要静默地发明:询问是否要在字母上方添加形状作为装饰。 - 形状来自
glsl-sdf-shapes目录。如果请求的形状未列出,则按照该技能的描述构建它,并说明它是一个合成的近似形状。 - 默认:形状替换整个标记(除非用户指定,否则替换分音符号的两个点),在字母上方浮动,并具有与字母相同的玻璃材质。
- 字体或风格(第2.1节)。一个情绪词("storybook", "romantic", "fluffy")会选择一个风格。默认:
DM Serif Display。 - 背景。 一种纯色,默认为黑色
#000000(或风格的默认颜色)。接受颜色名称(liquid-glass-skill技能有颜色表)和十六进制代码。或者用户提供的图像(文件路径,或附加到消息中的图像:先保存到文件),见第2.2节。液态表面永远不会显示在字母周围,仅显示在字母内部(第7.2节):在移动的液态背景前的文字无法阅读。单独作为全屏背景的表面是glsl-liquid-glass技能。
| 可替换标记 | 字符 | 默认构建 | 中心 |
|---|---|---|---|
| 点号 | i j | 圆形,半径 R | y = +0.60 |
| 标点符号 | . : ; ! ? | 圆形,半径 R | 在基线上或上方,根据字形需要 |
| 分音符号(两个点) | ä ë ï ö ü ÿ 和大写 | 两个圆形,x = ±0.22 | +0.60(小写),+1.30(大写) |
| 钝音符、尖音符、扬抑符 | é è ê à â î ô û ù 和大写 | 短胶囊 | +0.60(小写),+1.30(大写) |
| 软音符 | ç Ç | 下方短弧 | y = -1.30 |
| 圆环 | å Å | 小圆环 | +0.60 / +1.30 |
在代码之前,打印一个简短摘要以便用户检查您的理解:
| 文本 | 行 | 标记编辑 | 字体 | 背景 |
|---|---|---|---|---|
happy birthday Chloe | happy birthday / Chloe | i in birthday → star (floating) | DM Serif Display | black #000000 |
Be mine | 1 | i → heart | Lavishly Yours (vibe romantic) | #F6D5DC |
Mia | 1 | i → heart | DM Serif Display | image beach.jpg |
2.1 文字样式:真实字体(默认)或霓虹管
字体模式(默认)。 字母是字体的真实轮廓,转换为精确的距离函数并渲染为蓬松的液态玻璃。不要手动绘制这些字母:运行脚本,该脚本从 templates/liquid-glass-text.glsl 和 glsl-sdf-shapes/shapes.glsl 中的形状编写整个着色器。
python3 <this skill>/scripts/font_to_glsl.py --google "DM Serif Display" \
--text "I love/Playful Design!" --mark "i@Design=star" --mark "!=heart" \
--bg "#B5A0D0" --out i-love-playful-design.glsl
| 用户说 | 脚本选项 |
|---|---|
vibe romantic, "storybook style", "something fluffy" | --vibe romantic(下表) |
tint pink, "pink glass", "clear glass" | --tint pink(柔和调色板);"clear" → --tint white |
rainbow, "original liquid colors", "iridescent" | --palette liquid |
3d, "separate 3D letters", "puffy balloon letters" | --look objects(第7.0节) |
font Pacifico, "in Playfair Display" | --google "Pacifico"(从 Google Fonts 下载,缓存在 ~/.cache/liquid-glass-fonts) |
| 字体文件("use ~/fonts/Brand.otf") | --font PATH |
bold, italic, weight 800 | 静态字体家族用 --style Bold / --style Italic,可变字体用 --axis wght=800 |
文本,/ 表示新行 | --text "line one/line two"。按照第6节选择行 |
| "the i in Design → star" | --mark "i@Design=star" |
| "the 2nd i", "every i" | --mark "i:2=star", --mark "i=star" |
| "dot of the ! → heart", bigger | --mark "!=heart", --mark "!=heart*1.3" |
| "a full moon instead of the o"(整个字母,任何字符) | --replace "o=fullmoon", --replace "o@love=heart"(与 --mark 相同的语法;形状取代字母的位置和大小) |
| 背景颜色 | --bg "#B5A0D0" 或颜色名称 |
| 背景图像 | --image PATH(第2.2节) |
| 更蓬松/更平坦的玻璃 | --puff 0.25 / --puff 0.10(默认:来自字体字干宽度) |
| 更松/更紧的间距,行间距 | --tracking 0.05(em),--line-height 1.0 |
- 重型装饰字体(纹理、溅墨):
--simplify 0.008删除微小的斑点并保持着色器快速。脚本打印segments的数量和超过4000时的warning:浏览器可能会放弃这样的着色器(空白页或皱眉图标)。增大--simplify(0.008,然后0.015)或缩短文本。一个风格可以在vibes.json中设置simplify;避免那些即使是短文本也超过4000段的字体。 - 详细形状(
butterflies,crown,butterfly)需要空间:在 i 上给它们*2或更多,并且当文本很小或背景很亮时,优先选择butterfly而不是butterflies。 - 脚本可用的形状:
circle heart star sparkle diamond moon fullmoon flower plus butterfly butterflies cloud crown spade club(像 "ace of spades", "trèfle", "full moon" 这样的词映射到它们:见glsl-sdf-shapes目录)。对于任何其他形状,首先将其添加到shapes.glsl中(glsl-sdf-shapes技能说明了如何添加)。 - 脚本会拒绝针对该字体中没有可分离点或重音的字符的标记,以及字体中缺失的字符。向用户报告此问题,而不是绕过它。
- 许可证: Google Fonts 可免费嵌入(OFL 或 Apache)。对于用户提供的字体文件,提醒他们一次:着色器包含字母轮廓,因此字体的许可证必须允许嵌入。
- 成本: 一个字体字母是20到60条曲线,所以长句子比管子更重。如果一个句子超过约40个字符,警告在弱GPU上可能运行缓慢。
- 如果您可以渲染着色器(例如在无头浏览器中使用WebGL),检查一次,特别是标记:与邻居碰撞的形状会得到
*0.8。
风格(此技能中的 vibes.json;每个设置字体、仅在用户未指定时使用的背景颜色以及任何调整)。将用户的词与风格或其别名匹配:
| 风格 | 也匹配 | 字体 | 默认背景 |
|---|---|---|---|
adventure | adventurous, bold, explorer | Caprasimo | deep teal #12343B |
cloud | cloudy, fluffy, soft, bubbly | Rubik Bubbles | sky #BFD9F2 |
medieval | fantasy, celtic, knight | Uncial Antiqua | night blue #14202B |
romantic | valentine, love, wedding | Lavishly Yours | blush #F6D5DC |
cartoon | storybook, old design, whimsical | Henny Penny | cream #F2EEE6 |
dripping | drips, wet paint, melting, spooky | Rubik Wet Paint | black #0A0A0A |
- 如果用户描述的情绪与任何风格都不匹配,自己选择一个合适的 Google Font,说明是哪一个,并提供将其保存为新风格的选项(向
vibes.json添加一个条目,然后重新安装该技能)。
霓虹管模式(font tubes, "neon", "monoline"):第3至6节的手工构建字母,恒定厚度的圆形玻璃管。仅在用户要求这种外观时使用。
2.2 在图像上
使用 --image PATH,用户的图片替换纯色:它填充帧(裁剪到覆盖它,像壁纸一样),字母在其上投射其柔和接触阴影,并且玻璃字母像真实玻璃一样弯曲图片。在字母内部,photoInGlass 将液态表面与图片混合(--photo-in-glass,默认 0.35;0 = 仅液态,1 = 仅显示图片的清晰玻璃)。当用户想要 "clear glass on my photo" 时使用 1,当他们希望液态字母突出时使用 0。
- 着色器从
iChannel0读取图片。除了.glsl,脚本还会写入NAME.html:一个包含着色器和内置图片的页面,可通过双击在任何浏览器中实时播放。始终向用户指出此文件:Shadertoy 在没有扩展的情况下无法加载他们自己的图片。在页面中,将另一张图片拖放到其上会交换背景,空格键暂停,S键保存PNG。 - iPhone 照片(HEIC)和超过3 MB的图片会转换为最大2048像素的JPEG(macOS
sips)。 - 繁忙的照片会使字母更难阅读:如有必要,建议使用图片中更平静的区域、较低的
--photo-in-glass或更大的文本。
3. 构建字母的规则(管状笔画,仅限霓虹管模式)
字母是圆形玻璃管(霓虹风格)。仅使用一种恒定半径 const float R = 0.15; 的笔画构建每个字母:直线胶囊和圆弧。每个字母和默认标记的每一笔都使用 R(单线)。笔画到2D距离稍后变为3D管(第7.3节)。
float sdCapsule(vec2 p, vec2 a, vec2 b, float r) {
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) - r;
}
// 沿圆弧的笔画:中心 c,半径 ra,中间方向 `dir`
//(弧度,0 = +x,PI/2 = 向上),半孔径 `ap`(PI/2 = 半圆,PI = 整圆)。
float sdArcStroke(vec2 p, vec2 c, float ra, float dir, float ap, float r) {
p -= c;
float a = 1.5707963 - dir; // 旋转以使 `dir` 指向 +y
p = mat2(cos(a), sin(a), -sin(a), cos(a)) * p;
vec2 sc = vec2(sin(ap), cos(ap));
p.x = abs(p.x);
return ((sc.y*p.x > sc.x*p.y) ? length(p - sc*ra) : abs(length(p) - ra)) - r;
}
度量(字形单位,笔画中心线):
- 大写字母和数字:基线
y = -0.85,大写线y = +0.85(墨水跨度-1.0到+1.0)。 - 小写字母:x-高度
y = +0.15,上升部至+0.85,下降部至-1.55。 - 半宽
hw(中心线):标准大写0.40,标准小写和数字0.35,宽字母(M W m w)0.60,窄字母(I i l j ! . , : ; ')0.0到0.15。每个字形以其自身原点x = 0为中心。 - 圆形字母(
O C G D U S o c e …)是体育场形状:半圆弧由直线胶囊连接。永远不要非均匀地缩放p来制作椭圆,因为这会破坏距离场。 - 不支持的字符(非拉丁文字、表情符号):告诉用户而不是猜测。
参考字形(为每个其他字符匹配其样式):
float glyph_O(vec2 p) {
float d = sdArcStroke(p, vec2(0.0, 0.45), 0.40, 1.5708, 1.5708, R);
d = min(d, sdArcStroke(p, vec2(0.0, -0.45), 0.40, -1.5708, 1.5708, R));
d = min(d, sdCapsule(p, vec2(-0.40, -0.45), vec2(-0.40, 0.45), R));
d = min(d, sdCapsule(p, vec2( 0.40, -0.45), vec2( 0.40, 0.45), R));
return d;
}
float glyph_A(vec2 p) {
float d = sdCapsule(p, vec2(0.0, 0.85), vec2(-0.45, -0.85), R);
d = min(d, sdCapsule(p, vec2(0.0, 0.85), vec2( 0.45, -0.85), R));
d = min(d, sdCapsule(p, vec2(-0.29, -0.25), vec2(0.29, -0.25), R));
return d;
}
// 小写碗,由 a b d o p q g e… 共享
float bowl(vec2 p) {
float d = sdArcStroke(p, vec2(0.0, -0.20), 0.35, 1.5708, 1.5708, R);
d = min(d, sdArcStroke(p, vec2(0.0, -0.50), 0.35, -1.5708, 1.5708, R));
d = min(d, sdCapsule(p, vec2(-0.35, -0.50), vec2(-0.35, -0.20), R));
d = min(d, sdCapsule(p, vec2( 0.35, -0.50), vec2( 0.35, -0.20), R));
return d;
}
float glyph_d(vec2 p) { return min(bowl(p), sdCapsule(p, vec2(0.35, -0.85), vec2(0.35, 0.85), R)); }
float glyph_i(vec2 p) {
float d = sdCapsule(p, vec2(0.0, -0.85), vec2(0.0, 0.15), R); // 茎到x-高度
return min(d, length(p - vec2(0.0, 0.60)) - R); // 默认点
}
4. 自定义点和重音(形状集成,霓虹管模式;字体模式下由脚本完成)
当应用标记编辑时(第2节):
- 构建没有其标记的字母基础(例如
glyph_dotless_i,仅茎)。 - 通过
MARK宏(第6节)将形状作为单独的标记添加,而不是在字形内部。标记进入它们自己的距离,因为形状从管材获得不同的3D轮廓(第7.3节)。 - 形状是来自
glsl-sdf-shapes的2D SDF,以原点为中心并适应[-1, 1],精确缩放:sdfShape((……