three EffectComposer 效果合成器

387 阅读7分钟

EffectComposer 是 Three.js 中用于后处理(Post-processing)的一个类。它允许你在渲染完成之后对图像进行进一步的处理,例如添加特效、模糊、深度效果、色调映射等,进而实现更加丰富的视觉效果。
EffectComposer 相关的 Pass 是 Three.js 后期处理(Post-processing)系统的一部分,下面是一些常见的 Pass,它们主要用于在渲染结果上应用各种效果。你可以通过组合这些 Pass 来实现复杂的后期处理效果。

EffectComposer 有五个属性,十个方法

EffectComposer( renderer : WebGLRenderer, renderTarget : WebGLRenderTarget )
    renderer -- 用于渲染场景的渲染器。
    renderTarget -- (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。
    
    <template>
    <div id="parkingLot" ref="parkingLot">
    </div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import * as THREE from 'three';
import TWEEN from 'three/addons/libs/tween.module.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { RenderTransitionPass } from 'three/addons/postprocessing/RenderTransitionPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
const parkingLot = ref();

onMounted(async () => {
    const DOMEl = parkingLot.value;
    // 获取 DOMEl 的宽度和高度,以设置渲染器的大小。
    const width = DOMEl.clientWidth;
    const height = DOMEl.clientHeight;
    const renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );  // 设置渲染器的像素比,适应设备屏幕的分辨率
    renderer.setSize( width, height );  // 设置渲染器的大小
    DOMEl.appendChild( renderer.domElement );  // 将渲染器的DOM元素添加到指定的容器中
    const textures = [];  // 存储纹理的数组
    const clock = new THREE.Clock();  // 创建一个时钟,用于跟踪时间

    const initTextures  =  async function () {
        const loader = new THREE.TextureLoader();  // 创建纹理加载器
        for ( let i = 0; i < 6; i++ ) {
            console.log(i,"i")
            // 加载6张过渡纹理
            textures[i] = await loader.load( 'https://raw.githubusercontent.com/mrdoob/three.js/refs/heads/master/examples/textures/transition/transition' + ( i + 1 ) + '.png' );
        }
    }

    initTextures();

    

    // 用于控制过渡动画的参数
    const params = {
        transition: 0,  // 当前过渡状态
        texture: 5,  // 当前纹理索引
        cycle: true,  // 是否循环切换纹理
        threshold: 0.1,  // 过渡阈值
        sceneAnimate: true,
        transitionAnimate: true,
        useTexture: true
    };
    const gui = new GUI();

    gui.add( params, 'sceneAnimate' ).name( 'Animate scene' ); // 设置属性名称
    gui.add( params, 'transitionAnimate' ).name( 'Animate transition' );// 设置属名称
    gui.add( params, 'transition', 0, 1, 0.01 ).onChange( function ( value ) { // 监听 过渡值变化事件

        renderTransitionPass.setTransition( value );

    } ).listen();

    gui.add( params, 'useTexture' ).onChange( function ( value ) { // 监听使用纹理事件

        renderTransitionPass.useTexture( value );

    } );

    gui.add( params, 'texture', { Perlin: 0, Squares: 1, Cells: 2, Distort: 3, Gradient: 4, Radial: 5 } ).onChange( function ( value ) { // 监听纹理变化事件

        renderTransitionPass.setTexture( textures[ value ] );

    } ).listen();

    gui.add( params, 'cycle' ); // 添加

    gui.add( params, 'threshold', 0, 1, 0.01 ).onChange( function ( value ) {

        renderTransitionPass.setTextureThreshold( value );

    } );


    // 创建两个FX场景,用于过渡效果的显示
    const fxSceneA = new FXScene( new THREE.BoxGeometry( 2, 2, 2 ), new THREE.Vector3( 0, - 0.4, 0 ), 0xffffff );
    const fxSceneB = new FXScene( new THREE.IcosahedronGeometry( 1, 1 ), new THREE.Vector3( 0, 0.2, 0.1 ), 0x000000 );

    // 设置动画循环
    renderer.setAnimationLoop( animate );

    // 创建一个后处理合成器
    const composer = new EffectComposer( renderer );

    // 创建渲染过渡效果的Pass
    const renderTransitionPass = new RenderTransitionPass( fxSceneA.scene, fxSceneA.camera, fxSceneB.scene, fxSceneB.camera );
    renderTransitionPass.setTexture( textures[ 0 ] );  // 设置初始纹理
    composer.addPass( renderTransitionPass );

    // 创建输出Pass
    const outputPass = new OutputPass();
    composer.addPass( outputPass );

    // 设置过渡动画
    new TWEEN.Tween( params )
        .to( { transition: 1 }, 1500 )  // 设置过渡动画的目标值和持续时间
        .onUpdate( function () {
            renderTransitionPass.setTransition( params.transition );  // 更新过渡状态

            // 每次过渡后切换当前的纹理
            if ( params.cycle ) {
                if ( params.transition == 0 || params.transition == 1 ) {
                    // 切换到下一个纹理
                    params.texture = ( params.texture + 1 ) % textures.length;
                    renderTransitionPass.setTexture( textures[ params.texture ] );
                }
            }
        } )
        .repeat( Infinity )  // 无限循环
        .delay( 2000 )  // 延迟开始
        .yoyo( true )  // 启用往返动画效果
        .start();

    function animate() {
        const delta = clock.getDelta();  // 获取每帧的时间间隔
        fxSceneA.update( delta );  // 更新场景A
        fxSceneB.update( delta );  // 更新场景B
        render();  // 渲染当前场景
    }


    function render() {
        console.log(params,"params")
        // 根据过渡状态渲染场景
        if ( params.transition === 0 ) {
            renderer.render( fxSceneB.scene, fxSceneB.camera );  // 渲染场景B
        } else if ( params.transition === 1 ) {
            renderer.render( fxSceneA.scene, fxSceneA.camera );  // 渲染场景A
        } else {
            // 当过渡值在 0 到 1 之间时,渲染过渡效果
            composer.render();
        }
    }

    // 定义FX场景构造函数
    function FXScene( geometry, rotationSpeed, backgroundColor ) {
        const camera = new THREE.PerspectiveCamera( 50, width / height, 0.1, 100 );  // 创建透视相机
        camera.position.z = 20;  // 设置相机位置

        // 初始化场景
        const scene = new THREE.Scene();
        scene.background = new THREE.Color( backgroundColor );  // 设置背景颜色
        scene.add( new THREE.AmbientLight( 0xaaaaaa, 3 ) );  // 添加环境光

        // 创建一个定向光源
        const light = new THREE.DirectionalLight( 0xffffff, 3 );
        light.position.set( 0, 1, 4 );
        scene.add( light );

        this.rotationSpeed = rotationSpeed;

        // 根据几何体类型选择颜色
        const color = geometry.type === 'BoxGeometry' ? 0x0000ff : 0xff0000;
        const material = new THREE.MeshPhongMaterial( { color: color, flatShading: true } );  // 创建Phong材质
        const mesh = generateInstancedMesh( geometry, material, 500 );  // 创建实例化网格
        scene.add( mesh );

        this.scene = scene;
        this.camera = camera;
        this.mesh = mesh;

        // 更新场景中物体的旋转
        this.update = function ( delta ) {
            mesh.rotation.x += this.rotationSpeed.x * delta;
            mesh.rotation.y += this.rotationSpeed.y * delta;
            mesh.rotation.z += this.rotationSpeed.z * delta;
        };

        // 移除了resize处理
        this.resize = function () {
            // 如果需要处理resize事件,可以在这里添加
            camera.aspect = width / height;
            camera.updateProjectionMatrix();
        };
    }

    // 创建实例化网格的函数
    function generateInstancedMesh( geometry, material, count ) {
        const mesh = new THREE.InstancedMesh( geometry, material, count );  // 创建实例化网格

        const dummy = new THREE.Object3D();  // 创建一个空的Object3D对象,用于设置每个实例的位置、旋转和缩放
        const color = new THREE.Color();

        for ( let i = 0; i < count; i++ ) {
            dummy.position.x = Math.random() * 100 - 50;  // 随机位置
            dummy.position.y = Math.random() * 60 - 30;
            dummy.position.z = Math.random() * 80 - 40;

            dummy.rotation.x = Math.random() * 2 * Math.PI;  // 随机旋转
            dummy.rotation.y = Math.random() * 2 * Math.PI;
            dummy.rotation.z = Math.random() * 2 * Math.PI;

            dummy.scale.x = Math.random() * 2 + 1;  // 随机缩放

            if ( geometry.type === 'BoxGeometry' ) {
                dummy.scale.y = Math.random() * 2 + 1;
                dummy.scale.z = Math.random() * 2 + 1;
            } else {
                dummy.scale.y = dummy.scale.x;
                dummy.scale.z = dummy.scale.x;
            }

            dummy.updateMatrix();  // 更新矩阵

            mesh.setMatrixAt( i, dummy.matrix );  // 设置实例的矩阵
            mesh.setColorAt( i, color.setScalar( 0.1 + 0.9 * Math.random() ) );  // 设置每个实例的颜色
        }

        return mesh;  // 返回生成的实例化网格
    }

});

