I am new to both ThreeJS and GLSL. I am trying to reuse the output of one fragment shader in another, and also use that same output in the same shader in the next frame. In other words, the output of the shader depends on the output of the same shader in the previous frame. I am trying to use WebGLRenderTarget to achieve that.
Here is my code:
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')
var fire = new THREE.TextureLoader().load('./images/fire_01.png')
const fireLayer = new THREE.WebGLRenderTarget(width, height)
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3(width, height, 1) },
iTexture: { value: texture},
iFire: { value: fire }
}
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,
fragmentShader: mainShader
})
material.needsUpdate = true
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,
fragmentShader: fireShader
})
material.needsUpdate = true
const plane = new THREE.Mesh(geometry, material)
scene_2.add(plane)
}
function render() {
uniforms.iTime.value = (new Date() - now) * .001
renderer.setRenderTarget(fireLayer)
renderer.render(scene_2, camera_2)
if (scene.children[0])
scene.children[0].material.uniforms.iFire.value = fireLayer.texture
renderer.setRenderTarget(null)
renderer.render(scene, camera)
}
function animate() {
render()
requestAnimationFrame(animate)
}
init()
init_2()
animate()
Updating the texture as shown above ie
scene.children[0].material.uniforms.iFire.value = fireLayer.texture
results in an illegal feedback error. When I try updating the texture using {...fireLayer.texture}
nothing gets displayed. I'm suspecting that it's because copying like that does not copy the image.
How do I correctly copy the output of a fragment shader and use it in the same shader that produced it?
If you do try to read and write to the same image, you get undefined results and an illegal feedback error. You have to use a FramebufferTexture
. Copy the output of the shader into the framebuffer texture and use this texture as input for the next frame:
const fireLayerFbCopy = new THREE.FramebufferTexture(width, height)
const fireLayer = new THREE.WebGLRenderTarget(width, height)
function render() {
uniforms.iTime.value = (new Date() - now) * .001
renderer.setRenderTarget(fireLayer)
renderer.render(scene_2, camera_2)
if (scene.children[0])
scene.children[0].material.uniforms.iFire.value = fireLayerFbCopy
renderer.copyFramebufferToTexture(new THREE.Vector2(), fireLayerFbCopy);
renderer.setRenderTarget(null)
renderer.render(scene, camera)
}