EffectComposer + ShaderPass 实现分区特效

一、实现步骤

EffectComposer + Layers 实现分区特效中,EffectComposer更像是渲染出一张有辉光特效的“背景图”,renderer在这张“背景图”上渲染正常方块,以至于无论相机在哪个角度,都看到正常方块绘制在发光方块之上。针对这个问题,参考threejs提供的案例,使用ShaderPass实现分区辉光效果。步骤如下:

1、创建两个效果组合器bloomComposer和finalComposer,其中bloomComposer用来生成辉光效果,并将其渲染的结果作为着色器材质的输入,传递给finalComposer。finalComposer则用来渲染整个场景,在材质通道ShaderPass中将辉光效果整合到scene内。

const initComposer = () => {
    bloomComposer = new EffectComposer(renderer);
    bloomComposer.renderToScreen = false;

    const renderScene = new RenderPass(scene, camera);
    // 光晕
    const bloomPass = new UnrealBloomPass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        1.5,
        0.4,
        0.85
    );
    bloomPass.threshold = params.bloomThreshold;
    bloomPass.strength = params.bloomStrength;
    bloomPass.radius = params.bloomRadius;
    bloomComposer.addPass(renderScene);
    bloomComposer.addPass(bloomPass);

    finalComposer = new EffectComposer(renderer);
    const finalShader = new THREE.ShaderMaterial({
        uniforms: {
            baseTexture: { value: null },
            bloomTexture: { value: bloomComposer.renderTarget2.texture }
        },
        vertexShader: `
            varying vec2 vUv;
            void main() {
                vUv = uv;
                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            }
        `,
        fragmentShader: `
            uniform sampler2D baseTexture;
            uniform sampler2D bloomTexture;
            varying vec2 vUv;
            void main() {
                gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
            }
        `,
        defines: {}
    });
    const finalPass = new ShaderPass(finalShader, "baseTexture");
    finalPass.needsSwap = true;
    finalComposer.addPass(renderScene);
    finalComposer.addPass(finalPass);
};

2、创建正常方块和发光方块,并为它们设置不同的渲染图层,在渲染的过程钟将相机设置到对应图层进行渲染。

也可以像threejs案例中那样,在渲染发光物体时将非发光物体设置为黑色,这种方式对辉光效果有作用,因为黑色的物体不会产生辉光效果。对其他后处理效果就不一定适用了。

3、先用bloomComposer渲染出辉光效果,再使用finalComposer渲染场景。

const render = () => {
    renderer.autoClear = false;
    renderer.clear();

    camera.layers.set(1);
    bloomComposer.render();

    renderer.clearDepth(); // 清除深度缓存

    camera.layers.set(0);
    finalComposer.render(scene, camera);
    
    requestAnimationFrame(render);
};

 

二、效果

 

三、完整代码

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <script src="./src/index.js"></script>
  </body>
</html>

index.js

import * as THREE from "../node_modules/three/build/three.module.js";
import { OrbitControls } from "../node_modules/three/examples/jsm/controls/OrbitControls.js";
import { EffectComposer } from "../node_modules/three/examples/jsm/postprocessing/EffectComposer.js";
import { UnrealBloomPass } from "../node_modules/three/examples/jsm/postprocessing/UnrealBloomPass.js";
import { RenderPass } from "../node_modules/three/examples/jsm/postprocessing/RenderPass.js";
import { ShaderPass } from "../node_modules/three/examples/jsm/postprocessing/ShaderPass.js";

let scene, camera, renderer, bloomComposer, finalComposer;

const params = {
    exposure: 0,
    bloomStrength: 1.5,
    bloomThreshold: 0,
    bloomRadius: 0,
};

const init = () => {
    // 场景
    scene = new THREE.Scene();
    // 相机
    camera = new THREE.PerspectiveCamera(
        70,
        window.innerWidth / window.innerHeight,
        1,
        100000
    );
    camera.position.set(50, 50, 50);
    camera.position.y = 50;
    // 渲染器
    renderer = new THREE.WebGLRenderer({
        antialias: true,
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
    // 环境光
    const light = new THREE.AmbientLight(0xffffff, 0.6);
    light.layers.enable(0);
    light.layers.enable(1);
    scene.add(light);
    // 控制器
    const controls = new OrbitControls(camera, renderer.domElement);
    scene.add(new THREE.AxesHelper(100));
    window.onresize = () => {
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
    };
};

const initComposer = () => {
    bloomComposer = new EffectComposer(renderer);
    bloomComposer.renderToScreen = false;

    const renderScene = new RenderPass(scene, camera);
    // 光晕
    const bloomPass = new UnrealBloomPass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        1.5,
        0.4,
        0.85
    );
    bloomPass.threshold = params.bloomThreshold;
    bloomPass.strength = params.bloomStrength;
    bloomPass.radius = params.bloomRadius;
    bloomComposer.addPass(renderScene);
    bloomComposer.addPass(bloomPass);

    finalComposer = new EffectComposer(renderer);
    const finalShader = new THREE.ShaderMaterial({
        uniforms: {
            baseTexture: { value: null },
            bloomTexture: { value: bloomComposer.renderTarget2.texture }
        },
        vertexShader: `
            varying vec2 vUv;
            void main() {
                vUv = uv;
                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            }
        `,
        fragmentShader: `
            uniform sampler2D baseTexture;
            uniform sampler2D bloomTexture;
            varying vec2 vUv;
            void main() {
                gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
            }
        `,
        defines: {}
    });
    const finalPass = new ShaderPass(finalShader, "baseTexture");
    finalPass.needsSwap = true;
    finalComposer.addPass(renderScene);
    finalComposer.addPass(finalPass);
};

const main = () => {
    const geometry = new THREE.BoxGeometry(20, 20, 10);
    // 正常方块
    const normalMtl = new THREE.MeshLambertMaterial({ color: 0x00ffff });
    const normalBox = new THREE.Mesh(geometry, normalMtl);
    normalBox.position.z = -5;
    normalBox.layers.set(0);
    scene.add(normalBox);

    // 发光方块
    const bloomMtl = new THREE.MeshLambertMaterial({ color: 0xff5500 });
    const bloomBox = new THREE.Mesh(geometry, bloomMtl);
    bloomBox.position.z = 5;
    bloomBox.layers.set(1);
    scene.add(bloomBox);
};

const render = () => {
    renderer.autoClear = false;
    renderer.clear();

    camera.layers.set(1);
    bloomComposer.render();

    renderer.clearDepth(); // 清除深度缓存

    camera.layers.set(0);
    finalComposer.render(scene, camera);
    
    requestAnimationFrame(render);
};
init();
initComposer();
main();
render();

 

posted @ 2022-02-18 17:52  zero_to_infinity  阅读(642)  评论(0编辑  收藏  举报