</script>
<style lang="scss" scoped="scoped">

    #parkingLot {
        width: 940px;
        height: 940px;
        border: 1px solid #ccc;
        margin: 30px auto;
    }

</style>

属性

  • passes : Array 在代码中,passesEffectComposer 类的一个属性,它是一个包含所有渲染过程的数组。每个渲染过程被封装为一个 Pass(通道),用于在渲染过程中执行一系列图像效果或后期处理操作。在 EffectComposer 中,addPass() 用于将不同的 passes 加入到渲染管线,最终通过 composer.render() 渲染所有效果。
  • readBuffer : WebGLRenderTarget 在 Three.js 中,readBuffer 是一个 WebGLRenderTarget 类型的对象,用于指定渲染的输入目标。它通常在后期处理或者渲染到纹理时使用,尤其是在使用 EffectComposerPass 时。
    // 图像效果链:每个 pass 都可以读取上一个 pass 的渲染结果,进行处理后再输出到 writeBuffer,或者直接将结果呈现到屏幕。
    // 后期处理:在一些特殊的后期效果中,比如模糊、颜色校正等,可以从 readBuffer 中获取当前帧的图像数据进行处理。
    const composer = new EffectComposer( renderer );
    // 创建渲染目标
    const readBuffer = new THREE.WebGLRenderTarget( width, height );
    // 用于后期效果的 pass
    const renderPass = new RenderPass( scene, camera );
    composer.addPass( renderPass );
    // 自定义的 pass,需要读取和写入纹理
    const customPass = new CustomPass();
    customPass.readBuffer = readBuffer;  // 指定读的渲染目标
    composer.addPass( customPass );
    // 渲染效果链
    composer.render();
  • renderer : WebGLRenderer 内部渲染器的引用。
  • renderToScreen : Boolean 在 Three.js 中,renderToScreen 是一个布尔值(Boolean)属性,用于指定渲染结果是否直接显示到屏幕上。
    // renderToScreen = true: 将渲染结果直接显示到屏幕上。这意味着,当前的渲染结果将被最终输出,通常在渲染管线的最后一个 pass 中使用。
    // renderToScreen = false: 渲染结果不会直接显示到屏幕上。相反,渲染结果可能会被用于后续的图像处理或写入到纹理中,用于进一步的效果(比如模糊、颜色校正等)。
    const composer = new EffectComposer( renderer );
    // 渲染场景并应用后期效果
    const renderPass = new RenderPass( scene, camera );
    composer.addPass( renderPass );
    // 自定义的后期效果 pass
    const customPass = new CustomPass();
    customPass.renderToScreen = true;  // 指定将结果渲染到屏幕
    composer.addPass( customPass );
    // 渲染效果链
    composer.render();
  • writeBuffer : WebGLRenderTarget 在 Three.js 中,writeBuffer 是一个 WebGLRenderTarget 对象,通常用作渲染操作的输出目标。它用于存储从当前 pass 渲染产生的结果,通常是在后期处理链中使用,尤其是在 EffectComposer 和自定义 Pass 中。
    // writeBuffer 指定了当前 pass 渲染结果的输出目标。它通常是一个 WebGLRenderTarget,表示一个渲染目标纹理或帧缓冲区。
    // 渲染结果会写入到 writeBuffer 中,然后这个缓冲区的内容可以被用作后续 pass 的输入(通过 readBuffer)。
    const composer = new EffectComposer( renderer );
    // 创建渲染目标
    const writeBuffer = new THREE.WebGLRenderTarget( width, height );
    // 渲染场景并应用后期效果
    const renderPass = new RenderPass( scene, camera );
    composer.addPass( renderPass );
    // 自定义的后期效果 pass
    const customPass = new CustomPass();
    customPass.writeBuffer = writeBuffer;  // 指定渲染结果输出的目标
    composer.addPass( customPass );
    // 渲染效果链
    composer.render();

