文章目录
- 前言
- EffectComposer 使用流程
- 场景初始化:自转的地球
- 创建THREE.EffectComposer
- 添加后期处理通道并更新渲染
- EffectComposer 使用示例
- 示例一:FilmPass 添加电视效果
- 示例二:OutlinePass 添加闪烁效果
- 总结
前言
threejs中的效果合成器 EffectComposer 可以在场景渲染完毕后再增加一些特效,如:让场景再某些情况下变得更加模糊或者更加鲜艳,增加滤镜或者闪烁效果,使用扫描线添加老旧电视屏幕上的效果等等。本文介绍 EffectComposer的基本使用,并使用 OutlinePass 这个通道为地球添加闪烁效果
EffectComposer 使用流程
场景初始化:自转的地球
在正式使用效果合成器前,首先创建一个基本场景作为对比,在场景中通过贴图添加一个旋转的地球
import { useRef, useEffect, useCallback, useState } from 'react'
import * as THREE from 'three'
import OrbitControls from 'three-orbitcontrols';
import Earth from '../../assets/textures/earth/Earth.png'
import EarthNormal from '../../assets/textures/earth/EarthNormal.png'
import EarthSpec from '../../assets/textures/earth/EarthSpec.png'
import './index.scss'
const View = () => {
const page = useRef(); // useRef不会导致重新渲染
/**
* 场景、相机、渲染器作为threejs的基本结构,需要在页面进入时渲染完毕
*/
const scene = useRef(new THREE.Scene()).current; //场景
const camera = useRef(new THREE.PerspectiveCamera()).current; //摄像机(透视投影)
const render = useRef(new THREE.WebGLRenderer()).current; //渲染器
const controls = new OrbitControls(camera, render.domElement);//创建控件对象
const timer = useRef(null) // 定义定时器
const earthRef = useRef()
useEffect(() => {
page.current.appendChild(render.domElement);
init();
initLight();
addEarth();
renderScene();
}, [])
// 初始化场景
const init = useCallback(() => {
render.setSize(page.current.offsetWidth, page.current.offsetHeight); // 渲染器设置尺寸
// 设置背景颜色
render.setClearColor(new THREE.Color(0x000000)); // 设置背景颜色和透明度
render.shadowMap.enabled = true; // 渲染器允许渲染阴影⭐
/**
* 设置摄像机的属性
*/
camera.aspect = (page.current.offsetWidth / page.current.offsetHeight) // 摄像机设置屏幕宽高比
camera.fov = 45; // 摄像机的视角
camera.near = 0.01; // 近面距离
camera.far = 1001; // 远面距离
camera.position.set(30, 40, 30) // 设置摄像机在threejs坐标系中的位置
camera.lookAt(0, 0, 0) // 摄像机的指向
camera.updateProjectionMatrix(); // 更新摄像机投影矩阵,在任何参数被改变以后必须被调用
}, [render, scene])
// 初始化环境光
const initLight = () => {
const ambientLight = new THREE.AmbientLight(0x343434) // 基本光源
const spotLight = new THREE.SpotLight(0xFFFFFF); // 聚光灯
spotLight.position.set(-10, 30, 40);
spotLight.castShadow = true; // 只有该属性为true时,该点光源允许产生阴影,并且下列属性可用
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
spotLight.shadow.camera.fov = 15;
spotLight.castShadow = true;
spotLight.decay = 2;
spotLight.penumbra = 0.05;
scene.add(ambientLight, spotLight); // 向场景中添加光源
}
// 添加地球
function addEarth() {
const textureLoader = new THREE.TextureLoader();
const planetMaterial = new THREE.MeshPhongMaterial({
map: textureLoader.load(Earth),
normalMap: textureLoader.load(EarthNormal),
specularMap: textureLoader.load(EarthSpec),
specular: new THREE.Color(0x4444aa),
normalScale: new THREE.Vector2(6, 6),
shininess: 0.5
});
earthRef.current = new THREE.Mesh(new THREE.SphereGeometry(15, 40, 40), planetMaterial);
scene.add(earthRef.current);
}
// 渲染器执行渲染
const renderScene = useCallback(() => {
console.log('renderScene')
timer.current = window.requestAnimationFrame(() => renderScene())
controls.update();
earthRef.current.rotation.y += 0.001;
render.render(scene, camera);
}, [render])
return (
<div className='page' ref={page} />
)
};
export default View
创建THREE.EffectComposer
首先要创建一个 THREE.EffectComposer 对象,传入的参数是WebGLRenderer:
EffectComposer( renderer : WebGLRenderer, renderTarget : WebGLRenderTarget )
- renderer – 用于渲染场景的渲染器。
- renderTarget – (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。
const renderer = new THREE.WebGLRenderer();
const composer = new THREE.EffectComposer(renderer);
添加后期处理通道并更新渲染
- RenderPass: 渲染场景,但是不会添加至屏幕上
- FilmPass:添加扫描线。可以设置添加到屏幕
- addPass():将通道添加至组合器中
const renderPass = new THREE.RenderPass(scene, camera);
const effectFilm = new THREE.FilmPass(0.8, 0.325, 256, false);
effectFilm.renderToScreen = true;
const composer = new THREE.EffectComposer(renderer);
composer.addPass(renderPass);
composer.addPass(effectFilm);
在场景进行render时,使用 composer 而不是 renderer 进行渲染:
const renderScene = useCallback(() => {
console.log('renderScene')
const delta = clock.current.getDelta();
window.requestAnimationFrame(() => renderScene())
controls.update();
earthRef.current.rotation.y += 0.001;
// render.render(scene,camera);
composer.current.render(delta);
}, [render])
EffectComposer 使用示例
示例一:FilmPass 添加电视效果
修改后代码如下👇
import { useRef, useEffect, useCallback, useState } from 'react'
import * as THREE from 'three'
import OrbitControls from 'three-orbitcontrols';
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
//后处理通道包
import {RenderPass} from "three/examples/jsm/postprocessing/RenderPass.js";
import {FilmPass} from "three/examples/jsm/postprocessing/FilmPass.js";
import Earth from '../../assets/textures/earth/Earth.png'
import EarthNormal from '../../assets/textures/earth/EarthNormal.png'
import EarthSpec from '../../assets/textures/earth/EarthSpec.png'
import './index.scss'
const View = () => {
const page = useRef(); // useRef不会导致重新渲染
/**
* 场景、相机、渲染器作为threejs的基本结构,需要在页面进入时渲染完毕
*/
const scene = useRef(new THREE.Scene()).current; //场景
const camera = useRef(new THREE.PerspectiveCamera()).current; //摄像机(透视投影)
const render = useRef(new THREE.WebGLRenderer()).current; //渲染器
const composer = useRef(); //渲染器
const controls = new OrbitControls(camera, render.domElement);//创建控件对象
const clock = useRef(new THREE.Clock());
const earthRef = useRef()
useEffect(() => {
page.current.appendChild(render.domElement);
init();
initLight();
addEarth();
renderScene();
}, [])
// 初始化场景
const init = useCallback(() => {
render.setSize(page.current.offsetWidth, page.current.offsetHeight); // 渲染器设置尺寸
// 设置背景颜色
render.shadowMap.enabled = true;
render.shadowMapSoft = true;
render.shadowMap.type = THREE.PCFSoftShadowMap;
render.setClearColor(new THREE.Color(0x000000)); // 设置背景颜色和透明度
render.shadowMap.enabled = true; // 渲染器允许渲染阴影⭐
// 配置通道
const renderPass = new RenderPass(scene, camera);
const effectFilm = new FilmPass(0.8, 0.325, 256, false);
effectFilm.renderToScreen = true;
composer.current = new EffectComposer(render);
composer.current.addPass(renderPass);
composer.current.addPass(effectFilm);
/**
* 设置摄像机的属性
*/
camera.aspect = (page.current.offsetWidth / page.current.offsetHeight) // 摄像机设置屏幕宽高比
camera.fov = 45; // 摄像机的视角
camera.near = 0.1; // 近面距离
camera.far = 1001; // 远面距离
camera.position.set(-30, 40, 30) // 设置摄像机在threejs坐标系中的位置
camera.lookAt(0, 0, 0) // 摄像机的指向
camera.updateProjectionMatrix(); // 更新摄像机投影矩阵,在任何参数被改变以后必须被调用
}, [render, scene])
// 初始化环境光
const initLight = () => {
const ambientLight = new THREE.AmbientLight(0x343434) // 基本光源
const spotLight = new THREE.SpotLight(0xFFFFFF); // 聚光灯
spotLight.position.set(-10, 30, 40);
spotLight.castShadow = true; // 只有该属性为true时,该点光源允许产生阴影,并且下列属性可用
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
spotLight.shadow.camera.fov = 15;
spotLight.castShadow = true;
spotLight.decay = 2;
spotLight.penumbra = 0.05;
scene.add(ambientLight, spotLight); // 向场景中添加光源
}
// 添加地球
function addEarth() {
const textureLoader = new THREE.TextureLoader();
const planetMaterial = new THREE.MeshPhongMaterial({
map: textureLoader.load(Earth),
normalMap: textureLoader.load(EarthNormal),
specularMap: textureLoader.load(EarthSpec),
specular: new THREE.Color(0x4444aa),
normalScale: new THREE.Vector2(6, 6),
shininess: 0.5
});
earthRef.current = new THREE.Mesh(new THREE.SphereGeometry(15, 40, 40), planetMaterial);
scene.add(earthRef.current);
}
// 渲染器执行渲染
const renderScene = useCallback(() => {
console.log('renderScene')
const delta = clock.current.getDelta();
window.requestAnimationFrame(() => renderScene())
controls.update();
earthRef.current.rotation.y += 0.001;
// render.render(scene,camera);
composer.current.render(delta);
}, [render])
return (
<div className='page' ref={page} />
)
};
export default View
示例二:OutlinePass 添加闪烁效果
import { useRef, useEffect, useCallback, useState } from 'react'
import * as THREE from 'three'
import OrbitControls from 'three-orbitcontrols';
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
//后处理通道包
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
import Earth from '../../assets/textures/earth/Earth.png'
import EarthNormal from '../../assets/textures/earth/EarthNormal.png'
import EarthSpec from '../../assets/textures/earth/EarthSpec.png'
import './index.scss'
const View = () => {
const page = useRef(); // useRef不会导致重新渲染
/**
* 场景、相机、渲染器作为threejs的基本结构,需要在页面进入时渲染完毕
*/
const scene = useRef(new THREE.Scene()).current; //场景
const camera = useRef(new THREE.PerspectiveCamera()).current; //摄像机(透视投影)
const render = useRef(new THREE.WebGLRenderer()).current; //渲染器
const composer = useRef(); //渲染器
const controls = new OrbitControls(camera, render.domElement);//创建控件对象
const clock = useRef(new THREE.Clock());
const earthRef = useRef()
useEffect(() => {
page.current.appendChild(render.domElement);
addEarth();
init();
initLight();
renderScene();
}, [])
// 初始化场景
const init = useCallback(() => {
render.setSize(page.current.offsetWidth, page.current.offsetHeight); // 渲染器设置尺寸
// 设置背景颜色
render.shadowMap.enabled = true;
render.shadowMapSoft = true;
render.shadowMap.type = THREE.PCFSoftShadowMap;
render.setClearColor(new THREE.Color(0x000000)); // 设置背景颜色和透明度
render.shadowMap.enabled = true; // 渲染器允许渲染阴影⭐
// OutLine
const outlinePass = new OutlinePass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
scene,
camera,
[earthRef.current]);
const renderPass = new RenderPass(scene, camera);
outlinePass.edgeStrength = 2; // 边缘强度
outlinePass.edgeGlow = 2; // 光晕强度
outlinePass.edgeThickness = 3; // 光晕尺寸
outlinePass.pulsePeriod = 1;// 闪烁频率
outlinePass.visibleEdgeColor.set('red'); // 光晕颜色
composer.current = new EffectComposer(render);
composer.current.addPass(renderPass);
composer.current.addPass(outlinePass);
/**
* 设置摄像机的属性
*/
camera.aspect = (page.current.offsetWidth / page.current.offsetHeight) // 摄像机设置屏幕宽高比
camera.fov = 45; // 摄像机的视角
camera.near = 0.1; // 近面距离
camera.far = 1001; // 远面距离
camera.position.set(-30, 40, 30) // 设置摄像机在threejs坐标系中的位置
camera.lookAt(0, 0, 0) // 摄像机的指向
camera.updateProjectionMatrix(); // 更新摄像机投影矩阵,在任何参数被改变以后必须被调用
}, [render, scene])
// 初始化环境光
const initLight = () => {
const ambientLight = new THREE.AmbientLight(0x343434) // 基本光源
const spotLight = new THREE.SpotLight(0xFFFFFF); // 聚光灯
spotLight.position.set(-10, 30, 40);
spotLight.castShadow = true; // 只有该属性为true时,该点光源允许产生阴影,并且下列属性可用
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
spotLight.shadow.camera.fov = 15;
spotLight.castShadow = true;
spotLight.decay = 2;
spotLight.penumbra = 0.05;
scene.add(ambientLight, spotLight); // 向场景中添加光源
}
// 添加地球
function addEarth() {
const textureLoader = new THREE.TextureLoader();
const planetMaterial = new THREE.MeshPhongMaterial({
map: textureLoader.load(Earth),
normalMap: textureLoader.load(EarthNormal),
specularMap: textureLoader.load(EarthSpec),
specular: new THREE.Color(0x4444aa),
normalScale: new THREE.Vector2(6, 6),
shininess: 0.5
});
earthRef.current = new THREE.Mesh(new THREE.SphereGeometry(15, 40, 40), planetMaterial);
scene.add(earthRef.current);
// 渲染器执行渲染
const renderScene = useCallback(() => {
console.log('renderScene')
const delta = clock.current.getDelta();
window.requestAnimationFrame(() => renderScene())
controls.update();
earthRef.current.rotation.y += 0.001;
render.autoClear = false;
render.clear();
// render.render(scene,camera);
composer.current.render(delta);
}, [render])
return (
<div className='page' ref={page} />
)
};
export default View
总结
EffectComposer 使用流程
- 场景初始化:自转的地球
- 创建THREE.EffectComposer
- 添加后期处理通道并更新渲染
EffectComposer 使用示例
- 示例一:FilmPass 添加电视效果
- 示例二:OutlinePass 添加闪烁效果