ThreeJS 中的 GLSL 将 FragColor 与 UnrealBloom 混合以获得选择性发光
GLSL In ThreeJS Mix FragColor With UnrealBloom To Get Selective Glow
我想使用 Emission 贴图在 ThreeJS 中为导入的 GLTF 模型实现选择性绽放。
为了实现这一点,我应该首先使不应该绽放的对象完全变黑,然后使用 UnrealBloomPass 和 ShaderPass 我将以某种方式将绽放和非绽放效果混合在一起。
我需要使用我不太熟悉的 GLSL 代码。这是我的基本设置:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Render View</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body id="body" style="overflow: hidden">
<h1 id="info">loading scene, this might take a few seconds..</h1>
<!-- Warning: xhr progress seems to not work over cnd.jsdelivr -->
<!-- <div id="loading" id="myProgress"><div id="myBar"></div></div> -->
<input class="tool" id="backgroundColor" type="color" value="#2e2e2e">
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
}
</script>
<script type="module">
import * as THREE from 'https://threejs.org/build/three.module.js'
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js'
import { GUI } from 'https://threejs.org/examples/jsm/libs/dat.gui.module.js'
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'https://threejs.org/examples/jsm/loaders/GLTFLoader.js'
import { RGBELoader } from 'https://threejs.org/examples/jsm/loaders/RGBELoader.js'
import { EffectComposer } from 'https://threejs.org/examples/jsm/postprocessing/EffectComposer.js';
import { ShaderPass } from 'https://threejs.org/examples/jsm/postprocessing/ShaderPass.js';
import { RenderPass } from 'https://threejs.org/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'https://threejs.org/examples/jsm/postprocessing/UnrealBloomPass.js';
function $(e){return document.getElementById(e)}
function createContainer() {
var ctn = document.createElement( 'div' )
document.body.appendChild( ctn )
return ctn
}
function createScene() {
var scn = new THREE.Scene()
scn.fog = new THREE.Fog( new THREE.Color("rgb(100, 100, 100)"), 40, 150 )
return scn
}
function createCamera() {
var cam = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 )
cam.position.set( 20, 12, 20 )
return cam
}
function createRenderer() {
var rnd = new THREE.WebGLRenderer( { antialias: true } )
rnd.setPixelRatio( window.devicePixelRatio )
rnd.setSize( window.innerWidth, window.innerHeight )
rnd.toneMapping = THREE.ReinhardToneMapping
rnd.outputEncoding = THREE.sRGBEncoding
rnd.shadowMap.enabled = true
rnd.shadowMap.type = THREE.PCFSoftShadowMap
container.appendChild( rnd.domElement )
return rnd
}
function createControls() {
var ctr = new OrbitControls( camera, renderer.domElement )
ctr.minDistance = 1
ctr.maxDistance = 50
ctr.enablePan = true
ctr.enableZoom = true
ctr.enableDamping = true
ctr.dampingFactor = 0.1
ctr.rotateSpeed = 0.5
return ctr
}
function createDirectionalLight() {
var drt = new THREE.DirectionalLight( new THREE.Color("rgb(255, 255, 255)"), 1 )
drt.castShadow = true
drt.shadow.camera.top = 64
drt.shadow.camera.top = 64
drt.shadow.camera.bottom = - 64
drt.shadow.camera.left = - 64
drt.shadow.camera.right = 64
drt.shadow.camera.near = 0.2
drt.shadow.camera.far = 80
drt.shadow.camera.far = 80
drt.shadow.bias = - 0.002
drt.position.set( 0, 20, 20 )
drt.shadow.mapSize.width = 1024*8
drt.shadow.mapSize.height = 1024*8
// scene.add( new THREE.CameraHelper( drt.shadow.camera ) )
scene.add( drt )
return drt
}
function createSceneBounds() {
var cube = new THREE.Mesh( new THREE.BoxGeometry( 20, 20, 20 ) )
cube.position.y = 0
cube.visible = false
scene.add(cube)
return new THREE.Box3().setFromObject( cube );
}
function createBloomPass() {
var blp = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 )
blp.threshold = params.bloomThreshold
blp.strength = params.bloomStrength
blp.radius = params.bloomRadius
return blp
}
function createFinalPass() {
var shp = new ShaderPass(
new THREE.ShaderMaterial( {
uniforms: {
baseTexture: { value: null },
bloomTexture: { value: bloomComposer.renderTarget2.texture }
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
defines: {}
} ), "baseTexture"
);
shp.needsSwap = true
return shp
}
function createEnvironment( hdr, onLoad ) {
new RGBELoader().setDataType( THREE.UnsignedByteType ).load( hdr, function ( texture ) {
const pmremGenerator = new THREE.PMREMGenerator( renderer )
pmremGenerator.compileEquirectangularShader()
const envMap = pmremGenerator.fromEquirectangular( texture ).texture
scene.environment = envMap
texture.dispose()
pmremGenerator.dispose()
onLoad()
} );
}
function loadGLTF( file, onLoad ) {
new GLTFLoader().load( file , onLoad, function( xhr ) {
// Warning: xhr progress seems to not work over cnd.jsdelivr
// if ( xhr.lengthComputable ) {
// var percentComplete = xhr.loaded / xhr.total * 100
// var elem = document.getElementById("myBar");
// elem.style.width = Math.round(percentComplete, 2) + "%";
// console.log( "Loading Model - " + Math.round(percentComplete, 2) + "%" )
// }
})
}
const params = {
exposure: 1,
bloomStrength: 5,
bloomThreshold: 0,
bloomRadius: 0,
scene: "Scene with Glow"
};
const container = createContainer()
const scene = createScene()
const camera = createCamera()
const renderer = createRenderer()
const controls = createControls()
const directionalLight = createDirectionalLight()
const sceneBounds = createSceneBounds()
const renderScene = new RenderPass( scene, camera )
const bloomPass = createBloomPass()
const bloomComposer = new EffectComposer( renderer )
bloomComposer.addPass( renderScene )
bloomComposer.addPass( bloomPass )
const finalPass = createFinalPass()
const finalComposer = new EffectComposer( renderer )
finalComposer.addPass( renderScene )
finalComposer.addPass( finalPass )
var model = null
var importedMaterial = null
var emissiveMaterial = null
var mesh = null
var meshBounds = null
createEnvironment( "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/forest.hdr", function() {
loadGLTF( "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/turntable_121.glb", function ( gltf ) {
model = gltf.scene
model.traverse( function ( child ) {
if ( child.isMesh ) {
mesh = child
// enable shadows
mesh.castShadow = true
mesh.receiveShadow = true
// set original material
importedMaterial = mesh.material
importedMaterial.envMapIntensity = 1
// assign temporary black material
mesh.material = new THREE.MeshBasicMaterial({color: 0x000000})
// assign bloom only material
new THREE.TextureLoader()
.load("https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/RecordPlayer_Emission.jpeg",
function( texture ) {
texture.flipY = false
texture.encoding = THREE.sRGBEncoding
emissiveMaterial = new THREE.MeshBasicMaterial({map: texture});
mesh.material = emissiveMaterial
})
}
});
fitObjectToSceneBounds()
scene.add( model )
$("info").style.display = "none"
// Warning: xhr progress seems to not work over cnd.jsdelivr
// $("loading").style.display = "none"
animate()
})
})
const animate = function () {
requestAnimationFrame( animate )
// set background color
scene.background = new THREE.Color( new THREE.Color( $("backgroundColor").value ) )
$('body').attributes['style'].textContent='background-color:'+ $("backgroundColor").value
controls.update()
bloomComposer.render()
// finalComposer.render()
};
window.addEventListener( 'resize', function () {
const width = window.innerWidth
const height = window.innerHeight
renderer.setSize( width, height )
bloomComposer.setSize( width, height );
finalComposer.setSize( width, height );
camera.aspect = width / height
camera.updateProjectionMatrix()
})
function fitCameraToSelection( camera, controls, selection, fitOffset = 1 ) {
const box = new THREE.Box3()
try {
for( const object of selection ) {
box.expandByObject( object )
}
} catch( e ) { box.expandByObject( selection ) }
const size = box.getSize( new THREE.Vector3() )
const center = box.getCenter( new THREE.Vector3() )
const maxSize = Math.max( size.x, size.y, size.z )
const fitHeightDistance = maxSize / ( 1.7 * Math.atan( Math.PI * camera.fov / 360 ) )
const fitWidthDistance = fitHeightDistance / camera.aspect
const distance = fitOffset * Math.max( fitHeightDistance, fitWidthDistance )
const direction = controls.target.clone().sub( camera.position ).normalize().multiplyScalar( distance )
controls.maxDistance = distance * 10
controls.target.copy( center )
camera.near = distance / 100
camera.far = distance * 100
camera.updateProjectionMatrix()
camera.position.copy( controls.target ).sub(direction);
controls.update()
}
function fitObjectToSceneBounds() {
meshBounds = new THREE.Box3().setFromObject( model )
let lengthSceneBounds = {
x: Math.abs(sceneBounds.max.x - sceneBounds.min.x),
y: Math.abs(sceneBounds.max.y - sceneBounds.min.y),
z: Math.abs(sceneBounds.max.z - sceneBounds.min.z),
};
let lengthMeshBounds = {
x: Math.abs(meshBounds.max.x - meshBounds.min.x),
y: Math.abs(meshBounds.max.y - meshBounds.min.y),
z: Math.abs(meshBounds.max.z - meshBounds.min.z),
};
let lengthRatios = [
(lengthSceneBounds.x / lengthMeshBounds.x),
(lengthSceneBounds.y / lengthMeshBounds.y),
(lengthSceneBounds.z / lengthMeshBounds.z),
];
let minRatio = Math.min(...lengthRatios)
let padding = 0
minRatio -= padding
model.scale.set(minRatio, minRatio, minRatio)
}
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 ).step( 0.001 ).onChange( function ( value ) {
bloomPass.threshold = Number( value );
} );
gui.add( params, 'bloomStrength', 0.0, 20.0 ).step( 0.01 ).onChange( function ( value ) {
bloomPass.strength = Number( value );
} );
gui.add( params, 'bloomRadius', 0.0, 5.0 ).step( 0.01 ).onChange( function ( value ) {
bloomPass.radius = Number( value );
} );
</script>
</body>
</html>
当您查看 JSFiddle 中的结果时,您会发现光晕清晰可见,而对象的其余部分是黑色的。
现在我需要知道如何使用 GLSL 将片段着色器与 UnrealBloomPass 结合起来,以及如何将这两种结果混合在一起。但是我真的不知道该怎么做,因为我对 GLSL 没有太多经验,而且我不知道如何将它与 ThreeJS 结合使用。
我怎样才能使选择性绽放起作用?
选择性绽放的顺序还是一样:
- 使所有非泛光对象完全变黑
- 使用 bloomComposer 渲染场景
- 恢复materials/colors到之前的
- 使用 finalComposer 渲染场景
补丁模型的 material,具有共同的制服,表明将使用哪个渲染器:
body{
overflow: hidden;
margin: 0;
}
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
}
</script>
<script type="module">
console.clear();
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.127.0/build/three.module.js';
import {GLTFLoader} from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/controls/OrbitControls.js';
import { EffectComposer } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/ShaderPass.js';
import { UnrealBloomPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/UnrealBloomPass.js';
let model;
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100);
camera.position.set(7.7, 2.5, 7.2);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
//renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener("change", e => {console.log(camera.position)})
let light = new THREE.DirectionalLight(0xffffff, 1.5);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));
let uniforms = {
globalBloom: {value: 1}
}
let loader = new GLTFLoader();
loader.load( "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/turntable_121.glb", function ( gltf ) {
model = gltf.scene;
let emssvTex = new THREE.TextureLoader().load("https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/RecordPlayer_Emission.jpeg", function( texture ) {
texture.flipY = false
texture.encoding = THREE.sRGBEncoding
})
model.traverse( function ( child ) {
if ( child.isMesh ) {
child.material.emissiveMap = emssvTex;
child.material.onBeforeCompile = shader => {
shader.uniforms.globalBloom = uniforms.globalBloom;
shader.fragmentShader = `
uniform float globalBloom;
${shader.fragmentShader}
`.replace(
`#include <dithering_fragment>`,
`#include <dithering_fragment>
if (globalBloom > 0.5){
gl_FragColor = texture2D( emissiveMap, vUv );
}
`
);
console.log(shader.fragmentShader);
}
}
});
model.scale.setScalar(40);
scene.add(model);
});
// bloom
const renderScene = new RenderPass( scene, camera );
const bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 2, 0, 0 );
const bloomComposer = new EffectComposer( renderer );
bloomComposer.renderToScreen = false;
bloomComposer.addPass( renderScene );
bloomComposer.addPass( bloomPass );
const finalPass = new ShaderPass(
new THREE.ShaderMaterial( {
uniforms: {
baseTexture: { value: null },
bloomTexture: { value: bloomComposer.renderTarget2.texture }
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
defines: {}
} ), "baseTexture"
);
finalPass.needsSwap = true;
const finalComposer = new EffectComposer( renderer );
finalComposer.addPass( renderScene );
finalComposer.addPass( finalPass );
window.onresize = function () {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( innerWidth, innerHeight );
bloomComposer.setSize( innerWidth, innerHeight );
finalComposer.setSize( innerWidth, innerHeight );
};
renderer.setAnimationLoop( _ => {
renderer.setClearColor(0x000000);
uniforms.globalBloom.value = 1;
bloomComposer.render();
renderer.setClearColor(0x404040);
uniforms.globalBloom.value = 0;
finalComposer.render();
//renderer.render(scene, camera);
})
</script>
我想使用 Emission 贴图在 ThreeJS 中为导入的 GLTF 模型实现选择性绽放。
为了实现这一点,我应该首先使不应该绽放的对象完全变黑,然后使用 UnrealBloomPass 和 ShaderPass 我将以某种方式将绽放和非绽放效果混合在一起。
我需要使用我不太熟悉的 GLSL 代码。这是我的基本设置:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Render View</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body id="body" style="overflow: hidden">
<h1 id="info">loading scene, this might take a few seconds..</h1>
<!-- Warning: xhr progress seems to not work over cnd.jsdelivr -->
<!-- <div id="loading" id="myProgress"><div id="myBar"></div></div> -->
<input class="tool" id="backgroundColor" type="color" value="#2e2e2e">
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
}
</script>
<script type="module">
import * as THREE from 'https://threejs.org/build/three.module.js'
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js'
import { GUI } from 'https://threejs.org/examples/jsm/libs/dat.gui.module.js'
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'https://threejs.org/examples/jsm/loaders/GLTFLoader.js'
import { RGBELoader } from 'https://threejs.org/examples/jsm/loaders/RGBELoader.js'
import { EffectComposer } from 'https://threejs.org/examples/jsm/postprocessing/EffectComposer.js';
import { ShaderPass } from 'https://threejs.org/examples/jsm/postprocessing/ShaderPass.js';
import { RenderPass } from 'https://threejs.org/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'https://threejs.org/examples/jsm/postprocessing/UnrealBloomPass.js';
function $(e){return document.getElementById(e)}
function createContainer() {
var ctn = document.createElement( 'div' )
document.body.appendChild( ctn )
return ctn
}
function createScene() {
var scn = new THREE.Scene()
scn.fog = new THREE.Fog( new THREE.Color("rgb(100, 100, 100)"), 40, 150 )
return scn
}
function createCamera() {
var cam = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 )
cam.position.set( 20, 12, 20 )
return cam
}
function createRenderer() {
var rnd = new THREE.WebGLRenderer( { antialias: true } )
rnd.setPixelRatio( window.devicePixelRatio )
rnd.setSize( window.innerWidth, window.innerHeight )
rnd.toneMapping = THREE.ReinhardToneMapping
rnd.outputEncoding = THREE.sRGBEncoding
rnd.shadowMap.enabled = true
rnd.shadowMap.type = THREE.PCFSoftShadowMap
container.appendChild( rnd.domElement )
return rnd
}
function createControls() {
var ctr = new OrbitControls( camera, renderer.domElement )
ctr.minDistance = 1
ctr.maxDistance = 50
ctr.enablePan = true
ctr.enableZoom = true
ctr.enableDamping = true
ctr.dampingFactor = 0.1
ctr.rotateSpeed = 0.5
return ctr
}
function createDirectionalLight() {
var drt = new THREE.DirectionalLight( new THREE.Color("rgb(255, 255, 255)"), 1 )
drt.castShadow = true
drt.shadow.camera.top = 64
drt.shadow.camera.top = 64
drt.shadow.camera.bottom = - 64
drt.shadow.camera.left = - 64
drt.shadow.camera.right = 64
drt.shadow.camera.near = 0.2
drt.shadow.camera.far = 80
drt.shadow.camera.far = 80
drt.shadow.bias = - 0.002
drt.position.set( 0, 20, 20 )
drt.shadow.mapSize.width = 1024*8
drt.shadow.mapSize.height = 1024*8
// scene.add( new THREE.CameraHelper( drt.shadow.camera ) )
scene.add( drt )
return drt
}
function createSceneBounds() {
var cube = new THREE.Mesh( new THREE.BoxGeometry( 20, 20, 20 ) )
cube.position.y = 0
cube.visible = false
scene.add(cube)
return new THREE.Box3().setFromObject( cube );
}
function createBloomPass() {
var blp = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 )
blp.threshold = params.bloomThreshold
blp.strength = params.bloomStrength
blp.radius = params.bloomRadius
return blp
}
function createFinalPass() {
var shp = new ShaderPass(
new THREE.ShaderMaterial( {
uniforms: {
baseTexture: { value: null },
bloomTexture: { value: bloomComposer.renderTarget2.texture }
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
defines: {}
} ), "baseTexture"
);
shp.needsSwap = true
return shp
}
function createEnvironment( hdr, onLoad ) {
new RGBELoader().setDataType( THREE.UnsignedByteType ).load( hdr, function ( texture ) {
const pmremGenerator = new THREE.PMREMGenerator( renderer )
pmremGenerator.compileEquirectangularShader()
const envMap = pmremGenerator.fromEquirectangular( texture ).texture
scene.environment = envMap
texture.dispose()
pmremGenerator.dispose()
onLoad()
} );
}
function loadGLTF( file, onLoad ) {
new GLTFLoader().load( file , onLoad, function( xhr ) {
// Warning: xhr progress seems to not work over cnd.jsdelivr
// if ( xhr.lengthComputable ) {
// var percentComplete = xhr.loaded / xhr.total * 100
// var elem = document.getElementById("myBar");
// elem.style.width = Math.round(percentComplete, 2) + "%";
// console.log( "Loading Model - " + Math.round(percentComplete, 2) + "%" )
// }
})
}
const params = {
exposure: 1,
bloomStrength: 5,
bloomThreshold: 0,
bloomRadius: 0,
scene: "Scene with Glow"
};
const container = createContainer()
const scene = createScene()
const camera = createCamera()
const renderer = createRenderer()
const controls = createControls()
const directionalLight = createDirectionalLight()
const sceneBounds = createSceneBounds()
const renderScene = new RenderPass( scene, camera )
const bloomPass = createBloomPass()
const bloomComposer = new EffectComposer( renderer )
bloomComposer.addPass( renderScene )
bloomComposer.addPass( bloomPass )
const finalPass = createFinalPass()
const finalComposer = new EffectComposer( renderer )
finalComposer.addPass( renderScene )
finalComposer.addPass( finalPass )
var model = null
var importedMaterial = null
var emissiveMaterial = null
var mesh = null
var meshBounds = null
createEnvironment( "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/forest.hdr", function() {
loadGLTF( "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/turntable_121.glb", function ( gltf ) {
model = gltf.scene
model.traverse( function ( child ) {
if ( child.isMesh ) {
mesh = child
// enable shadows
mesh.castShadow = true
mesh.receiveShadow = true
// set original material
importedMaterial = mesh.material
importedMaterial.envMapIntensity = 1
// assign temporary black material
mesh.material = new THREE.MeshBasicMaterial({color: 0x000000})
// assign bloom only material
new THREE.TextureLoader()
.load("https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/RecordPlayer_Emission.jpeg",
function( texture ) {
texture.flipY = false
texture.encoding = THREE.sRGBEncoding
emissiveMaterial = new THREE.MeshBasicMaterial({map: texture});
mesh.material = emissiveMaterial
})
}
});
fitObjectToSceneBounds()
scene.add( model )
$("info").style.display = "none"
// Warning: xhr progress seems to not work over cnd.jsdelivr
// $("loading").style.display = "none"
animate()
})
})
const animate = function () {
requestAnimationFrame( animate )
// set background color
scene.background = new THREE.Color( new THREE.Color( $("backgroundColor").value ) )
$('body').attributes['style'].textContent='background-color:'+ $("backgroundColor").value
controls.update()
bloomComposer.render()
// finalComposer.render()
};
window.addEventListener( 'resize', function () {
const width = window.innerWidth
const height = window.innerHeight
renderer.setSize( width, height )
bloomComposer.setSize( width, height );
finalComposer.setSize( width, height );
camera.aspect = width / height
camera.updateProjectionMatrix()
})
function fitCameraToSelection( camera, controls, selection, fitOffset = 1 ) {
const box = new THREE.Box3()
try {
for( const object of selection ) {
box.expandByObject( object )
}
} catch( e ) { box.expandByObject( selection ) }
const size = box.getSize( new THREE.Vector3() )
const center = box.getCenter( new THREE.Vector3() )
const maxSize = Math.max( size.x, size.y, size.z )
const fitHeightDistance = maxSize / ( 1.7 * Math.atan( Math.PI * camera.fov / 360 ) )
const fitWidthDistance = fitHeightDistance / camera.aspect
const distance = fitOffset * Math.max( fitHeightDistance, fitWidthDistance )
const direction = controls.target.clone().sub( camera.position ).normalize().multiplyScalar( distance )
controls.maxDistance = distance * 10
controls.target.copy( center )
camera.near = distance / 100
camera.far = distance * 100
camera.updateProjectionMatrix()
camera.position.copy( controls.target ).sub(direction);
controls.update()
}
function fitObjectToSceneBounds() {
meshBounds = new THREE.Box3().setFromObject( model )
let lengthSceneBounds = {
x: Math.abs(sceneBounds.max.x - sceneBounds.min.x),
y: Math.abs(sceneBounds.max.y - sceneBounds.min.y),
z: Math.abs(sceneBounds.max.z - sceneBounds.min.z),
};
let lengthMeshBounds = {
x: Math.abs(meshBounds.max.x - meshBounds.min.x),
y: Math.abs(meshBounds.max.y - meshBounds.min.y),
z: Math.abs(meshBounds.max.z - meshBounds.min.z),
};
let lengthRatios = [
(lengthSceneBounds.x / lengthMeshBounds.x),
(lengthSceneBounds.y / lengthMeshBounds.y),
(lengthSceneBounds.z / lengthMeshBounds.z),
];
let minRatio = Math.min(...lengthRatios)
let padding = 0
minRatio -= padding
model.scale.set(minRatio, minRatio, minRatio)
}
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 ).step( 0.001 ).onChange( function ( value ) {
bloomPass.threshold = Number( value );
} );
gui.add( params, 'bloomStrength', 0.0, 20.0 ).step( 0.01 ).onChange( function ( value ) {
bloomPass.strength = Number( value );
} );
gui.add( params, 'bloomRadius', 0.0, 5.0 ).step( 0.01 ).onChange( function ( value ) {
bloomPass.radius = Number( value );
} );
</script>
</body>
</html>
当您查看 JSFiddle 中的结果时,您会发现光晕清晰可见,而对象的其余部分是黑色的。
现在我需要知道如何使用 GLSL 将片段着色器与 UnrealBloomPass 结合起来,以及如何将这两种结果混合在一起。但是我真的不知道该怎么做,因为我对 GLSL 没有太多经验,而且我不知道如何将它与 ThreeJS 结合使用。
我怎样才能使选择性绽放起作用?
选择性绽放的顺序还是一样:
- 使所有非泛光对象完全变黑
- 使用 bloomComposer 渲染场景
- 恢复materials/colors到之前的
- 使用 finalComposer 渲染场景
补丁模型的 material,具有共同的制服,表明将使用哪个渲染器:
body{
overflow: hidden;
margin: 0;
}
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
}
</script>
<script type="module">
console.clear();
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.127.0/build/three.module.js';
import {GLTFLoader} from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/controls/OrbitControls.js';
import { EffectComposer } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/ShaderPass.js';
import { UnrealBloomPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/UnrealBloomPass.js';
let model;
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100);
camera.position.set(7.7, 2.5, 7.2);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
//renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener("change", e => {console.log(camera.position)})
let light = new THREE.DirectionalLight(0xffffff, 1.5);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));
let uniforms = {
globalBloom: {value: 1}
}
let loader = new GLTFLoader();
loader.load( "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/turntable_121.glb", function ( gltf ) {
model = gltf.scene;
let emssvTex = new THREE.TextureLoader().load("https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/RecordPlayer_Emission.jpeg", function( texture ) {
texture.flipY = false
texture.encoding = THREE.sRGBEncoding
})
model.traverse( function ( child ) {
if ( child.isMesh ) {
child.material.emissiveMap = emssvTex;
child.material.onBeforeCompile = shader => {
shader.uniforms.globalBloom = uniforms.globalBloom;
shader.fragmentShader = `
uniform float globalBloom;
${shader.fragmentShader}
`.replace(
`#include <dithering_fragment>`,
`#include <dithering_fragment>
if (globalBloom > 0.5){
gl_FragColor = texture2D( emissiveMap, vUv );
}
`
);
console.log(shader.fragmentShader);
}
}
});
model.scale.setScalar(40);
scene.add(model);
});
// bloom
const renderScene = new RenderPass( scene, camera );
const bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 2, 0, 0 );
const bloomComposer = new EffectComposer( renderer );
bloomComposer.renderToScreen = false;
bloomComposer.addPass( renderScene );
bloomComposer.addPass( bloomPass );
const finalPass = new ShaderPass(
new THREE.ShaderMaterial( {
uniforms: {
baseTexture: { value: null },
bloomTexture: { value: bloomComposer.renderTarget2.texture }
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
defines: {}
} ), "baseTexture"
);
finalPass.needsSwap = true;
const finalComposer = new EffectComposer( renderer );
finalComposer.addPass( renderScene );
finalComposer.addPass( finalPass );
window.onresize = function () {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( innerWidth, innerHeight );
bloomComposer.setSize( innerWidth, innerHeight );
finalComposer.setSize( innerWidth, innerHeight );
};
renderer.setAnimationLoop( _ => {
renderer.setClearColor(0x000000);
uniforms.globalBloom.value = 1;
bloomComposer.render();
renderer.setClearColor(0x404040);
uniforms.globalBloom.value = 0;
finalComposer.render();
//renderer.render(scene, camera);
})
</script>