方法

  • addPass ( pass : Pass ) : undefined pass -- 将被添加到过程链的过程 将传入的过程添加到过程链。
  • dispose () : undefined 销毁
  • insertPass ( pass : Pass, index : Integer ) : undefined pass -- 将被插入到过程链的过程。 index -- 定义过程链中过程应插入的位置。 将传入的过程插入到过程链中所给定的索引处。
  • isLastEnabledPass ( passIndex : Integer ) : Boolean 在 Three.js 中,isLastEnabledPass(passIndex: Integer): Boolean 方法用于判断指定索引的 pass 是否为渲染链中的最后一个启用的 pass。这个方法通常用于处理复杂的后期效果或渲染管线,特别是在你需要知道当前 pass 是否是最后一个渲染步骤时。
    const composer = new THREE.EffectComposer(renderer);
    // 定义一些 pass
    const renderPass = new THREE.RenderPass(scene, camera);
    composer.addPass(renderPass);
    const blurPass = new THREE.ShaderPass(blurShader);
    composer.addPass(blurPass);
    const finalPass = new THREE.ShaderPass(finalShader);
    composer.addPass(finalPass);
    // 检查 finalPass 是否是最后一个启用的 pass
    if (composer.isLastEnabledPass(2)) {
        console.log("finalPass 是最后一个启用的 pass。");
    } else {
        console.log("finalPass 不是最后一个启用的 pass。");
    }
    // 渲染场景
    composer.render();
  • removePass ( pass : Pass ) : undefined 与 addPass 相反
  • render ( deltaTime : Float ) : undefined 执行所有启用的后期处理过程,来产生最终的帧,
  • reset ( renderTarget : WebGLRenderTarget ) : undefined renderTarget -- (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。 重置所有EffectComposer的内部状态。
  • setPixelRatio ( pixelRatio : Float ) : undefined pixelRatio -- 设备像素比 设置设备的像素比。该值通常被用于HiDPI设备,以阻止模糊的输出。 因此,该方法语义类似于WebGLRenderer.setPixelRatio()。
  • setSize ( width : Integer, height : Integer ) : undefined width -- EffectComposer的宽度。 height -- EffectComposer的高度。 考虑设备像素比,重新设置内部渲染缓冲和过程的大小为(width, height)。 因此,该方法语义类似于WebGLRenderer.setSize()。
  • swapBuffers () : undefined 交换内部的读/写缓冲。