Defold 着色器与后处理:GLSL 材质、特效与全屏后期

系统覆盖 Defold 着色器与后处理:着色器管线(vertex/fragment)、材质(Material)配置、内置着色器与自定义 GLSL、常用特效(溶解/描边/水波)、全屏后处理(模糊/色调)、性能权衡。

引言

着色器(Shader)是 Defold 视觉表现的「上限」——发光、溶解、描边、水波、全屏色调,全都靠 GLSL。本文讲透 Defold 着色器体系:先理解渲染管线(vertex → fragment 两阶段)与材质(Material)怎么把 shader 绑到精灵上,再写第一个自定义 shader(变色/溶解/描边),接着讲全屏后处理(模糊/色调/画面扭曲)的实现原理,最后给着色器性能权衡与常见坑,让你用代码给游戏上「电影级滤镜」。

前置:/defold-game-engine-introduction/(组件与渲染)、/defold-editor-asset-pipeline/(资源管线)。渲染管线原理见 [[graphics]]。


目录


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 并行计算

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「defold」更多文章

  1. Defold 音频系统:Sound 组件、背景音乐、3D 音效与声音管理
  2. Defold 精灵与动画:Sprite、Flipbook、缓动与程序动画
  3. Defold 性能优化:Draw Call、图集合并、内存与 Profiler 实战