Defold Render Scripts 与自定义渲染管线

系统讲解 Defold 渲染管线:内置 render script 原理、摄像机栈管理、视口与多分辨率适配、后处理(Bloom/Blur)实现、多摄像机同屏叠加、全屏特效 shader 与材质系统。

引言

Defold 的渲染不是「黑盒」——通过 Render Script(.render_script 文件),你可以完全掌控每一帧的画面输出流程。从默认的 2D 精灵渲染,到多摄像机分屏、后处理 Bloom 效果、全屏 shader 特效,Render Script 是瓶颈所在,也是定制的关键。本文系统讲解 Defold 渲染管线:内置 render script 的工作流程、摄像机栈管理、视口与分辨率适配、Bloom/Blur 后处理实现、多摄像机同屏叠加、全屏特效 shader 编写与材质系统。

前置:/defold-camera-viewport/(摄像机与视口基础)。Shader 入门见 /defold-shader-postprocessing/。


目录


1. Render Script 是什么?渲染的「指挥脚本」

Render Script = 用 Lua 写的渲染管线脚本,每帧由引擎调用,决定:

1. 清屏(颜色/深度)
2. 设置视口与投影矩阵
3. 遍历摄像机,绘制可见对象
4. 后处理(Bloom/Gaussian Blur/全屏特效)
5. 输出到屏幕 / Render Target

创建 Render Script:

1. 右键 Assets → New → Render
2. 自动生成 default.render + default.render_script
3. 在 game.project → Bootstrap → Render 指定使用的 .render 文件

说明:.render 文件是材质与渲染脚本的关联表,实际逻辑在 .render_script 中。


2. 内置渲染管线走读

2.1 默认 render_script 核心结构

-- 默认 render_script 的核心流程
function init(self)
    -- 创建 render target(后处理用)
    self.my_predicates = {}
    -- 注册材质/绘制谓词
end

function update(self, dt)
    -- 1. 清屏
    render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1})

    -- 2. 设置视口
    local width = render.get_window_width()
    local height = render.get_window_height()
    render.set_viewport(0, 0, width, height)

    -- 3. 遍历所有摄像机
    local cameras = render.get_cameras()   -- 实际 API 稍不同,见下文
    for _, camera in ipairs(cameras) do
        render.set_view(camera.view)
        render.set_projection(camera.proj)
        render.draw(self.tile_pred)       -- 绘制 Tilemap
        render.draw(self.sprite_pred)     -- 绘制 Sprite
        render.draw(self.particle_pred)   -- 绘制粒子
        render.draw(self.gui_pred)        -- 绘制 GUI
    end

    -- 4. 如果需要后处理,绘制到屏幕 quad
end

2.2 核心 API 速览

-- 清屏
render.clear({
    [render.BUFFER_COLOR_BIT] = vmath.vector4(0.2, 0.2, 0.2, 1.0),
    [render.BUFFER_DEPTH_BIT] = 1,
    [render.BUFFER_STENCIL_BIT] = 0
})

-- 设置状态
render.set_viewport(x, y, width, height)
render.set_view(matrix4_view)           -- 摄像机 view 矩阵
render.set_projection(matrix4_proj)     -- 投影矩阵(正交/透视)

-- 绘制谓词
render.draw(predicates)                 -- 按材质/Tag 批量绘制对象

-- Render Target
render.enable_texture(slot, handle)     -- 绑定纹理到 shader slot
render.set_render_target(handle)        -- 切换到 FBO 渲染

2.3 谓词(Predicate)与材质过滤

Predicate = 按 Tag 分组绘制,类似渲染队列:

-- 在 render_script 中创建谓词
function init(self)
    -- 创建谓词:只绘制 Tag 包含 "sprite" 的材质
    self.sprite_pred = render.predicate({"sprite"})
    self.tile_pred = render.predicate({"tile"})
    self.particle_pred = render.predicate({"particle"})
    self.gui_pred = render.predicate({"gui"})
end

3. 摄像机栈:多摄像机的管理

