You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ThreeJS中用WebGLRenderTarget实现着色器输出递归复用?

ThreeJS着色器输出复用解决方案

问题描述

作为ThreeJS和GLSL新手,需要实现两个核心需求:

  • 将一个片段着色器的输出传递给另一个着色器使用
  • 同一着色器的下一帧能复用自身前一帧的输出(当前帧渲染依赖前一帧结果)

尝试用WebGLRenderTarget实现时遇到两个问题:

  • 直接执行scene.children[0].material.uniforms.iFire.value = fireLayer.texture触发非法反馈错误,因为WebGL禁止同时将一个纹理作为渲染目标和采样源
  • 使用{...fireLayer.texture}浅拷贝纹理后无内容显示,这种方式无法复制纹理的图像数据

解决方案

核心思路是使用两个WebGLRenderTarget交替读写,避免同一帧内对同一个纹理同时进行读写操作,具体步骤如下:

1. 创建双渲染目标

创建两个规格完全一致的WebGLRenderTarget,用于交替存储上一帧的渲染结果,一个作为当前帧的输入(读),另一个作为当前帧的输出(写)。

2. 交替渲染与赋值

在每一帧的渲染循环中:

  • 先将着色器渲染到写目标
  • 将读目标的纹理赋值给需要复用的uniform(包括当前着色器自身下一帧的输入)
  • 交换两个目标的读写角色,为下一帧做准备

3. 修正uniform共享问题

避免两个着色器共享同一套uniform对象,防止互相干扰,应为每个着色器单独创建uniform(可以复用基础值)。

修改后的完整代码

import * as THREE from "three"

var camera, camera_2, fireShader, mainShader, renderer, scene, scene_2
const width = 1200
const height = 720
const texture = new THREE.TextureLoader().load('./images/00_map.png')
const initialFire = new THREE.TextureLoader().load('./images/fire_01.png')

// 创建两个交替使用的渲染目标
const fireLayer1 = new THREE.WebGLRenderTarget(width, height)
const fireLayer2 = new THREE.WebGLRenderTarget(width, height)
// 初始化读写目标,初始读目标用初始纹理
let currentReadTarget = { texture: initialFire }
let currentWriteTarget = fireLayer1

// 为两个着色器分别创建uniform,避免共享干扰
const mainUniforms = {
    iTime: { value: 0 },
    iResolution: { value: new THREE.Vector3(width, height, 1) },
    iTexture: { value: texture},
    iFire: { value: initialFire }
}

const fireUniforms = {
    iTime: { value: 0 },
    iResolution: { value: new THREE.Vector3(width, height, 1) },
    iFire: { value: initialFire } // 这里传入上一帧的结果
}

const now = new Date()

async function init() {
    const stage = document.getElementById('stage')
    renderer = new THREE.WebGLRenderer({antialias: true})
    scene = new THREE.Scene()
    camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0.1, 20)
    camera.position.z = 5

    renderer.setSize(width, height)
    stage.appendChild(renderer.domElement)

    mainShader = await (await fetch('shaders/frag.glsl')).text()
    
    const geometry = new THREE.PlaneGeometry(width, height)    
    const material = new THREE.ShaderMaterial({
        uniforms: mainUniforms,
        fragmentShader: mainShader
    })

    const plane = new THREE.Mesh(geometry, material)
    scene.add(plane)    
}

async function init_2 () {
    scene_2 = new THREE.Scene()
    camera_2 = new THREE.OrthographicCamera(width / -2, width / 2, height / -2, height / -2, 0.1, 20)
    camera_2.position.z = 5

    fireShader = await (await fetch('shaders/fire.glsl')).text()
    
    const geometry = new THREE.PlaneGeometry(width, height)    
    const material = new THREE.ShaderMaterial({
        uniforms: fireUniforms,
        fragmentShader: fireShader
    })
    const plane = new THREE.Mesh(geometry, material)
    scene_2.add(plane)   
}

function render() {
    const elapsedTime = (new Date() - now) * .001
    mainUniforms.iTime.value = elapsedTime
    fireUniforms.iTime.value = elapsedTime

    // 1. 将fireShader渲染到当前写目标
    renderer.setRenderTarget(currentWriteTarget)
    // 给fireShader传入上一帧的结果(读目标的纹理)
    fireUniforms.iFire.value = currentReadTarget.texture
    renderer.render(scene_2, camera_2)

    // 2. 将最新的结果传递给mainShader
    mainUniforms.iFire.value = currentWriteTarget.texture

    // 3. 交换读写目标,为下一帧准备
    const temp = currentReadTarget
    currentReadTarget = currentWriteTarget
    currentWriteTarget = temp === fireLayer1 ? fireLayer2 : fireLayer1

    // 4. 渲染主场景到屏幕
    renderer.setRenderTarget(null)
    renderer.render(scene, camera)
}

function animate() {    
    render()
    requestAnimationFrame(animate)
}

init()
init_2()
animate()

关键修改说明

  • 双RenderTarget交替:用fireLayer1和fireLayer2交替存储帧数据,避免同一帧内读写冲突
  • 独立uniform对象:为mainShader和fireShader分别创建uniform,防止赋值互相覆盖
  • 帧数据传递逻辑:每一帧先把上一帧的结果(读目标)传给fireShader,渲染后将新结果(写目标)传给mainShader,最后交换目标角色

内容的提问来源于stack exchange,提问作者murume

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 15:47:23