引言
着色器(Shader)是 Defold 视觉表现的「上限」——发光、溶解、描边、水波、全屏色调,全都靠 GLSL。本文讲透 Defold 着色器体系:先理解渲染管线(vertex → fragment 两阶段)与材质(Material)怎么把 shader 绑到精灵上,再写第一个自定义 shader(变色/溶解/描边),接着讲全屏后处理(模糊/色调/画面扭曲)的实现原理,最后给着色器性能权衡与常见坑,让你用代码给游戏上「电影级滤镜」。
前置:/defold-game-engine-introduction/(组件与渲染)、/defold-editor-asset-pipeline/(资源管线)。渲染管线原理见 [[graphics]]。
目录
- 1. 着色器是什么:渲染管线的两阶段
- 2. 材质(Material):把 shader 绑到精灵
- 3. 第一个自定义着色器:变色与溶解
- 4. 常用特效:描边、水波与发光
- 5. 全屏后处理:模糊、色调与扭曲
- 6. Uniform 传参:让 shader 动起来
- 7. 后处理实现:渲染目标与全屏 pass
- 8. 着色器性能权衡
- 9. 常见坑与调试
- 10. 速查表
- 延伸阅读
1. 着色器是什么:渲染管线的两阶段
GPU 渲染一个精灵走两个 shader 阶段:
Vertex shader(顶点着色器):
输入:每个顶点(位置/UV/颜色)
输出:变换后的顶点 + 插值数据
作用:摆位置、算 UV
Fragment shader(片元着色器):
输入:每个像素(插值后的 UV/颜色)
输出:最终像素颜色
作用:上色、特效(90% 特效写在这)
管线示意:
顶点数据 → [Vertex] → 光栅化 → [Fragment] → 屏幕像素
位置/UV 变换顶点 像素插值 最终颜色
心智:Vertex 管「在哪」,Fragment 管「长啥样」——写特效 90% 时间花在 fragment shader。
2. 材质(Material):把 shader 绑到精灵
材质 = shader + 渲染状态(blend/深度等):
Material(材质):
├─ Vertex Program(顶点着色器)
├─ Fragment Program(片元着色器)
├─ Blend 模式 / 深度测试
└─ Tags(批处理分组)
Defold 里创建材质:
1. 写 .fp(fragment)和 .vp(vertex)GLSL 文件
2. 新建 Material 资源,绑定两个 shader
3. 把 Material 赋给 Sprite / 模型 / GUI 节点
内置材质:Defold 自带若干(builtins/materials/),可复制改。
记忆:材质是「shader + 渲染状态」的打包——精灵想用自定义效果,就给它换材质。
3. 第一个自定义着色器:变色与溶解
Fragment shader 基础结构(Defold GLSL):
// my_color.fp —— 变色特效
varying mediump vec2 var_texcoord0; // 精灵的 UV 坐标
uniform lowp sampler2D DIFFUSE_TEXTURE; // 精灵纹理
uniform lowp vec4 tint; // Defold 传入的颜色
void main()
{
vec4 color = texture2D(DIFFUSE_TEXTURE, var_texcoord0);
// 变色:只保留红色通道
color.rgb *= vec3(1.0, 0.3, 0.3);
gl_FragColor = color * tint;
}
溶解(dissolve)效果:
// dissolve.fp —— 用噪声做溶解
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D DIFFUSE_TEXTURE;
uniform lowp vec4 tint;
uniform mediump float progress; // 0-1,溶解进度
uniform mediump float threshold; // 噪声阈值
void main()
{
vec4 color = texture2D(DIFFUSE_TEXTURE, var_texcoord0);
// 简单伪噪声:用 UV 构造
float noise = fract(sin(dot(var_texcoord0, vec2(12.9898, 78.233))) * 43758.5453);
float cutoff = 1.0 - progress; // 溶解线
if (noise > cutoff - threshold) discard; // 丢像素 = 溶解
gl_FragColor = color * tint;
}
记忆:fragment shader = 读纹理 → 改颜色 → 输出——变色就是乘个颜色、溶解就是按噪声 discard。
4. 常用特效:描边、水波与发光
描边(outline)——取邻域对比:
// outline.fp —— 边缘检测描边
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D DIFFUSE_TEXTURE;
uniform lowp vec4 tint;
uniform lowp vec2 texel_size; // 单纹素大小
void main()
{
vec4 c = texture2D(DIFFUSE_TEXTURE, var_texcoord0);
float alpha = c.a;
// 周围 4 个方向采样,若旁边有透明 → 是边缘
float around =
texture2D(DIFFUSE_TEXTURE, var_texcoord0 + vec2(texel_size.x, 0)).a +
texture2D(DIFFUSE_TEXTURE, var_texcoord0 - vec2(texel_size.x, 0)).a +
texture2D(DIFFUSE_TEXTURE, var_texcoord0 + vec2(0, texel_size.y)).a +
texture2D(DIFFUSE_TEXTURE, var_texcoord0 - vec2(0, texel_size.y)).a;
if (alpha > 0.01 && around < 3.0) {
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0); // 黄色描边
} else {
gl_FragColor = c * tint;
}
}
水波(UV 偏移模拟):
// wave.fp —— UV 正弦偏移
uniform mediump float time;
void main()
{
vec2 uv = var_texcoord0;
uv.x += sin(uv.y * 10.0 + time * 3.0) * 0.02; // 水平波浪
vec4 color = texture2D(DIFFUSE_TEXTURE, uv);
gl_FragColor = color * tint;
}
记忆:描边看邻域透明、水波挪 UV、发光叠亮色——特效 = 采样技巧 + 数学函数。
5. 全屏后处理:模糊、色调与扭曲
后处理 = 把「整张画面」当纹理再做一次 shader:
场景渲染到渲染目标(Render Target)→ 当纹理 → 全屏 quad → 后处理 shader → 屏幕
常见后处理:模糊、色调、泛光、暗角、画面扭曲
模糊(blur)——采样周围像素平均:
// blur.fp —— 高斯模糊(简化版)
uniform lowp sampler2D SCENE; // 全屏纹理
uniform lowp vec2 texel_size;
uniform mediump int direction; // 0=横向 1=纵向
void main()
{
vec4 sum = vec4(0.0);
// 9 个采样点加权平均(横向模糊示例)
for (int i = -4; i <= 4; i++) {
vec2 off = vec2(float(i) * texel_size.x, 0.0) * float(1 - direction);
// 简化:均匀加权
sum += texture2D(SCENE, var_texcoord0 + off);
}
gl_FragColor = sum / 9.0;
}
色调(色调偏移 / 暗角):
// 色调:移色相
gl_FragColor = vec4(scene.r * 0.5 + scene.g * 0.5, scene.g, scene.b, 1.0);
// 暗角:边缘压暗
float vig = smoothstep(1.0, 0.3, length(var_texcoord0 - 0.5));
记忆:后处理三步——场景进纹理、全屏 shader 采样、输出屏幕——模糊平均邻居、暗角乘边缘权重。
6. Uniform 传参:让 shader 动起来
Uniform = 从代码传值给 shader——让效果实时变化:
// shader 里声明 uniform
uniform mediump float progress; // 溶解进度
uniform mediump float time; // 时间
Defold 代码里设置 uniform:
-- 给 sprite 的材质 uniform 传值
local sp = msg.url("#sprite")
go.set(sp, "material", res) -- 设置材质
-- 动态设置 uniform(每帧传 progress)
render.enable_material_uniform(sp, "progress", { 0.8 })
-- 或用 msg:
msg.post(sp, "set_material_uniform", {
uniform = hash("progress"),
value = { self.progress },
})
常用 uniform 传参模式:
-- 受伤闪白:把闪白强度传进去
function on_hit(self)
self.flash = 1.0
end
function update(self, dt)
self.flash = math.max(self.flash - dt * 3, 0)
msg.post("#sprite", "set_material_uniform",
{ uniform = hash("flash"), value = { self.flash } })
end
记忆:uniform 是「代码和 shader 的桥梁」——时间、进度、强度全从 Lua 传,shader 就活了。
7. 后处理实现:渲染目标与全屏 pass
完整后处理流程(Defold render script 层):
1. 创建 Render Target(渲染目标)
2. 场景渲染到 RT(不直接上屏)
3. 切换 RT 为全屏纹理
4. 用后处理材质画一个全屏 quad
5. 输出到屏幕
Render script 骨架(简化):
-- render/render.postfx.script
function init(self)
self.target = render.create_render_target({ }) -- 渲染目标
self.quad = create_fullscreen_quad() -- 全屏 quad
end
function update(self)
-- 第一遍:场景画到渲染目标
render.set_render_target(self.target)
render.draw_scene()
-- 第二遍:用后处理材质画 quad 到屏幕
render.set_render_target(render.RENDER_TARGET_DEFAULT)
render.enable_material(self.postfx_material) -- 后处理材质
render.draw(self.quad, self.target_texture)
end
记忆:后处理 = 「先离屏画场景、再全屏处理上屏」——render script 里切两遍 pass 就完成。
8. 着色器性能权衡
着色器不是免费的——每像素计算要花钱:
| 因素 | 性能影响 |
|---|---|
| 采样数 | 一次 texture2D 有成本(模糊 9 采样较贵) |
| 全屏后处理 | 每像素跑 shader(分辨率越高越贵) |
| 分支 | GPU 分支有开销 |
| 复杂函数 | sin/pow 相对贵 |
| 移动端 | 纹理采样与 ALU 都受限 |
性能策略:
1. 后处理降分辨率(0.5x)再放大 → 省 4 倍
2. 能合批的别拆(同材质)
3. 特效只在需要时开(受击闪白淡出后关)
4. 少用全屏模糊,用可承受的采样数
5. 移动端优先简单 shader
记忆:着色器性能 = 采样数 × 分辨率——全屏后处理先降分辨率、模糊少采样、特效用完就关。
9. 常见坑与调试
Defold 着色器常见问题:
| 坑 | 原因 | 解法 |
|---|---|---|
| 全黑/全白 | 变量名或常量不符 | 检查 uniform 名与内置常量 |
| 没效果 | 材质没生效 | 确认精灵用对材质 |
| 花屏 | UV 坐标错 | 检查 texcoord 声明 |
| 性能暴跌 | 后处理全分辨率 | 降 RT 分辨率 |
| 平台差异 | 精度/内置函数不同 | 用 mediump 保守写法 |
| 编译报错 | GLSL 版本语法 | 对照 builtins 例子 |
调试手段:
1. 改一个变量看变化(二分定位)
2. 用纯色代替采样(确认 shader 是否执行)
3. 打印 uniform 值(代码侧确认传参)
4. 对照 Defold 内置 shader 例子
记忆:shader 排错三板斧——纯色替换定位、二分改变量、对照内置例子——先确认执行了,再查算法。
10. 速查表
| 需求 | 做法 |
|---|---|
| 创建材质 | Material 绑 .vp + .fp |
| 变色 | fragment 乘颜色 |
| 溶解 | 噪声阈值 discard |
| 描边 | 邻域采样对比 |
| 水波 | UV 正弦偏移 |
| 后处理 | 离屏 RT + 全屏 quad |
| 模糊 | 邻域加权平均 |
| 传参 | set_material_uniform |
| 性能 | 降分辨率 + 少采样 |
| 调试 | 纯色替换 + 二分变量 |
一句话记忆:Defold 着色器 = vertex 管位置 + fragment 管颜色;材质把 shader 绑到精灵;变色乘色、溶解 discard、描边看邻域、水波挪 UV;后处理先离屏渲染再全屏采样;uniform 从 Lua 传参让特效动起来;性能靠降分辨率 + 少采样——GLSL 一学,画面天花板抬高三米。
延伸阅读
- /defold-editor-asset-pipeline/ — 材质与资源管线
- /defold-sprite-animation/ — 精灵渲染与图集
- /defold-performance-optimization/ — 着色器性能权衡
- /defold-3d-games/ — 光照与材质
- [[graphics]] — GLSL 与渲染管线
- [[hpc]] — GPU 并行计算
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。