一、问题描述:
想要实现在一个场景中含有辉光效果的物体也有普通物体,完成之后发现效果虽然实现了但是容器背景成了黑色,故在网上查找了一些大佬经验,总结了解决方法。
二、需理解的知识点:
layers.set()
删除图层对象已有的所有对应关系,增加与参数指定的图层的对应关系。layers.enable()
增加图层对象与参数指定图层的对应关系,不会删除原有的图层对于关系。layers.mask
用 bit mask 表示当前图层对象与 0 - 31 中的哪些图层相对应。所属层所对应的比特位为 1,其他位位 0。
三、辉光效果实现:
1. 初始化渲染器,定义辉光渲染器 bloomComposer
,全局渲染器 finalComposer
const initComposer = (el) => {
// 去掉锯齿—1
// 通过ShaderPass构造函数把FXAAShader着色器和uniforms构成的对象作为参数,创建一个锯齿通道FXAAShaderPass,然后把锯齿通道插入到composer中。
const effectFXAA = new ShaderPass(FXAAShader);
effectFXAA.uniforms['resolution'].value.set(0.6 / window.innerWidth, 0.6 / window.innerHeight); // 渲染区域Canvas画布宽高度 不一定是全屏,也可以是区域值
effectFXAA.renderToScreen = true;
// 去掉锯齿—1
const renderScene = new RenderPass(scene, camera); // RenderPass这个通道会在当前场景(scene)和摄像机(camera)的基础上渲染出一个新场景,新建:
// 添加光晕效果—2
bloomPass = new UnrealBloomPass( // UnrealBloomPass通道可实现一个泛光效果。
new THREE.Vector2(el.offsetWidth, el.offsetHeight),
1.5,
0,
0.85,
);
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;
// 着色器通道容器–放进容器里
bloomComposer = new EffectComposer(renderer); // EffectComposer可以理解为着色器通道容器,着色器通道按照先后顺序添加进来并执行
// bloomComposer.renderToScreen = true;
bloomComposer.addPass(renderScene);
bloomComposer.addPass(bloomPass); // 添加光晕效果
bloomComposer.addPass(effectFXAA); // 去掉锯齿
// 着色器通道容器–放进容器里
const finalPass = new ShaderPass(
new THREE.ShaderMaterial({
uniforms: {
baseTexture: { value: null },
// finalPass读去了bloomComposer生成的texture(EffectComposer默认生成两张texture,一张用来读,一张用来写,renderTarget2.texture对应写入的那张)
bloomTexture: { value: bloomComposer.renderTarget2.texture },
tDiffuse: { value: null },
glowColor: { value: null },
},
vertexShader: vertexshader,
fragmentShader: fragmentshader,
defines: {},
}),
'baseTexture',
);
finalPass.needsSwap = true;
finalPass.material.uniforms.glowColor.value = new THREE.Color();
finalComposer = new EffectComposer(renderer);
finalComposer.addPass(renderScene);
finalComposer.addPass(finalPass);
finalComposer.addPass(effectFXAA);
};
2. 实现渲染函数 render()
方案一: 相机分层渲染
const render2 = () => {
camera.layers.set(BLOOM_SCENE);
bloomComposer.render();
camera.layers.set(ENTIRE_SCENE);
finalComposer.render();
requestAnimationFrame(render2);
};
方案二: 相机不分层渲染
const render1 = () => {
scene.traverse(darkenNonBloomed);
bloomComposer.render();
scene.traverse(restoreMaterial);
finalComposer.render();
requestAnimationFrame(render1);
};
const bloomIgnore = []; // 跟辉光光晕有关的变量
function darkenNonBloomed(obj) {
if (obj instanceof THREE.Scene) {
// 此处忽略Scene,否则场景背景会被影响
materials.scene = obj.background;
obj.background = null;
// return;
}
if (
obj instanceof THREE.Sprite ||
bloomIgnore.includes(obj.type) ||
(obj.isMesh && bloomLayer.test(obj.layers) === false) // 判断与辉光是否同层
) {
materials[obj.uuid] = obj.material;
// 将不需要辉光的材质设置为黑色
obj.material = darkMaterial;
}
}
function restoreMaterial(obj) {
if (obj instanceof THREE.Scene) {
obj.background = materials.scene;
delete materials.scene;
return;
}
if (materials[obj.uuid]) {
obj.material = materials[obj.uuid];
delete materials[obj.uuid];
}
}
四、解决添加辉光效果之后背景变成黑色
寻找解决办法的时候有看到一种方法是修改three库中UnrealBloomPass.js的代码,我尝试之后并没有解决。
可以解决背景问题的是方法是修改着色器中fragmentshader
为:
const fragmentshader = `uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
vec4 base_color = texture2D(baseTexture, vUv);
vec4 bloom_color = texture2D(bloomTexture, vUv);
float lum = 0.21 * bloom_color.r + 0.71 * bloom_color.g + 0.07 * bloom_color.b;
gl_FragColor = vec4(base_color.rgb + bloom_color.rgb, max(base_color.a, lum));
}`;
最后结果为:
参考:
zhuanlan.zhihu.com/p/652824252
完整代码:
<template>
<div id="three" class="three" ref="three"></div>
</template>
<script setup>
import * as THREE from 'three';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; // 轨道控制
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; // 效果合成器–EffectComposer依赖RenderPass.js、ShaderPass.js、CopyShader.js库
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'; // 虚幻效果通道
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; // 渲染通道
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; // 着色器通道
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; // 抗锯齿着色器
import { onMounted } from 'vue';
const fragmentshader = `uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
vec4 base_color = texture2D(baseTexture, vUv);
vec4 bloom_color = texture2D(bloomTexture, vUv);
float lum = 0.21 * bloom_color.r + 0.71 * bloom_color.g + 0.07 * bloom_color.b;
gl_FragColor = vec4(base_color.rgb + bloom_color.rgb, max(base_color.a, lum));
// gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
}`;
const vertexshader = `varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;
let scene, camera, renderer, bloomComposer, finalComposer;
const ENTIRE_SCENE = 0, // 全部的,整个的场景
BLOOM_SCENE = 1; // 光晕场景
const bloomLayer = new THREE.Layers(); // 光晕层次-创建一个图层对象
bloomLayer.set(BLOOM_SCENE); // 先把光晕层次设置光晕场景的层次1
const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' }); // 跟辉光光晕有关的变量
const materials = {}; // 跟辉光光晕有关的变量
const params = {
exposure: 1, // 暴露
bloomStrength: 1.5, // 光晕强度
bloomThreshold: 0, // 光晕阈值
bloomRadius: 0, // 光晕半径
};
const init = (el) => {
// 场景
scene = new THREE.Scene();
// 相机 el.offsetWidth, el.offsetHeight
camera = new THREE.PerspectiveCamera(70, el.offsetWidth / el.offsetHeight, 1, 100000);
camera.position.set(50, 50, 50);
camera.position.y = 50;
// 渲染器
renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
alpha: true,
});
renderer.setClearColor(0x000000, 0); // 设置背景颜色
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(el.offsetWidth, el.offsetHeight);
renderer.toneMapping = THREE.ReinhardToneMapping;
el.appendChild(renderer.domElement);
// 环境光
const light = new THREE.AmbientLight(0xffffff, 0.6);
light.layers.enable(ENTIRE_SCENE);
light.layers.enable(BLOOM_SCENE);
scene.add(light);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.AxesHelper(100));
window.onresize = () => {
renderer.setSize(el.offsetWidth, el.offsetHeight);
camera.aspect = el.offsetWidth / el.offsetHeight;
camera.updateProjectionMatrix();
};
};
// 添加演示物体
function addCubes1() {
const geometry = new THREE.BoxGeometry(20, 20, 10);
// 正常方块
const normalMtl = new THREE.MeshLambertMaterial({ color: 0x35c5ea });
const normalBox = new THREE.Mesh(geometry, normalMtl);
normalBox.position.z = -30;
// normalBox.layers.enable(ENTIRE_SCENE); // 这行代码其实没有卵用,如果没有创建layer他会自动创建 设置图层为0,网格模型1默认图层是0,对应掩码是1
console.log('查看网格模型默认图层掩码值normalBox', normalBox.layers.mask);
scene.add(normalBox);
// 通过Three.js渲染器渲染场景的时候,场景中的模型对象图层必须和相机对象的图层一样,模型对象才会被渲染出来,一般默认情况下,网格Mesh等模型对象和相机对象Camera默认的图层都是0,具体点说就是它们的图层属性.layers值Layers对象表示的图层都是0,0层对应的.mask属性值是1.
// 发光方块
const bloomMtl = new THREE.MeshLambertMaterial({ color: 0x35c5ea });
const bloomBox = new THREE.Mesh(geometry, bloomMtl);
bloomBox.position.z = 30;
bloomBox.layers.enable(BLOOM_SCENE);
console.log('查看网格模型默认图层掩码值bloomBox', bloomBox.layers.mask);
scene.add(bloomBox);
}
function addCubes() {
// 创建两个box, 将box进行layers进行分层是重要代码,camera默认渲染0层
let texture = new THREE.TextureLoader().load('/src/assets/images/crate.gif');
let texture1 = new THREE.TextureLoader().load('/src/assets/images/crate.gif');
var geometry1 = new THREE.BoxGeometry(20, 20, 20);
var material1 = new THREE.MeshBasicMaterial({
map: texture,
});
var cube1 = new THREE.Mesh(geometry1, material1);
// 重要代码,将当前创建的box分配到0层
// cube1.layers.enable(ENTIRE_SCENE); // 这行代码其实没有卵用,如果没有创建layer他会自动创建 设置图层为0,网格模型1默认图层是0,对应掩码是1
console.log('查看网格模型默认图层掩码值cube1', cube1.layers.mask);
cube1.position.set(-30, 0, 0);
scene.add(cube1);
// 通过Three.js渲染器渲染场景的时候,场景中的模型对象图层必须和相机对象的图层一样,模型对象才会被渲染出来,一般默认情况下,网格Mesh等模型对象和相机对象Camera默认的图层都是0,具体点说就是它们的图层属性.layers值Layers对象表示的图层都是0,0层对应的.mask属性值是1.
// 发光方块
var geometry2 = new THREE.BoxGeometry(20, 20, 20);
var material2 = new THREE.MeshBasicMaterial({
map: texture1,
});
var cube2 = new THREE.Mesh(geometry2, material2);
cube2.position.set(30, 0, 0);
cube2.layers.enable(BLOOM_SCENE);
console.log('查看网格模型默认图层掩码值cube2', cube2.layers.mask);
scene.add(cube2);
}
function changeBloom() {
const gui = new GUI();
gui.add(params, 'exposure', 0.1, 2).onChange(function (value) {
renderer.toneMappingExposure = Math.pow(value, 4.0);
});
gui.add(params, 'bloomThreshold', 0.0, 1.0).onChange(function (value) {
bloomPass.threshold = Number(value);
});
gui.add(params, 'bloomStrength', 0.0, 3.0).onChange(function (value) {
bloomPass.strength = Number(value);
});
gui
.add(params, 'bloomRadius', 0.0, 1.0)
.step(0.01)
.onChange(function (value) {
bloomPass.radius = Number(value);
});
}
let bloomPass;
const initComposer = (el) => {
// 去掉锯齿—1
// 通过ShaderPass构造函数把FXAAShader着色器和uniforms构成的对象作为参数,创建一个锯齿通道FXAAShaderPass,然后把锯齿通道插入到composer中。
const effectFXAA = new ShaderPass(FXAAShader);
effectFXAA.uniforms['resolution'].value.set(0.6 / window.innerWidth, 0.6 / window.innerHeight); // 渲染区域Canvas画布宽高度 不一定是全屏,也可以是区域值
effectFXAA.renderToScreen = true;
// 去掉锯齿—1
const renderScene = new RenderPass(scene, camera); // RenderPass这个通道会在当前场景(scene)和摄像机(camera)的基础上渲染出一个新场景,新建:
// 添加光晕效果—2
bloomPass = new UnrealBloomPass( // UnrealBloomPass通道可实现一个泛光效果。
new THREE.Vector2(el.offsetWidth, el.offsetHeight),
1.5,
0,
0.85,
);
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;
// 着色器通道容器–放进容器里
bloomComposer = new EffectComposer(renderer); // EffectComposer可以理解为着色器通道容器,着色器通道按照先后顺序添加进来并执行
// bloomComposer.renderToScreen = true;
bloomComposer.addPass(renderScene);
bloomComposer.addPass(bloomPass); // 添加光晕效果
bloomComposer.addPass(effectFXAA); // 去掉锯齿
// 着色器通道容器–放进容器里
const finalPass = new ShaderPass(
new THREE.ShaderMaterial({
uniforms: {
baseTexture: { value: null },
// finalPass读去了bloomComposer生成的texture(EffectComposer默认生成两张texture,一张用来读,一张用来写,renderTarget2.texture对应写入的那张)
bloomTexture: { value: bloomComposer.renderTarget2.texture },
tDiffuse: { value: null },
glowColor: { value: null },
},
vertexShader: vertexshader,
fragmentShader: fragmentshader,
defines: {},
}),
'baseTexture',
);
finalPass.needsSwap = true;
finalPass.material.uniforms.glowColor.value = new THREE.Color();
finalComposer = new EffectComposer(renderer);
finalComposer.addPass(renderScene);
finalComposer.addPass(finalPass);
finalComposer.addPass(effectFXAA);
};
// 不采用相机分层渲染
const render1 = () => {
scene.traverse(darkenNonBloomed);
bloomComposer.render();
scene.traverse(restoreMaterial);
finalComposer.render();
requestAnimationFrame(render1);
};
// 相机分层渲染
const render2 = () => {
camera.layers.set(BLOOM_SCENE);
bloomComposer.render();
camera.layers.set(ENTIRE_SCENE);
finalComposer.render();
requestAnimationFrame(render2);
};
const bloomIgnore = []; // 跟辉光光晕有关的变量
function darkenNonBloomed(obj) {
if (obj instanceof THREE.Scene) {
// 此处忽略Scene,否则场景背景会被影响
materials.scene = obj.background;
obj.background = null;
// return;
}
if (
obj instanceof THREE.Sprite || // 此处忽略Sprite
bloomIgnore.includes(obj.type) ||
(obj.isMesh && bloomLayer.test(obj.layers) === false) // 判断与辉光是否同层
) {
materials[obj.uuid] = obj.material;
// 将不需要辉光的材质设置为黑色
obj.material = darkMaterial;
}
}
function restoreMaterial(obj) {
if (obj instanceof THREE.Scene) {
obj.background = materials.scene;
delete materials.scene;
return;
}
if (materials[obj.uuid]) {
obj.material = materials[obj.uuid];
delete materials[obj.uuid];
}
}
const main = () => {
const el = document.getElementById('three');
init(el);
initComposer(el);
addCubes();
addCubes1();
changeBloom();
};
onMounted(() => {
main();
render2();
});
</script>
<style>
.three {
position: relative;
width: 800px;
height: 600px;
background: red;
}
</style>