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

ThreeJS PingPong纹理渲染无报错但无画面问题求助

新版ThreeJS中PingPong纹理交换无渲染内容问题排查

问题背景

尝试用ThreeJS的PingPong纹理交换实现烟雾效果,参考旧版教程适配最新版后,代码无报错但屏幕完全无渲染内容。已完成教程第一步(纹理交换+黑渐红着色器),旧版代码可正常运行,已适配新版.render()仅接受两个参数的变化,但仍无效。

原代码

JavaScript代码

import * as THREE from 'three';
import FragmentShader from './main.frag';

var scene;
var camera;
var renderer;

function scene_setup(){
    scene = new THREE.Scene();
    var width = window.innerWidth;
    var height = window.innerHeight;
    camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 );
    camera.position.z = 2;

    renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );
}
scene_setup();

var bufferScene;
var textureA;
var textureB;
var bufferMaterial;
var plane;
var bufferObject;
var finalMaterial;
var quad;

function buffer_texture_setup(){
    bufferScene = new THREE.Scene();
    textureA = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { 
        minFilter: THREE.LinearFilter, 
        magFilter: THREE.NearestFilter
    });
    textureB = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { 
        minFilter: THREE.LinearFilter, 
        magFilter: THREE.NearestFilter
    } );

    bufferMaterial = new THREE.ShaderMaterial( {
        uniforms: {
            bufferTexture: { type: "t", value: textureA },
            res : {type: 'v2',value:new THREE.Vector2(window.innerWidth,window.innerHeight)}
        },
        fragmentShader: FragmentShader
    } );
    plane = new THREE.PlaneGeometry( window.innerWidth, window.innerHeight );
    bufferObject = new THREE.Mesh( plane, bufferMaterial );
    bufferScene.add(bufferObject);

    finalMaterial =  new THREE.MeshBasicMaterial({map: textureB});
    quad = new THREE.Mesh( plane, finalMaterial );
    scene.add(quad);
}
buffer_texture_setup();

function render() {
    requestAnimationFrame( render );

    renderer.setRenderTarget(textureB);
    renderer.clear();
    renderer.render(bufferScene,camera);
      
    var t = textureA;
    textureA = textureB;
    textureB = t;
    quad.material.map = textureB;
    bufferMaterial.uniforms.bufferTexture.value = textureA;

    renderer.render( scene, camera );
}
render();

片段着色器代码

uniform vec2 res;
uniform sampler2D bufferTexture;
void main() {
    vec2 pixel = gl_FragCoord.xy / res.xy;
    gl_FragColor = texture2D( bufferTexture, pixel );
    gl_FragColor.r += 0.01;
}

问题原因及修复方案

1. 初始纹理未初始化

textureA初始为空白纹理,第一次渲染时着色器读取无内容的纹理,后续叠加无法显示。需先渲染黑色初始帧到textureA。

2. 平面尺寸与正交相机不匹配

使用THREE.PlaneGeometry(window.innerWidth, window.innerHeight)会导致平面超出正交相机视锥体范围,应改用单位平面THREE.PlaneGeometry(2, 2),刚好铺满正交相机视口。

3. 渲染目标类型与着色器兼容性

新版ThreeJS中,WebGLRenderTarget默认类型可能截断颜色值,需明确设置type: THREE.FloatType以支持浮点颜色值叠加。

4. 纹理更新未触发材质更新

交换纹理后需设置material.needsUpdate = true,否则ThreeJS不会重新加载纹理。

5. 着色器变量兼容性

新版ThreeJS推荐使用out vec4 fragColor替代旧的gl_FragColor,提升兼容性。

修改后的代码

JavaScript代码

import * as THREE from 'three';
import FragmentShader from './main.frag';

let scene, camera, renderer;

function scene_setup() {
    scene = new THREE.Scene();
    const width = window.innerWidth;
    const height = window.innerHeight;
    camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 1000);
    camera.position.z = 2;

    renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    document.body.appendChild(renderer.domElement);
}
scene_setup();

let bufferScene, textureA, textureB, bufferMaterial, bufferObject, finalMaterial, quad;

function buffer_texture_setup() {
    bufferScene = new THREE.Scene();
    // 明确设置渲染目标类型为FloatType,支持浮点颜色值
    const renderTargetOpts = {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.NearestFilter,
        type: THREE.FloatType
    };
    textureA = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, renderTargetOpts);
    textureB = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, renderTargetOpts);

    bufferMaterial = new THREE.ShaderMaterial({
        uniforms: {
            bufferTexture: { value: textureA },
            res: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
        },
        fragmentShader: FragmentShader
    });
    // 使用单位平面,适配正交相机视口
    const plane = new THREE.PlaneGeometry(2, 2);
    bufferObject = new THREE.Mesh(plane, bufferMaterial);
    bufferScene.add(bufferObject);

    finalMaterial = new THREE.MeshBasicMaterial({ map: textureB });
    quad = new THREE.Mesh(plane, finalMaterial);
    scene.add(quad);

    // 初始化textureA为黑色
    renderer.setRenderTarget(textureA);
    renderer.clearColor(0, 0, 0, 1);
    renderer.clear();
    renderer.setRenderTarget(null);
}
buffer_texture_setup();

function render() {
    requestAnimationFrame(render);

    // 渲染到textureB
    renderer.setRenderTarget(textureB);
    renderer.clear();
    renderer.render(bufferScene, camera);

    // 交换纹理并更新材质
    [textureA, textureB] = [textureB, textureA];
    quad.material.map = textureB;
    quad.material.needsUpdate = true;
    bufferMaterial.uniforms.bufferTexture.value = textureA;
    bufferMaterial.needsUpdate = true;

    // 渲染到屏幕
    renderer.setRenderTarget(null);
    renderer.render(scene, camera);
}
render();

片段着色器代码

uniform vec2 res;
uniform sampler2D bufferTexture;
out vec4 fragColor; // 替代gl_FragColor

void main() {
    vec2 pixel = gl_FragCoord.xy / res.xy;
    fragColor = texture(bufferTexture, pixel); // 使用texture替代texture2D
    fragColor.r += 0.01;
    // 确保颜色值不超过1.0,防止溢出
    fragColor.r = min(fragColor.r, 1.0);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 03:54:58