学习threejs,使用EffectComposer后期处理组合器(采用RenderPass、ShaderPass渲染通道),实现自定义效果

#王者杯·14天创作挑战营·第1期#

👨‍⚕️ 主页: gis分享者
👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨‍⚕️ 收录于专栏:threejs gis工程师



一、🍀前言

本文详细介绍如何基于threejs在三维场景中使用EffectComposer后期处理组合器(采用RenderPass、ShaderPass渲染通道),实现自定义效果,亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️THREE.EffectComposer 后期处理

THREE.EffectComposer 用于在three.js中实现后期处理效果。该类管理了产生最终视觉效果的后期处理过程链。 后期处理过程根据它们添加/插入的顺序来执行,最后一个过程会被自动渲染到屏幕上。

1.1.1 ☘️代码示例

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
// 初始化 composer
const composer = new EffectComposer(renderer);
// 创建 RenderPass 并添加到 composer
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 添加其他后期处理通道(如模糊)
// composer.addPass(blurPass);
// 在动画循环中渲染
function animate() {
  composer.render();
  requestAnimationFrame(animate);
}

1.1.2 ☘️构造函数

EffectComposer( renderer : WebGLRenderer, renderTarget : WebGLRenderTarget )
renderer – 用于渲染场景的渲染器。
renderTarget – (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。

1.1.3 ☘️属性

.passes : Array
一个用于表示后期处理过程链(包含顺序)的数组。

渲染通道:
BloomPass   该通道会使得明亮区域参入较暗的区域。模拟相机照到过多亮光的情形
DotScreenPass   将一层黑点贴到代表原始图片的屏幕上
FilmPass    通过扫描线和失真模拟电视屏幕
MaskPass    在当前图片上贴一层掩膜,后续通道只会影响被贴的区域
RenderPass  该通道在指定的场景和相机的基础上渲染出一个新的场景
SavePass    执行该通道时,它会将当前渲染步骤的结果复制一份,方便后面使用。这个通道实际应用中作用不大;
ShaderPass  使用该通道你可以传入一个自定义的着色器,用来生成高级的、自定义的后期处理通道
TexturePass 该通道可以将效果组合器的当前状态保存为一个纹理,然后可以在其他EffectCoposer对象中将该纹理作为输入参数

.readBuffer : WebGLRenderTarget
内部读缓冲区的引用。过程一般从该缓冲区读取先前的渲染结果。

.renderer : WebGLRenderer
内部渲染器的引用。

.renderToScreen : Boolean
最终过程是否被渲染到屏幕(默认帧缓冲区)。

.writeBuffer : WebGLRenderTarget
内部写缓冲区的引用。过程常将它们的渲染结果写入该缓冲区。

1.1.4 ☘️方法

.addPass ( pass : Pass ) : undefined
pass – 将被添加到过程链的过程

将传入的过程添加到过程链。

.dispose () : undefined
释放此实例分配的 GPU 相关资源。每当您的应用程序不再使用此实例时调用此方法。

.insertPass ( pass : Pass, index : Integer ) : undefined
pass – 将被插入到过程链的过程。

index – 定义过程链中过程应插入的位置。

将传入的过程插入到过程链中所给定的索引处。

.isLastEnabledPass ( passIndex : Integer ) : Boolean
passIndex – 被用于检查的过程

如果给定索引的过程在过程链中是最后一个启用的过程,则返回true。 由EffectComposer所使用,来决定哪一个过程应当被渲染到屏幕上。

.removePass ( pass : Pass ) : undefined
pass – 要从传递链中删除的传递。

从传递链中删除给定的传递。

.render ( deltaTime : Float ) : undefined
deltaTime – 增量时间值。

执行所有启用的后期处理过程,来产生最终的帧,

.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
交换内部的读/写缓冲。

1.2 ☘️THREE.RenderPass

THREE.RenderPass用于将场景渲染到中间缓冲区,为后续的后期处理效果(如模糊、色调调整等)提供基础。

1.2.1 ☘️构造函数

RenderPass(scene, camera, overrideMaterial, clearColor, clearAlpha)

  • scene THREE.Scene 要渲染的 Three.js 场景对象。
  • camera THREE.Camera 场景对应的相机(如 PerspectiveCamera)。
  • overrideMaterial THREE.Material (可选) 覆盖场景中所有物体的材质(默认 null)。
  • clearColor THREE.Color (可选) 渲染前清除画布的颜色(默认不主动清除)。
  • clearAlpha number (可选) 清除画布的透明度(默认 0)。

1.2.2 ☘️属性

.enabled:boolean
是否启用此通道(默认 true)。设为 false 可跳过渲染。

.clear:boolean
渲染前是否清除画布(默认 true)。若需叠加多个 RenderPass,可设为 false。

.needsSwap:boolean
是否需要在渲染后交换缓冲区(通常保持默认 false)。

1.2.3 ☘️方法

.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。

1.3 ☘️THREE.ShaderPass

THREE.ShaderPass是 Three.js 后期处理模块的核心组件之一,允许开发者通过自定义着色器(Shader)实现任意特效,为后期处理链提供高度灵活性。

1.3.1 ☘️构造函数

ShaderPass(shader, textureID)

  • shader Object 包含着色器代码和 uniforms 的配置对象。
  • textureID string (可选) 输入纹理的 uniform 名称(默认 tDiffuse)。

1.3.2 ☘️属性

.enabled:boolean
是否启用此通道(默认 true)。设为 false 可临时禁用效果。

.uniforms:object
着色器 uniforms 的引用,支持动态修改参数:

shaderPass.uniforms.uStrength.value = 0.8; // 修改自定义参数

.renderToScreen:boolean
是否直接渲染到屏幕(默认 false)。若为最后通道,需设为 true。

1.3.3 ☘️方法

.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。

二、🍀使用EffectComposer后期处理组合器(采用RenderPass、ShaderPass渲染通道),实现自定义效果

1. ☘️实现思路

  • 1、初始化renderer渲染器。
  • 2、初始化Scene三维场景scene。
  • 3、初始化camera相机,定义相机位置 camera.position.set,设置相机方向camera.lookAt。
  • 4、创建THREE.AmbientLight环境光源ambi,创建DirectionalLight平行光源spotLight,设置spotLight位置和光强,scene场景加入ambi和spotLight。
  • 5、加载几何模型:定义createMesh方法(参数:geom 几何对象),用于生成网格对象。调用createMesh方法创建球体网格对象sphere,scene场景添加sphere。创建RenderPass、多个ShaderPass渲染通道,创建THREE.EffectComposer后期处理组合器composer,composer添加创建的渲染通道。定义controls方法,方法内定义gui控制ShaderPass特效通道参数以及参数更新方法。定义render方法,实现sphere的旋转动画,调用composer的渲染方法render。具体代码参考下面代码样例。
  • 6、加入gui控制。加入stats监控器,监控帧数信息。

2. ☘️代码样例

custom-shader.js 代码:

THREE.CustomGrayScaleShader = {
    uniforms: {
        "tDiffuse": {type: "t", value: null},
        "rPower": {type: "f", value: 0.2126},
        "gPower": {type: "f", value: 0.7152},
        "bPower": {type: "f", value: 0.0722}
    },

    // 0.2126 R + 0.7152 G + 0.0722 B
    // vertexshader is always the same for postprocessing steps
    vertexShader: [
        "varying vec2 vUv;",
        "void main() {",
        "vUv = uv;",
        "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
        "}"

    ].join("\n"),

    fragmentShader: [

        // pass in our custom uniforms
        "uniform float rPower;",
        "uniform float gPower;",
        "uniform float bPower;",

        // pass in the image/texture we'll be modifying
        "uniform sampler2D tDiffuse;",

        // used to determine the correct texel we're working on
        "varying vec2 vUv;",

        // executed, in parallel, for each pixel
        "void main() {",

        // get the pixel from the texture we're working with (called a texel)
        "vec4 texel = texture2D( tDiffuse, vUv );",

        // calculate the new color
        "float gray = texel.r*rPower + texel.g*gPower + texel.b*bPower;",

        // return this new color
        "gl_FragColor = vec4( vec3(gray), texel.w );",

        "}"

    ].join("\n")

};

THREE.CustomBitShader = {
    uniforms: {

        "tDiffuse": {type: "t", value: null},
        "bitSize": {type: "i", value: 4}

    },
    vertexShader: [

        "varying vec2 vUv;",

        "void main() {",

        "vUv = uv;",
        "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",

        "}"

    ].join("\n"),
    fragmentShader: [

        "uniform int bitSize;",

        "uniform sampler2D tDiffuse;",

        "varying vec2 vUv;",

        "void main() {",

        "vec4 texel = texture2D( tDiffuse, vUv );",
        "float n = pow(float(bitSize),2.0);",
        "float newR = floor(texel.r*n)/n;",
        "float newG = floor(texel.g*n)/n;",
        "float newB = floor(texel.b*n)/n;",

        "gl_FragColor = vec4( vec3(newR,newG,newB), 1.0);",

        "}"

    ].join("\n")
};

主要代码:

<!DOCTYPE html>
<html>

<head>
    <title>学习threejs,使用EffectComposer后期处理组合器(采用RenderPass、ShaderPass渲染通道),实现自定义效果</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <script type="text/javascript" src="../libs/OrbitControls.js"></script>
    <script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
    <script type="text/javascript" src="../libs/shaders/CopyShader.js"></script>
    <script type="text/javascript" src="../libs/postprocessing/EffectComposer.js"></script>
    <script type="text/javascript" src="../libs/postprocessing/MaskPass.js"></script>
    <script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
    <script type="text/javascript" src="custom-shader.js"></script>
    <script type="text/javascript" src="../libs/postprocessing/RenderPass.js"></script>

    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<!-- Js代码块 -->
<script type="text/javascript">

    // 初始化
    function init() {

        var stats = initStats();

        // 创建三维场景scene
        var scene = new THREE.Scene();

        // 创建相机
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        // 创建渲染器webGLRenderer,设置webGLRenderer渲染器的背景颜色、大小和投影
        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        var sphere = createMesh(new THREE.SphereGeometry(10, 40, 40));
        // scene场景添加三维球体
        scene.add(sphere);
        // 设置相机位置和方向
        camera.position.x = -10;
        camera.position.y = 15;
        camera.position.z = 25;

        camera.lookAt(new THREE.Vector3(0, 0, 0));

        var orbitControls = new THREE.OrbitControls(camera);
        orbitControls.autoRotate = false;
        var clock = new THREE.Clock();
		// 添加环境光和平行光
        var ambi = new THREE.AmbientLight(0x181818);
        scene.add(ambi);

        var spotLight = new THREE.DirectionalLight(0xffffff);
        spotLight.position.set(550, 100, 550);
        spotLight.intensity = 0.6;

        scene.add(spotLight);

        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var renderPass = new THREE.RenderPass(scene, camera);
        var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
        effectCopy.renderToScreen = true;
        var shaderPass = new THREE.ShaderPass(THREE.CustomGrayScaleShader);
        shaderPass.enabled = false;

        var bitPass = new THREE.ShaderPass(THREE.CustomBitShader);
        bitPass.enabled = false;

        var composer = new THREE.EffectComposer(webGLRenderer);
        composer.addPass(renderPass);
        composer.addPass(shaderPass);
        composer.addPass(bitPass);
        composer.addPass(effectCopy);

        var controls = new function () {
            this.grayScale = false;
            this.rPower = 0.2126;
            this.gPower = 0.7152;
            this.bPower = 0.0722;

            this.bitShader = false;
            this.bitSize = 8;

            this.updateEffectFilm = function () {
                shaderPass.enabled = controls.grayScale;
                shaderPass.uniforms.rPower.value = controls.rPower;
                shaderPass.uniforms.gPower.value = controls.gPower;
                shaderPass.uniforms.bPower.value = controls.bPower;
            };

            this.updateBit = function () {
                bitPass.enabled = controls.bitShader;
                bitPass.uniforms.bitSize.value = controls.bitSize;
            }
        };

        var gui = new dat.GUI();
        var grayMenu = gui.addFolder('gray scale');
        grayMenu.add(controls, 'grayScale').onChange(controls.updateEffectFilm);
        grayMenu.add(controls, 'rPower', 0, 1).onChange(controls.updateEffectFilm);
        grayMenu.add(controls, 'gPower', 0, 1).onChange(controls.updateEffectFilm);
        grayMenu.add(controls, 'bPower', 0, 1).onChange(controls.updateEffectFilm);

        var bitMenu = gui.addFolder('bit');
        bitMenu.add(controls, 'bitShader').onChange(controls.updateBit);
        bitMenu.add(controls, 'bitSize', 2, 24).step(1).onChange(controls.updateBit);

        var step = 0;
        render();

        function createMesh(geom) {
            var planetTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/Earth.png");
            var specularTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthSpec.png");
            var normalTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthNormal.png");

            var planetMaterial = new THREE.MeshPhongMaterial();
            planetMaterial.specularMap = specularTexture;
            planetMaterial.specular = new THREE.Color(0x4444aa);


            planetMaterial.normalMap = normalTexture;
            planetMaterial.map = planetTexture;
            var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]);

            return mesh;
        }

        function render() {
            stats.update();

            var delta = clock.getDelta();
            orbitControls.update(delta);

            sphere.rotation.y += 0.002;
            requestAnimationFrame(render);
            composer.render(delta);
        }

        function initStats() {
            var stats = new Stats();
            stats.setMode(0);

            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';

            document.getElementById("Stats-output").appendChild(stats.domElement);

            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

效果如下
在这里插入图片描述

评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gis分享者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值