👨⚕️ 主页: 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环境光源ambiLight,创建THREE.SpotLight聚光灯光源spotLight,设置spotLight位置、光强、投影,scene场景加入ambiLight和spotLight。
- 5、加载几何模型:添加地面立方体网格对象cube,设置cube的材质、位置、旋转和投影信息,scene添加cube。添加三个立方体网格对象cube1、cube2、cube3,设置三个立方体的颜色和位置信息,scene添加cube1、cube2、cube3。添加‘libertStatue.obj’模型mesh,设置模型大小、位置等信息,scene添加object。创建RenderPass、多个ShaderPass渲染通道,创建THREE.EffectComposer后期处理组合器composer,composer添加创建的渲染通道。定义controls方法,方法内定义gui控制ShaderPass特效通道参数以及参数更新方法。定义render方法,实现cube1、cube2、cube3和模型mesh的旋转动画,调用composer的渲染方法render。具体代码参考下面代码样例。
- 6、加入gui控制。加入stats监控器,监控帧数信息。
2. ☘️代码样例
<!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/OBJLoader.js"></script>
<script type="text/javascript" src="../libs/MTLLoader.js"></script>
<script type="text/javascript" src="../libs/OBJMTLLoader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/EffectComposer.js"></script>
<script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/RenderPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/MaskPass.js"></script>
<script type="text/javascript" src="../libs/shaders/CopyShader.js"></script>
<script type="text/javascript" src="../libs/shaders/BrightnessContrastShader.js"></script>
<script type="text/javascript" src="../libs/shaders/ColorifyShader.js"></script>
<script type="text/javascript" src="../libs/shaders/SepiaShader.js"></script>
<script type="text/javascript" src="../libs/shaders/RGBShiftShader.js"></script>
<script type="text/javascript" src="../libs/shaders/ColorCorrectionShader.js"></script>
<script type="text/javascript" src="../libs/shaders/MirrorShader.js"></script>
<script type="text/javascript" src="../libs/shaders/VignetteShader.js"></script>
<script type="text/javascript" src="../libs/shaders/HueSaturationShader.js"></script>
<script type="text/javascript" src="../libs/shaders/BlendShader.js"></script>
<script type="text/javascript" src="../libs/shaders/KaleidoShader.js"></script>
<script type="text/javascript" src="../libs/shaders/LuminosityShader.js"></script>
<script type="text/javascript" src="../libs/shaders/TechnicolorShader.js"></script>
<script type="text/javascript" src="../libs/shaders/UnpackDepthRGBAShader.js"></script>
<style>
body {
/* set margin to 0 and overflow to hidden, to go fullscreen */
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的背景色、大小和touy
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xaaaaff, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
//设置相机的位置和方向
camera.position.x = 20;
camera.position.y = 30;
camera.position.z = 40;
camera.lookAt(new THREE.Vector3(-15, -10, -25));
// 添加聚光灯光源
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.castShadow = true;
spotLight.position.set(0, 60, 50);
spotLight.intensity = 1;
spotLight.shadowMapWidth = 2048;
spotLight.shadowMapHeight = 2048;
spotLight.shadowCameraFov = 120;
spotLight.shadowCameraNear = 1;
spotLight.shadowCameraFar = 1000;
var ambiLight = new THREE.AmbientLight(0x444444);
scene.add(ambiLight);
scene.add(spotLight);
var plane = new THREE.BoxGeometry(1600, 1600, 0.1, 40, 40);
var cube = new THREE.Mesh(plane, new THREE.MeshPhongMaterial(
{
color: 0xffffff,
map: THREE.ImageUtils.loadTexture("../assets/textures/general/plaster-diffuse.jpg"),
normalMap: THREE.ImageUtils.loadTexture("../assets/textures/general/plaster-normal.jpg"),
normalScale: new THREE.Vector2(0.6, 0.6)
}));
cube.material.map.wrapS = THREE.RepeatWrapping;
cube.material.map.wrapT = THREE.RepeatWrapping;
cube.material.normalMap.wrapS = THREE.RepeatWrapping;
cube.material.normalMap.wrapT = THREE.RepeatWrapping;
cube.rotation.x = Math.PI / 2;
cube.material.map.repeat.set(80, 80);
cube.receiveShadow = true;
cube.position.z = -150;
cube.position.x = -150;
scene.add(cube);
var cube1 = new THREE.Mesh(new THREE.BoxGeometry(30, 10, 2), new THREE.MeshPhongMaterial({color: 0xff0000}));
cube1.position.x = -15;
cube1.position.y = 5;
cube1.position.z = 15;
cube1.castShadow = true;
scene.add(cube1);
var cube2 = cube1.clone();
cube2.material = cube1.material.clone();
cube2.material.color = new THREE.Color(0x00ff00);
cube2.position.z = 5;
cube2.position.x = -20;
scene.add(cube2);
var cube3 = cube1.clone();
cube3.material = cube1.material.clone();
cube3.material.color = new THREE.Color(0x0000ff);
cube3.position.z = -8;
cube3.position.x = -25;
scene.add(cube3);
var mesh;
// webGLRenderer 绑定html要素
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
var loader = new THREE.OBJMTLLoader();
loader.load('../assets/models/sol/libertStatue.obj', '../assets/models/sol/libertStatue.mtl',
function (event) {
var object = event;
// fix for incorrect uvs.
console.log(event);
var geom = object.children[0].geometry;
var uv3 = geom.faceVertexUvs[0][0];
var uv4 = geom.faceVertexUvs[0][10];
// fill in the missing ones
for (var j = 0; j < 7616 - 7206; j++) {
if (geom.faces[j + 7206] instanceof THREE.Face4) {
geom.faceVertexUvs[0].push(uv4);
} else {
geom.faceVertexUvs[0].push(uv4);
}
}
object.children.forEach(function (e) {
e.castShadow = true
});
object.scale.set(20, 20, 20);
mesh = object;
mesh.position.x = 15;
mesh.position.z = 5;
scene.add(object);
});
var mirror = new THREE.ShaderPass(THREE.MirrorShader);
mirror.enabled = false;
var hue = new THREE.ShaderPass(THREE.HueSaturationShader);
hue.enabled = false;
var vignette = new THREE.ShaderPass(THREE.VignetteShader);
vignette.enabled = false;
var colorCorrection = new THREE.ShaderPass(THREE.ColorCorrectionShader);
colorCorrection.enabled = false;
var rgbShift = new THREE.ShaderPass(THREE.RGBShiftShader);
rgbShift.enabled = false;
var brightness = new THREE.ShaderPass(THREE.BrightnessContrastShader);
brightness.uniforms.brightness.value = 0;
brightness.uniforms.contrast.value = 0;
brightness.enabled = false;
brightness.uniforms.brightness.value = 0;
brightness.uniforms.contrast.value = 0;
var colorify = new THREE.ShaderPass(THREE.ColorifyShader);
colorify.uniforms.color.value = new THREE.Color(0xffffff);
colorify.enabled = false;
var sepia = new THREE.ShaderPass(THREE.SepiaShader);
sepia.uniforms.amount.value = 1;
sepia.enabled = false;
var kal = new THREE.ShaderPass(THREE.KaleidoShader);
kal.enabled = false;
var lum = new THREE.ShaderPass(THREE.LuminosityShader);
lum.enabled = false;
var techni = new THREE.ShaderPass(THREE.TechnicolorShader);
techni.enabled = false;
var unpack = new THREE.ShaderPass(THREE.UnpackDepthRGBAShader);
unpack.enabled = false;
var renderPass = new THREE.RenderPass(scene, camera);
var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
effectCopy.renderToScreen = true;
var composer = new THREE.EffectComposer(webGLRenderer);
composer.addPass(renderPass);
composer.addPass(brightness);
composer.addPass(sepia);
composer.addPass(mirror);
composer.addPass(colorify);
composer.addPass(colorCorrection);
composer.addPass(rgbShift);
composer.addPass(vignette);
composer.addPass(hue);
composer.addPass(kal);
composer.addPass(lum);
composer.addPass(techni);
composer.addPass(unpack);
composer.addPass(effectCopy);
var controls = new function () {
this.brightness = 0.01;
this.contrast = 0.01;
this.select = 'none';
this.color = 0xffffff;
this.amount = 1;
this.powRGB_R = 2;
this.mulRGB_R = 1;
this.powRGB_G = 2;
this.mulRGB_G = 1;
this.powRGB_B = 2;
this.mulRGB_B = 1;
this.rgbAmount = 0.005;
this.angle = 0.0;
this.side = 1;
this.offset = 1;
this.darkness = 1;
this.hue = 0.01;
this.saturation = 0.01;
this.kalAngle = 0;
this.kalSides = 6;
this.rotate = false;
this.switchShader = function () {
switch (controls.select) {
case 'none' :
{
enableShader();
break;
}
case 'colorify' :
{
enableShader(colorify);
break;
}
case 'brightness' :
{
enableShader(brightness);
break;
}
case 'sepia' :
{
enableShader(sepia);
break;
}
case 'colorCorrection' :
{
enableShader(colorCorrection);
break;
}
case 'rgbShift' :
{
enableShader(rgbShift);
break;
}
case 'mirror' :
{
enableShader(mirror);
break;
}
case 'vignette' :
{
enableShader(vignette);
break;
}
case 'hueAndSaturation' :
{
enableShader(hue);
break;
}
case 'kaleidoscope' :
{
enableShader(kal);
break;
}
case 'luminosity' :
{
enableShader(lum);
break;
}
case 'technicolor' :
{
enableShader(techni);
break;
}
case 'unpackDepth' :
{
enableShader(unpack);
break;
}
}
};
this.changeBrightness = function () {
brightness.uniforms.brightness.value = controls.brightness;
brightness.uniforms.contrast.value = controls.contrast;
};
this.changeColor = function () {
colorify.uniforms.color.value = new THREE.Color(controls.color);
};
this.changeSepia = function () {
sepia.uniforms.amount.value = controls.amount;
};
this.changeCorrection = function () {
colorCorrection.uniforms.mulRGB.value = new THREE.Vector3(controls.mulRGB_R, controls.mulRGB_G, controls.mulRGB_B);
colorCorrection.uniforms.powRGB.value = new THREE.Vector3(controls.powRGB_R, controls.powRGB_G, controls.powRGB_B);
};
this.changeRGBShifter = function () {
rgbShift.uniforms.amount.value = controls.rgbAmount;
rgbShift.uniforms.angle.value = controls.angle;
};
this.changeMirror = function () {
mirror.uniforms.side.value = controls.side;
};
this.changeVignette = function () {
vignette.uniforms.darkness.value = controls.darkness;
vignette.uniforms.offset.value = controls.offset;
};
this.changeHue = function () {
hue.uniforms.hue.value = controls.hue;
hue.uniforms.saturation.value = controls.saturation;
};
this.changeKal = function () {
kal.uniforms.sides.value = controls.kalSides;
kal.uniforms.angle.value = controls.kalAngle;
};
function enableShader(shader) {
// we're not interested in the first or the last one
for (var i = 1; i < composer.passes.length - 1; i++) {
if (composer.passes[i] == shader) {
composer.passes[i].enabled = true;
} else {
composer.passes[i].enabled = false;
}
}
}
};
var gui = new dat.GUI();
gui.add(controls, "select", ['none', "colorify", 'brightness', 'sepia', 'colorCorrection', 'rgbShift', 'mirror', 'vignette', 'hueAndSaturation', 'kaleidoscope', 'luminosity', 'technicolor']).onChange(controls.switchShader);
gui.add(controls, "rotate");
var bnFolder = gui.addFolder("Brightness");
bnFolder.add(controls, "brightness", -1, 1).onChange(controls.changeBrightness);
bnFolder.add(controls, "contrast", -1, 1).onChange(controls.changeBrightness);
var clFolder = gui.addFolder("Colorify");
clFolder.addColor(controls, "color").onChange(controls.changeColor);
var colFolder = gui.addFolder('Color Correction');
colFolder.add(controls, "powRGB_R", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "powRGB_G", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "powRGB_B", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "mulRGB_R", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "mulRGB_G", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "mulRGB_B", 0, 5).onChange(controls.changeCorrection);
var sepiaFolder = gui.addFolder("Sepia");
sepiaFolder.add(controls, "amount", 0, 2).step(0.1).onChange(controls.changeSepia);
var shiftFolder = gui.addFolder("RGB Shift");
shiftFolder.add(controls, "rgbAmount", 0, 0.1).step(0.001).onChange(controls.changeRGBShifter);
shiftFolder.add(controls, "angle", 0, 3.14).step(0.001).onChange(controls.changeRGBShifter);
var mirrorFolder = gui.addFolder("mirror");
mirrorFolder.add(controls, "side", 0, 3).step(1).onChange(controls.changeMirror);
var vignetteFolder = gui.addFolder("vignette");
vignetteFolder.add(controls, "darkness", 0, 2).onChange(controls.changeVignette);
vignetteFolder.add(controls, "offset", 0, 2).onChange(controls.changeVignette);
var hueAndSat = gui.addFolder("hue and saturation");
hueAndSat.add(controls, "hue", -1, 1).step(0.01).onChange(controls.changeHue);
hueAndSat.add(controls, "saturation", -1, 1).step(0.01).onChange(controls.changeHue);
var kalMenu = gui.addFolder("Kaleidoscope");
kalMenu.add(controls, "kalAngle", -2 * Math.PI, 2 * Math.PI).onChange(controls.changeKal);
kalMenu.add(controls, "kalSides", 2, 20).onChange(controls.changeKal);
render();
function render() {
stats.update();
//
if (controls.rotate) {
if (mesh) mesh.rotation.y += 0.01;
cube1.rotation.y += 0.01;
cube2.rotation.y += 0.01;
cube3.rotation.y += 0.01;
}
requestAnimationFrame(render);
composer.render();
}
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>
效果如下