离屏渲染
两趟渲染:第一趟画进 256×256 的帧缓冲,第二趟把该帧缓冲的颜色附件当纹理采样到屏幕。 这是后期处理、拾取与多通道合成的共同骨架。
说明
Framebuffer 把两张纹理绑成渲染目标:颜色附件负责出图,深度附件负责遮挡。
第一趟通道传 framebuffer,视口随之变成 256×256;
第二趟不传,画到画布,颜色附件即可作为普通纹理传入 bindings。
屏幕上那块四边形每帧按 CPU 算出的角度旋转,纹理内容每帧都由第一趟重新生成, 所以旋转时能看到画面是实时的离屏结果,而不是一张静态图片。
| 项目 | 取值 | 说明 |
|---|---|---|
| 离屏尺寸 | 256 × 256 | 由帧缓冲的宽高决定 |
| 颜色附件 | rgba8unorm | 用途 SAMPLE | RENDER | COPY_DST |
| 深度附件 | device.preferredDepthFormat | 用途 RENDER |
| 绘制调用 | 2 趟各 1 次 | 场景与合成各一个模型 |
| 绑定名 | uScene | 合成着色器里的 sampler2D |
| 清除 | clearColor + clearDepth | 深度每帧回到 1 |
两趟通道
const offscreen = device.beginRenderPass({
framebuffer: fbo,
clearColor: [0.02, 0.04, 0.07, 1],
clearDepth: 1
});
sceneModel.draw(offscreen);
offscreen.end();
const screen = device.beginRenderPass({ clearColor: [0.04, 0.06, 0.09, 1] });
composite.draw(screen);
screen.end();
两个通道必须先后结束,不能嵌套:end() 会恢复进入通道前的状态栈,
中间插入另一个 beginRenderPass 会让状态互相覆盖。
附件用途要一次给全
颜色纹理要同时带 SAMPLE 与 RENDER。
只给 RENDER,WebGL 后端仍可能采样成功,但 WebGPU 会直接判定不可采样,
同一份代码换后端就黑屏。用途在创建时固定,事后无法追加。
const colorTex = device.createTexture({
width: 256,
height: 256,
format: 'rgba8unorm',
usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_DST,
sampler: { minFilter: 'linear', magFilter: 'linear', mipmapFilter: 'none' }
});
const fbo = device.createFramebuffer({
width: 256,
height: 256,
colorAttachments: [colorTex],
depthStencilAttachment: depthTex
});
视口由谁决定
没给 parameters.viewport 时,通道自动取目标尺寸:
画到帧缓冲用帧缓冲的宽高,画到画布用绘制缓冲的宽高。
手工指定视口只在做分屏或小地图时才需要,写死像素值会让页面缩放后错位。
GIS 里的对应场景
拾取缓冲区是最常见的用法:第一趟把要素编号编码成颜色画进离屏纹理, 点击时按坐标回读一个像素即可知道命中了谁。 地形晕渲、阴影图、抗锯齿的历史帧也走同一条路径。
不要在同一帧里同时读写同一张纹理。 把帧缓冲的颜色附件绑定为采样源、又准备画回该帧缓冲,会形成反馈回路, 多数驱动会给出未定义结果。合成一趟必须画到另一个目标。