引言
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 是什么?渲染的「指挥脚本」
- 2. 内置渲染管线走读
- 3. 摄像机栈:多摄像机的管理
- 4. 视口与多分辨率适配
- 5. 后处理:Bloom 与 Blur 实现
- 6. 多摄像机同屏叠加
- 7. 全屏特效 Shader 与材质系统
- 8. 性能建议
- 9. 速查表
- 相关阅读
- 延伸阅读
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 过滤绘制 |
| 创建 RT | render.render_target(name, {...}) | FBO 离屏渲染 |
| 绑定纹理 | render.enable_texture(slot, handle) | Shader 采样器 |
| 切换 RT | render.set_render_target(handle) | 默认传 DEFAULT |
| 多摄像机 | 多次 set_viewport + draw | 小地图/分屏 |
| Bloom 流程 | 场景 → Bright Extract → Blur H → Blur V → Composite | Ping-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
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。