SpriteJS 3D 渲染对象(RenderTarget)实战:FrameBuffer 离屏渲染、纹理回读与双缓冲复用

发布时间:2026/10/13 1:43:13
SpriteJS 3D 渲染对象(RenderTarget)实战:FrameBuffer 离屏渲染、纹理回读与双缓冲复用 图形学3D渲染【免费下载链接】spritejsA cross platform high-performance graphics system.项目地址https://gitcode.com/gh_mirrors/sp/spritejs点击查看免费下载导读在 SpriteJS 3D 扩展中RenderTarget是一块可绘制的 FrameBuffer用于把 3D 场景先渲染到离屏缓冲再以纹理的形式贴回屏幕从而支撑「渲染结果作为输入再次渲染」这类多次渲染需求。本文以 docs/zh-cn/ext3d/render-target.md 为核心结合仓库内可运行的 demo 与 API 文档系统讲解 RenderTarget 的构造参数、layer.renderTarget调用方式、多 Target 叠加、双缓冲复用以及autoClear控制缓冲区保留等完整实战方案。为什么需要渲染对象RenderTarget在普通的 3D 绘制流程里场景内容直接被渲染到屏幕上。但有些时候我们需要多次渲染例如先把一些内容渲染到 FrameBuffer 中然后作为纹理输出到屏幕将前一步的渲染结果当作下一步的输入形成链式/迭代处理如后期处理、GPGPU 流体模拟。在 SpriteJS 的 3D 扩展sprite-extend-3d中这个需求由RenderTarget对象解决——它定义了一个用于将元素绘制到 FrameBuffer 的对象见 docs/zh-cn/api/ext3d/render-target.md。RenderTarget 继承自Group3d因此它本身就是一个 3D 分组节点可以像普通 Group 一样append子元素、拥有自己的相机。使用前请确保已按 docs/zh-cn/ext3d/index.md 的说明加载sprite-extend-3d在spritejs之后引入即可随后通过spritejs.ext3d.RenderTarget访问该 API。快速上手把场景渲染到 FrameBuffer 再贴回屏幕下面这段完整示例与 demos/3d/render_target/index.js 一致展示了 RenderTarget 最核心的用法一个立方体mesh被渲染到target的 FrameBuffer 上然后target.texture作为另一个立方体mesh2的 Program 纹理最终mesh2输出到屏幕。const vertex /* glsl */ precision highp float; precision highp int; attribute vec3 position; attribute vec3 normal; attribute vec2 uv; uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; uniform mat3 normalMatrix; varying vec2 vUv; varying vec3 vNormal; void main() { vUv uv; vNormal normalize(normalMatrix * normal); gl_Position projectionMatrix * modelViewMatrix * vec4(position, 1.0); } ; const fragment /* glsl */ precision highp float; precision highp int; uniform sampler2D tMap; varying vec2 vUv; varying vec3 vNormal; void main() { vec3 normal normalize(vNormal); float lighting 0.2 * dot(normal, normalize(vec3(-0.3, 0.8, 0.6))); vec3 tex texture2D(tMap, vUv).rgb; gl_FragColor.rgb tex lighting vec3(vUv - 0.5, 0.0) * 0.1; gl_FragColor.a 1.0; } ; const {Scene} spritejs; const {Cube, RenderTarget} spritejs.ext3d; const container document.getElementById(container); const scene new Scene({ container, displayRatio: 2, }); const layer scene.layer3d(fglayer, { camera: { fov: 35, }, }); layer.camera.attributes.pos [0, 1, 5]; layer.camera.lookAt([0, 0, 0]); const texture layer.createTexture({ image: new Uint8Array([ 191, 25, 54, 255, 96, 18, 54, 255, 96, 18, 54, 255, 37, 13, 53, 255, ]), width: 2, height: 2, magFilter: layer.gl.NEAREST, }); const program layer.createProgram({ vertex, fragment, texture, }); const target new RenderTarget(layer.gl, { width: 512, height: 512, camera: { fov: 35, }, }); target.camera.attributes.pos [0, 1, 5]; target.camera.lookAt([0, 0, 0]); const mesh new Cube(program); target.append(mesh); const targetProgram layer.createProgram({ vertex, fragment, texture: target.texture, }); target.addEventListener(beforerender, (e) { layer.gl.clearColor(0.15, 0.05, 0.2, 1); }); target.addEventListener(afterrender, (e) { layer.gl.clearColor(1, 1, 1, 0); }); const mesh2 new Cube(targetProgram); layer.append(mesh2); layer.tick(() { layer.renderTarget(target); mesh.attributes.rotateY - 1.5; mesh2.attributes.rotateY - 0.9; mesh2.attributes.rotateX - 1; });拆解这段代码的完整流程定义着色器顶点着色器做常规的modelViewMatrix/projectionMatrix/normalMatrix变换片元着色器通过uniform sampler2D tMap采样纹理并叠加一个简单的方向光计算。创建 3D 层与相机scene.layer3d(fglayer, {camera: {fov: 35}})创建一个带透视相机的 3D 层layer.camera与target.camera分别设置位置[0, 1, 5]并lookAt([0, 0, 0])——RenderTarget 拥有独立的相机可以看作一个迷你 3D 场景。创建 RenderTargetnew RenderTarget(layer.gl, {width: 512, height: 512, camera: {fov: 35}})在离屏开辟一块 512×512 的 FrameBuffer。把元素挂到 target 上target.append(mesh)这与把元素append到 layer 上的方式完全一致。回读纹理通过target.texture只读属性返回渲染后的纹理数据创建第二个 ProgramtargetProgram用它再生成一个立方体mesh2并layer.append(mesh2)。渲染到离屏缓冲在layer.tick中调用layer.renderTarget(target)先执行 target 上元素的渲染beforerender/afterrender事件用来在渲染前后修改clearColor如渲染时清为深紫色(0.15, 0.05, 0.2)渲染结束后恢复为白色透明(1, 1, 1, 0)。驱动动画每帧分别旋转两个立方体即可看到mesh2表面显示的是mesh渲染到 FrameBuffer 中的画面。RenderTarget 构造参数详解根据 docs/zh-cn/api/ext3d/render-target.mdnew RenderTarget(gl, attrs)支持的配置项及默认值如下配置项默认值说明widthgl.canvas.width离屏缓冲宽度像素heightgl.canvas.height离屏缓冲高度像素targetgl.FRAMEBUFFER绑定的帧缓冲类型color1颜色附件的数量depthtrue是否启用深度缓冲stencilfalse是否启用模板缓冲depthTexturefalse是否使用深度纹理注意开启后与 stencil 不兼容即「stencil breaks」wrapSgl.CLAMP_TO_EDGE水平方向纹理环绕方式wrapTgl.CLAMP_TO_EDGE垂直方向纹理环绕方式minFiltergl.LINEAR纹理缩小过滤方式magFilterminFilter纹理放大过滤方式默认跟随 minFiltertypegl.UNSIGNED_BYTE纹理数据类型formatgl.RGBA纹理格式internalFormatformat内部存储格式默认跟随 formatunpackAlignment—像素数据行对齐方式premultiplyAlpha—是否预乘 AlphacameracameraOptions该 RenderTarget 使用的相机配置bufferfalse是否启用双缓冲true时可用swap()交换缓冲实用建议需要精确纹理采样时可像示例一样指定magFilter: layer.gl.NEAREST避免放大时的线性插值模糊涉及深度测试的 3D 内容要保持depth: true只做 2D 后处理或 GPGPU 计算时可设depth: false以节省开销高精度浮点模拟如流体可将type设为半浮点WebGL2 下为gl.HALF_FLOAT并显式指定format/internalFormat参见下文「源码佐证」一节的demos/3d/post/index.js。作为Group3d的子类RenderTarget 同样继承Node3d的位置/旋转/缩放属性pos、rotate、scale等与 Group 的子元素管理能力也拥有readonly texture属性。核心方法包括renderBy(layer, {root this, ...options} {})让 RenderTarget 实例被 layer 渲染swap()当构造时设置了buffer后交换当前缓冲与目标缓冲。layer.renderTarget把内容绘制到目标缓冲区layer.renderTarget(target, options)是Layer3d提供的方法用于渲染指定的 RenderTarget 对象参见 docs/zh-cn/api/ext3d/layer3d.md。与bindTarget的「持续绑定」不同renderTarget通常配合tick在每帧显式调用用于一次性绘制某个目标。它支持传入渲染选项在 demos/3d/post/index.js 的流体模拟中可以看到实际用法layer.renderTarget(velocity, { root: splatProgram, sort: false, update: false, });其中root本次渲染的根节点缺省时渲染 RenderTarget 自身挂载的子元素。传入外部节点即可实现「不把 root 添加到 renderTarget 上针对不同 renderTarget 复用同一个 root」sort是否对网格排序计算类渲染如后处理可设为false跳过排序update是否更新网格数据固定几何如全屏三角形可设为false跳过更新以提升性能。底层上这些选项与Layer3d.setRenderOptions(opts)的配置update true、sort true、frustumCull true、clear对应均来自 docs/zh-cn/api/ext3d/layer3d.md。多次渲染的三种实现方案「渲染结果作为纹理继续参与渲染」是 RenderTarget 最常见的进阶场景。官方文档给出了三种逐步递进的方案。方案一创建多个 RenderTarget 叠加当需要多道处理时最直接的做法是创建多个 RenderTarget把同一个 root 复用到不同目标上再读取各自的textureconst target1 new RenderTarget(layer.gl, { width: 512, height: 512, camera: { fov: 35, }, }); const target2 new RenderTarget(layer.gl, { width: 512, height: 512, camera: { fov: 35, }, }); const target3 new RenderTarget(layer.gl, { width: 512, height: 512, camera: { fov: 35, }, }); ... const root new Mesh3d(...); layer.tick(() { // 我们可以不将root添加到renderTarget上这样就可以针对不同的renderTarget复用root layer.renderTarget(target1, {root}); root.program.uniforms.tMap.value target1.texture; layer.renderTarget(target2, {root}); root.program.uniforms.tMap.value target2.texture; layer.renderTarget(target3, {root}); root.program.uniforms.tMap.value target2.texture; ... });注意两个关键点root 不挂载到 RenderTarget 上而是通过layer.renderTarget(target, {root})临时指定这样同一个root可以在不同 target 之间复用纹理通过 uniform 切换每轮渲染前把root.program.uniforms.tMap.value指向上一个 target 的texture实现结果链式传递。这种方案的缺点是处理 N 次就要创建 N 个 RenderTargetFrameBuffer 与纹理都占用显存比较消耗内存。方案二两个 target 依次交换如果处理是反复迭代的其实只需要两个target依次交换使用即可const root new Mesh3d(...); layer.tick(() { // 我们可以不将root添加到renderTarget上这样就可以针对不同的renderTarget复用root layer.renderTarget(target1, {root}); root.program.uniforms.tMap.value target1.texture; layer.renderTarget(target2, {root}); root.program.uniforms.tMap.value target2.texture; layer.renderTarget(target1, {root}); ... });target1 → target2 → target1 → ...交替渲染每次读取的是上一轮写出的结果从而把内存占用从 N 个 target 降到 2 个。方案三双缓冲 buffer: true swap()更简洁的做法是创建 RenderTarget 时传入配置项buffer: true启用双缓冲这样连第二个 target 都不需要代码简化为const root new Mesh3d(...); layer.tick(() { // 我们可以不将root添加到renderTarget上这样就可以针对不同的renderTarget复用root layer.renderTarget(target, {root}); root.program.uniforms.tMap.value target.texture; target.swap(); layer.renderTarget(target, {root}); root.program.uniforms.tMap.value target.texture; target.swap(); layer.renderTarget(target, {root}); ... });target.swap()交换缓冲与目标渲染时写入「背面缓冲」swap后再读到的target.texture就是刚渲染完的那一帧天然规避了「一边读一边写同一块缓冲」的冲突。保留缓冲区内容layer.autoClear默认情况下每次执行layer.renderTarget都会清空之前缓冲区中的内容clearColor默认由beforerender事件控制。如果不希望清空可以把layer.autoClear设为false这样每次渲染会保留缓冲区中已有的内容新内容直接叠加其上layer.autoClear false; // 执行多次 renderTarget缓冲区内容不丢失 layer.renderTarget(target, {root}); ... layer.autoClear true; // 记得恢复Layer3d的autoClear属性默认true含义即「绘制前是否自动清除 layer 或 FrameBuffer」见 docs/zh-cn/api/ext3d/layer3d.md。在迭代式渲染中关闭它还能省去大量gl.clear调用提升性能。源码佐证流体模拟中的 RenderTarget 链式用法仓库内的 demos/3d/post/index.js对应文档 docs/zh-cn/ext3d/post.md是一个把 RenderTarget 用到极致的 GPGPU 流体模拟示例与本文主题高度相关可以作为进阶参考多个 RenderTarget 分工分别创建density染料密度、velocity速度场、pressure压强、divergence散度、curl旋度等模拟场尺寸和格式各不相同如RGBA16F、RG16F、R16F并通过supportRenderTextureFormat检测设备对渲染纹理格式的支持情况半浮点纹理在 WebGL2 下使用gl.HALF_FLOAT与浮点内部格式模拟场因此拥有比 8bit 更高的数值精度buffer swap 迭代velocity、density、pressure均配置buffer: true每完成一步模拟就swap()一次将上一步结果作为下一步输入splat、advection、divergence、pressure、gradient subtract 等多道 Pass 依次执行关闭自动清屏layer.autoClear false让模拟过程不再清除缓冲区每帧结束时再恢复为true全屏三角形复用所有模拟 Pass 共用一个由Geometry构造的全屏三角形网格配合sort: false, update: false提升渲染效率。这套写法正是「多个 RenderTarget 双缓冲 swap autoClear 控制」三种方案在生产级场景中的综合应用也印证了本文上述各节所述机制。小结与延伸阅读RenderTarget 让 SpriteJS 3D 扩展具备了完整的离屏渲染能力离屏绘制new RenderTarget(gl, attrs)创建 FrameBuffer元素照常append用layer.renderTarget(target)触发渲染结果回读target.texture作为 Program 的 texture 参与下一次渲染多次处理优先考虑buffer: trueswap()的双缓冲方案其次是两个 target 交换最后才用多 target 叠加内容保留layer.autoClear false控制渲染前是否清空缓冲区。如需进一步深入可在仓库中继续查阅RenderTarget 完整 API 参考构造参数、属性、方法全表Layer3d API 参考renderTarget、bindTarget、autoClear、setRenderOptions等3D 扩展总览与安装方式后期处理通道指南layer.post与 RenderTarget 的关系官方指出后期处理本质上就是通过多个 RenderTarget 实现可运行示例demos/3d/render_target/index.js 与 demos/3d/post/index.js赞分享图形学3D渲染【免费下载链接】spritejsA cross platform high-performance graphics system.项目地址https://gitcode.com/gh_mirrors/sp/spritejs点击查看免费下载相关推荐Node Editor Framework核心组件解析Node、Canvas、ConnectionPort深度剖析Node Editor Framework核心组件解析Node、Canvas、ConnectionPort深度剖析 Node Editor Framework如何快速上手 CrossPoint Reader从刷机到阅读的完整教程如何快速上手 CrossPoint Reader从刷机到阅读的完整教程 想要为你的 Xteink X3/X4 电纸书刷入开源固件 CrossPoint Rea如何使用WaybackProxy在5分钟内访问历史网页简单教程与配置指南如何使用WaybackProxy在5分钟内访问历史网页简单教程与配置指南 WaybackProxy是一款强大的HTTP代理工具能够通过互联网档案馆Wayba上一篇如何快速掌握Pyfa5个提升EVE Online舰船配置效率的终极技巧下一篇AutoActions 开源项目教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询