3.1 摄像机组件配置

1. 给 Game Object 添加 Camera 组件
2. 设置:
   - Type: Orthographic(2D)/ Perspective(3D)
   - Near/Far Z
   - Orthographic Zoom
   - Orthographic Projection: Fixed Width / Fixed Height / Fixed Auto

3.2 在 Render Script 中遍历摄像机

function update(self, dt)
    -- 清屏
    render.clear(...)

    -- 获取所有活跃摄像机(按 Z 顺序)
    render.set_depth_mask(true)
    render.set_stencil_mask(0xff)

    -- 定义绘制顺序
    local draw_order = {
        self.tile_pred,
        self.sprite_pred,
        self.particle_pred,
        self.gui_pred
    }

    -- 光栅化设置
    render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
    render.enable_state(render.STATE_BLEND)

    -- 默认正交投影(2D 游戏)
    local w = render.get_window_width()
    local h = render.get_window_height()
    local proj = vmath.matrix4_orthographic(0, w, 0, h, -1, 1)
    render.set_projection(proj)
    render.set_view(vmath.matrix4_identity())

    -- 绘制各层
    for _, pred in ipairs(draw_order) do
        render.draw(pred)
    end
end

记忆:Render Script 的 update()` 每帧跑一次,按「清屏 → 设视口 → 设投影 → 遍历谓词绘制」的顺序执行。


4. 视口与多分辨率适配

4.1 响应式适配策略

-- 固定高度适配(推荐竖屏手游)
function update(self, dt)
    local window_w = render.get_window_width()
    local window_h = render.get_window_height()

    -- 设计分辨率 720x1280,固定高度
    local design_w, design_h = 720, 1280
    local scale = window_h / design_h
    local scaled_w = window_w / scale

    -- 设置投影
    local proj = vmath.matrix4_orthographic(
        0, scaled_w,       -- 左右
        0, design_h,       -- 下上
        -1, 1
    )
    render.set_projection(proj)

    -- 居中裁剪或延展
    local offset_x = (scaled_w - design_w) / 2
    render.set_viewport(
        math.floor(offset_x * scale), 0,
        math.floor(design_w * scale), window_h
    )
end

4.2 黑边适配(Letterbox)

function update(self, dt)
    local ww = render.get_window_width()
    local wh = render.get_window_height()
    local dw, dh = 1280, 720   -- 设计分辨率

    local scale = math.min(ww / dw, wh / dh)
    local vw = math.floor(dw * scale)
    local vh = math.floor(dh * scale)
    local vx = math.floor((ww - vw) / 2)
    local vy = math.floor((wh - vh) / 2)

    -- 黑边模式
    render.set_viewport(vx, vy, vw, vh)

    local proj = vmath.matrix4_orthographic(0, dw, 0, dh, -1, 1)
    render.set_projection(proj)
end

5. 后处理:Bloom 与 Blur 实现

5.1 后处理流程

正常场景 → 渲染到 Render Target A → Bloom Shader(提取高亮)
                                      ↓
高亮图 → 多次降采样 + Gaussian Blur(水平/垂直各一次)
                                      ↓
原图 + Blur 图叠加(Additive)→ 屏幕

5.2 Render Target 创建

function init(self)
    -- 创建两个 render target 做 Ping-Pong Blur
    local w, h = render.get_window_width(), render.get_window_height()

    self.rt1 = render.render_target("rt1", {
        format = render.FORMAT_RGBA,
        width = w,
        height = h,
        min_filter = render.FILTER_LINEAR,
        mag_filter = render.FILTER_LINEAR
    })

    self.rt2 = render.render_target("rt2", {
        format = render.FORMAT_RGBA,
        width = w / 2,   -- 降采样加速
        height = h / 2,
        min_filter = render.FILTER_LINEAR,
        mag_filter = render.FILTER_LINEAR
    })

    -- 全屏 quad 材质
    self.quad_pred = render.predicate({"quad"})
end

5.3 Bloom + Blur Render Script

function update(self, dt)
    local w = render.get_window_width()
    local h = render.get_window_height()

    -- Step 1: 场景渲染到 rt1
    render.set_render_target(self.rt1)
    render.clear({[render.BUFFER_COLOR_BIT] = vmath.vector4(0,0,0,0)})
    render.set_viewport(0, 0, w, h)
    render.set_view(vmath.matrix4_identity())
    render.set_projection(vmath.matrix4_orthographic(0, w, 0, h, -1, 1))

    -- 画场景
    render.draw(self.sprite_pred)
    render.draw(self.tile_pred)

    -- Step 2: 提取高亮 → rt2(降采样)
    render.set_render_target(self.rt2)
    render.clear({[render.BUFFER_COLOR_BIT] = vmath.vector4(0,0,0,0)})
    render.set_viewport(0, 0, w/2, h/2)
    render.enable_texture(0, self.rt1)
    render.draw(self.bright_extract_pred)   -- 用 bright_extract.material

    -- Step 3: 水平 Blur → rt1
    render.set_render_target(self.rt1)
    render.clear({[render.BUFFER_COLOR_BIT] = vmath.vector4(0,0,0,0)})
    render.set_viewport(0, 0, w/2, h/2)
    render.enable_texture(0, self.rt2)
    render.draw(self.blur_h_pred)

    -- Step 4: 垂直 Blur → rt2
    render.set_render_target(self.rt2)
    render.clear({[render.BUFFER_COLOR_BIT] = vmath.vector4(0,0,0,0)})
    render.enable_texture(0, self.rt1)
    render.draw(self.blur_v_pred)

    -- Step 5: 合成 → 屏幕
    render.set_render_target(render.RENDER_TARGET_DEFAULT)
    render.clear({[render.BUFFER_COLOR_BIT] = vmath.vector4(0.1,0.1,0.1,1)})
    render.set_viewport(0, 0, w, h)
    render.set_projection(vmath.matrix4_orthographic(0, w, 0, h, -1, 1))

    -- 画原场景
    render.enable_texture(0, self.rt1_temp or self.rt1)
    render.draw(self.sprite_pred)   -- 或者直接保持场景在 rt1_temp

    -- 叠加 bloom
    render.enable_texture(1, self.rt2)
    render.draw(self.composite_pred)
end

注意:上面的简化了中间缓存管理。实际项目中需要更精细的 render target 引用管理,避免每帧创建/销毁带来的开销。

5.4 Bright Extract Shader

-- bright_extract.fp(Fragment Shader)
varying mediump vec2 var_texcoord0;

uniform lowp sampler2D texture_sampler;
uniform lowp vec4 bright_threshold;

void main()
{
    lowp vec4 color = texture2D(texture_sampler, var_texcoord0.xy);
    lowp float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
    if (brightness > bright_threshold.x) {
        gl_FragColor = color;
    } else {
        gl_FragColor = vec4(0.0);
    }
}

6. 多摄像机同屏叠加

6.1 场景:小地图 + 主视角

function update(self, dt)
    local w = render.get_window_width()
    local h = render.get_window_height()

    -- 清屏
    render.clear(...)

    -- === 主摄像机(全屏)===
    render.set_viewport(0, 0, w, h)
    render.set_view(self.main_camera_view)
    render.set_projection(self.main_camera_proj)
    render.draw(self.sprite_pred)
    render.draw(self.tile_pred)

    -- === 小地图摄像机(右下角 200x150)===
    local minimap_w, minimap_h = 200, 150
    render.set_viewport(w - minimap_w - 10, 10, minimap_w, minimap_h)
    render.set_view(self.minimap_camera_view)
    -- 小地图用正交 + 缩放
    local mm_proj = vmath.matrix4_orthographic(-200, 200, -150, 150, -1, 1)
    render.set_projection(mm_proj)

    -- 画小地图场景
    render.draw(self.tile_pred)
    render.draw(self.sprite_pred)

    -- === 小地图边框 ===
    render.set_viewport(0, 0, w, h)
    render.set_projection(vmath.matrix4_orthographic(0, w, 0, h, -1, 1))
    render.draw(self.gui_pred)   -- 画 UI 层覆盖
end

6.2 分屏游戏

-- 左右分屏(2 人合作)
function update(self, dt)
    local w = render.get_window_width()
    local h = render.get_window_height()

    render.clear(...)

    -- 左半屏 - Player 1
    render.set_viewport(0, 0, w / 2, h)
    render.set_view(self.p1_view)
    render.set_projection(self.p1_proj)
    render.draw(self.sprite_pred)

    -- 右半屏 - Player 2
    render.set_viewport(w / 2, 0, w / 2, h)
    render.set_view(self.p2_view)
    render.set_projection(self.p2_proj)
    render.draw(self.sprite_pred)

    -- 中间分割线
    render.set_viewport(0, 0, w, h)
    render.set_projection(vmath.matrix4_orthographic(0, w, 0, h, -1, 1))
    render.draw(self.gui_pred)
end

记忆:多相机 = 多次设置 viewport + view + projection → draw——每个相机就是一个「视口窗口」。


7. 全屏特效 Shader 与材质系统

7.1 材质文件结构(.material)

my_effect.material:
  Tag: postprocess
  Vertex Shader: /shaders/fullscreen.vp
  Fragment Shader: /shaders/my_effect.fp
  Vertex Constants:
    - view_proj
  Fragment Constants:
    - texture_sampler
    - u_time

7.2 全屏顶点 Shader

-- fullscreen.vp
uniform mediump mat4 view_proj;

attribute mediump vec4 position;
attribute mediump vec2 texcoord0;

varying mediump vec2 var_texcoord0;

void main()
{
    gl_Position = view_proj * vec4(position.xyz, 1.0);
    var_texcoord0 = texcoord0;
}

7.3 扫描线特效 Fragment Shader

-- scanline.fp
varying mediump vec2 var_texcoord0;

uniform lowp sampler2D texture_sampler;
uniform lowp vec4 u_time;

void main()
{
    lowp vec4 color = texture2D(texture_sampler, var_texcoord0.xy);

    // 扫描线效果
    float scanline = sin(var_texcoord0.y * 800.0 + u_time.x * 2.0) * 0.1;
    color.rgb -= scanline;

    // 暗角 vignette
    float dist = distance(var_texcoord0.xy, vec2(0.5));
    color.rgb *= smoothstep(0.8, 0.2, dist);

    gl_FragColor = color;
}

7.4 在 Render Script 中传递时间参数

function update(self, dt)
    self.time = (self.time or 0) + dt

    -- ... 设好 render target 和 viewport ...

    -- 传递 uniform 给 shader
    local constants = render.constant_buffer()
    constants.u_time = vmath.vector4(self.time, 0, 0, 0)
    render.draw(self.scanline_pred, constants)
end

8. 性能建议

优化项建议
Render Target 尺寸后处理用 1/2 或 1/4 分辨率,大幅减少像素处理
Blur Pass 次数通常 2 Pass(水平+垂直)足够,不必多次迭代
Predicate 数量合并同类 Tag,减少 draw call 批次
每帧重建矩阵缓存 view/proj 矩阵,只在变化时更新
移动端后处理谨慎使用,低端机可能直接关闭 Bloom
多摄像机小地图每帧只更新一次,不必逐帧重算 view

9. 速查表

需求API/方法说明
清屏render.clear({[BUFFER_COLOR_BIT]=color})RGBA + 深度
设置视口render.set_viewport(x, y, w, h)屏幕像素坐标
正交投影vmath.matrix4_orthographic(l,r,b,t,n,f)2D 游戏默认
创建谓词render.predicate({"tag1","tag2"})按 Tag 过滤绘制
创建 RTrender.render_target(name, {...})FBO 离屏渲染
绑定纹理render.enable_texture(slot, handle)Shader 采样器
切换 RTrender.set_render_target(handle)默认传 DEFAULT
多摄像机多次 set_viewport + draw小地图/分屏
Bloom 流程场景 → Bright Extract → Blur H → Blur V → CompositePing-Pong RT
传递常量render.constant_buffer()Shader uniform

一句话记忆:Render Script 是渲染总指挥——update 每帧「清屏 → 设视口 → 投影 → 遍历谓词 draw」;多相机靠多次 set_viewport 实现;后处理用 Render Target 做 Ping-Pong Blur + Bloom;全屏特效用正交 quad + shader;性能靠降采样 RT 和缓存矩阵。


相关阅读

  • /defold-shader-postprocessing/ — Shader 入门与后处理基础
  • /defold-camera-viewport/ — 摄像机组件与视口配置
  • /defold-performance-optimization/ — Draw Call 与渲染性能优化
  • /defold-sprite-animation/ — Sprite 渲染与材质

延伸阅读

  • /defold-ui-gui/ — GUI 渲染层级与覆盖
  • /defold-3d-games/ — 3D 透视投影与深度缓冲
  • /defold-particle-system/ — 粒子渲染的谓词处理
  • 图形学与 GPU 专题 — GPU 渲染管线原理
  • 游戏开发专题 — 游戏引擎渲染架构
-- ======================================
-- 完整可运行示例:自定义 Render Script
-- 放入 custom.render_script
-- ======================================

function init(self)
    -- 创建绘制谓词
    self.tile_pred = render.predicate({"tile"})
    self.sprite_pred = render.predicate({"sprite"})
    self.gui_pred = render.predicate({"gui"})
    self.particle_pred = render.predicate({"particle"})

    -- 清屏颜色
    self.clear_color = vmath.vector4(0.2, 0.22, 0.25, 1.0)

    print("[init] Custom Render Script 已初始化")
end

function update(self, dt)
    local w = render.get_window_width()
    local h = render.get_window_height()

    -- 1. 清屏
    render.clear({
        [render.BUFFER_COLOR_BIT] = self.clear_color,
        [render.BUFFER_DEPTH_BIT] = 1,
        [render.BUFFER_STENCIL_BIT] = 0
    })

    -- 2. 设置视口(全窗口)
    render.set_viewport(0, 0, w, h)

    -- 3. 设置渲染状态
    render.set_blend_func(
        render.BLEND_SRC_ALPHA,
        render.BLEND_ONE_MINUS_SRC_ALPHA
    )
    render.enable_state(render.STATE_BLEND)
    render.enable_state(render.STATE_DEPTH_TEST)
    render.set_depth_mask(false)

    -- 4. 正交投影(适配不同分辨率)
    local design_w, design_h = 1280, 720
    local scale = math.min(w / design_w, h / design_h)
    local scaled_w = design_w * scale
    local scaled_h = design_h * scale
    local offset_x = (w - scaled_w) / 2
    local offset_y = (h - scaled_h) / 2

    render.set_viewport(
        math.floor(offset_x),
        math.floor(offset_y),
        math.floor(scaled_w),
        math.floor(scaled_h)
    )

    local proj = vmath.matrix4_orthographic(0, design_w, 0, design_h, -1, 1)
    render.set_projection(proj)
    render.set_view(vmath.matrix4_identity())

    -- 5. 按顺序绘制各层
    render.draw(self.tile_pred)       -- 背景层
    render.draw(self.sprite_pred)     -- 精灵层
    render.draw(self.particle_pred)   -- 粒子层
    render.draw(self.gui_pred)        -- UI 顶层
end

function on_message(self, message_id, message, sender)
    if message_id == hash("set_clear_color") then
        self.clear_color = message.color
    end
end

继续阅读

探索更多技术文章

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

全部文章 返回首页

「defold」更多文章

  1. Defold 网络通信:HTTP、WebSocket 与 REST API
  2. Defold Native Extensions:C/C++ 扩展引擎能力
  3. Defold Tilemap 碰撞与关卡设计