How to change my scene background by creating a post-processing BLOOM? - three.js

I am new to THREE.js and i want the object to light up when the mouse rolls over.
But when I change the scene.background color to white by Bloom,
I find that Bloom affects the appearance of the background color.
how do I solve this problem?
it's before
enter image description here
and it's after
enter image description here
here is the complete code
<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="importmap">
{
"imports": {
"three": "./build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from './jsm/controls/OrbitControls.js';
import Stats from './jsm/libs/stats.module.js';
import { GUI } from './jsm/libs/lil-gui.module.min.js';
import { EffectComposer } from './jsm/postprocessing/EffectComposer.js';
import { RenderPass } from './jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from './jsm/postprocessing/UnrealBloomPass.js';
import { FBXLoader } from './Loader/FBXLoader/FBXLoader.js';
import { OBJLoader } from './Loader/OBJLoader.js';
import { MTLLoader } from './Loader/MTLLoader.js'
import { GLTFLoader } from "./Loader/GLTFLoader.js";
import { ShaderPass } from './jsm/postprocessing/ShaderPass.js';
const ENTIRE_SCENE = 0, BLOOM_SCENE = 1;
const bloomLayer = new THREE.Layers();
bloomLayer.set(BLOOM_SCENE);
let params = {
exposure: 1,
bloomThreshold: 0.41,
bloomStrength: 0.66,
bloomRadius: 0.05,
scene: 'Scene with Glow'
};
//set MeshBasicMaterial
const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' });
const materials = {};
/*--------renderer--------*/
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//add scene
const scene = new THREE.Scene();
/*--------camera--------*/
const camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 100000000);
camera.position.set(0, 10, 0);
camera.lookAt(0, 0, 0);
/*--------OrbitControls--------*/
let controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.enablePan = false;
controls.maxPolarAngle = Math.PI / 2;
controls.enableDamping = true;
//Render
const renderScene = new RenderPass(scene, camera);
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;
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);
/*--------Stats--------*/
const stats = Stats();
stats.showPanel(0);
document.body.appendChild(stats.dom);
/*--------AmbientLight-1--------*/
scene.add(new THREE.AmbientLight(0xffffff, 1));
//light1
const light1 = new THREE.PointLight(0xddffdd, 1);
light1.position.z = 0;
light1.position.y = 300;
light1.position.x = 200;
scene.add(light1);
//GUI
const gui = new GUI();
gui.add(params, 'scene', ['Scene with Glow', 'Scene only']).onChange(function (value) {
switch (value) {
case 'Scene with Glow':
bloomComposer.renderToScreen = false;
break;
case 'Scene only':
// nothing to do
break;
}
});
const folder = gui.addFolder('Bloom Parameters');
folder.add(params, 'exposure', 0.1, 2).onChange(function (value) {
renderer.toneMappingExposure = Math.pow(value, 4.0);
});
folder.add(params, 'bloomThreshold', 0.0, 1.0).onChange(function (value) {
bloomPass.threshold = Number(value);
});
folder.add(params, 'bloomStrength', 0.0, 10.0).onChange(function (value) {
bloomPass.strength = Number(value);
});
folder.add(params, 'bloomRadius', 0.0, 1.0).step(0.01).onChange(function (value) {
bloomPass.radius = Number(value);
});
/*--------scene.background--------*/
scene.background = new THREE.Color(0xffffff);
/*--------cube--------*/
const geometry = new THREE.BoxGeometry(4, 4, 4);
const material = new THREE.MeshBasicMaterial({ color: 0x000050 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
/*--------raycaster--------*/
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
window.addEventListener('mousemove', onMouseMove);
console.log(scene);
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
scene.children[2].layers.set(ENTIRE_SCENE);
if (intersects[0]) {
const intersects_object_name = intersects[0].object.name;
intersects[0].object.layers.enableAll(BLOOM_SCENE);
}
}
//window.onresize
window.onresize = function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
function disposeMaterial(obj) {
if (obj.material) {
obj.material.dispose();
}
}
//render
function render() {
switch (params.scene) {
case 'Scene only':
renderer.render(scene, camera);
break;
case 'Scene with Glow':
default:
// render scene with bloom
renderBloom(true);
// render the entire scene, then render bloom scene on top
finalComposer.render();
break;
}
}
function renderBloom(mask) {
if (mask === true) {
scene.traverse(darkenNonBloomed);
bloomComposer.render();
scene.traverse(restoreMaterial);
} else {
camera.layers.set(BLOOM_SCENE);
bloomComposer.render();
camera.layers.set(ENTIRE_SCENE);
}
}
function darkenNonBloomed(obj) {
if (obj.isMesh && bloomLayer.test(obj.layers) === false) {
materials[obj.uuid] = obj.material;
obj.material = darkMaterial;
}
}
function restoreMaterial(obj) {
if (materials[obj.uuid]) {
obj.material = materials[obj.uuid];
delete materials[obj.uuid];
}
}
function animate() {
stats.update();
render();
requestAnimationFrame(animate);
};
animate();
</script>
Here are examples if needed
https://smile881225.github.io/AskQuestions/

Try to change this part:
if (mask === true) {
scene.traverse(darkenNonBloomed);
bloomComposer.render();
scene.traverse(restoreMaterial);
} else {
to this:
if (mask === true) {
renderer.setClearColor(0x000000); // all must be black, including background
scene.traverse(darkenNonBloomed);
bloomComposer.render();
renderer.setClearColor(0x006432); // set the color you want
scene.traverse(restoreMaterial);
} else {

Related

How can I project a 2d shader in a 3d object

the title make it looks easy but I'm struggling to get this pie chart shader on my 3d model. For this I'm using three js. here is my code till now:
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Site Prof Matheus</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' type='text/css' media='screen' href='./styles/main.css'>
<script type='module' src='./src/main.js'></script>
</head>
<body>
</body>
</html>
main.js:
import * as THREE from 'https://cdn.skypack.dev/three'
import { OrbitControls } from 'https://cdn.skypack.dev/three-stdlib/controls/OrbitControls'
import { GLTFLoader } from 'https://cdn.skypack.dev/three-stdlib/loaders/GLTFLoader'
import { vertexShader } from '../shaders/vertex.glsl.js'
import { fragmentShader } from '../shaders/fragment.glsl.js'
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const loader = new GLTFLoader();
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor(0x002653, 0.5)
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.setPixelRatio(window.devicePixelRatio);
const r_material = new THREE.ShaderMaterial({ //Roulette material
uniforms: {
iTime: { value: 1.0 },
resolution: { value: new THREE.Vector2 }
},
vertexShader,
fragmentShader
})
loader.load(
'../roulette.glb',
function (gltf) {
gltf.scene.traverse((o) => {
if (o.isMesh) o.material = r_material;
});
scene.add(gltf.scene);
},
function () { },
function (err) {
console.log('error: ' + err);
}
)
camera.position.z = 5;
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = true;
controls.enableDamping = true;
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
animate();
vertex.glsl.js:
const vertexShader = /* glsl */`
void main()
{
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`
// uniform vec3 iResolution;
// uniform int SEGMENTCOUNT;
export { vertexShader };
fragment.glsl.js:
const fragmentShader = /* glsl */`
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
gl_Position
}
`
export { fragmentShader };
and here is my roulette.gbl model
the intended result is to have a shader with colors that I choose, all the parts equal, the colors can repeat and the shader should covers the whole mesh, less the bottom. intended result
PS. i see my object looks a flat plain on the image, i guess just need to add some proper light, here is my object geometry just for sake of curiosity: my mesh geometry
PSS. some antialiasing on the pie chart shader would be very welcome
This is how you can do it, modifying a material with .onBeforeCompile():
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import {
OrbitControls
} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 2000);
camera.position.set(0, 1, 1).setLength(12);
let renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", onWindowResize);
let controls = new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper());
let light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));
let path = new THREE.Path();
path.moveTo(0, -1);
path.lineTo(4, -1);
path.absarc(4, -0.5, 0.5, Math.PI * 1.5, 0);
path.absarc(4, 0.5, 0.5, 0, Math.PI * 0.5);
path.lineTo(0, -0.5);
let g = new THREE.LatheGeometry(path.getPoints(50), 72);
let m = new THREE.MeshLambertMaterial({
color: 0x00ff7f,
//wireframe: true,
onBeforeCompile: shader => {
shader.vertexShader = `
varying vec3 vPos;
${shader.vertexShader}
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
vPos = position;
`
);
//console.log(shader.vertexShader);
shader.fragmentShader = `
#define ss(a, b, c) smoothstep(a, b, c)
varying vec3 vPos;
${shader.fragmentShader}
`.replace(
`vec4 diffuseColor = vec4( diffuse, opacity );`,
`
vec3 col = diffuse;
int N = 37;
float a = atan(vPos.x,-vPos.z)+PI;
float r = PI2/float(N);
float cId = floor(a/r);
vec3 br = mod(cId, 2.) == 0. ? vec3(0) : vec3(1, 0, 0); // black / red
br = cId == 0. ? vec3(0, 0.75, 0) : br; // green
float d = length(vPos.xz);
float fw = length(fwidth(vPos.xz));
col = mix(col, br, ss(3. - fw, 3., d) - ss(4., 4. + fw, d));
col = mix(diffuse, col, clamp(sign(vPos.y), 0., 1.));
vec4 diffuseColor = vec4( col, opacity );
`
);
//console.log(shader.fragmentShader);
}
})
let o = new THREE.Mesh(g, m);
o.position.y = 0.5;
o.rotation.y = Math.PI;
scene.add(o);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
function onWindowResize() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
}
</script>

Display loaded OBJ model in wireframe mode in three.js

I wanted to display my loaded .obj file in wireframe mode..
I got to know about the WireFrameGeometry But for somereason the .mtl texture only gets display .
Below is the code ..
/* Model */
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setBaseUrl('assets/');
mtlLoader.setPath('assets/');
mtlLoader.load('materialfile.mtl', function(materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.setPath('assets/');
objLoader.load('Objectfile.obj', function(object) {
object.traverse(function(child) {
if (child.isMesh) {
var wireframeGeomtry = new THREE.WireframeGeometry(child.geometry);
var wireframeMaterial = new THREE.LineBasicMaterial({
color: 0xffffff
});
var wireframe = new THREE.LineSegments(wireframeGeomtry, wireframeMaterial);
child.add(wireframe);
}
});
scene.add(object);
});
});
I only want the wireframe of the model without any fill..
Thanks in advance.
The entire code is below...
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="three.js"></script>
<script src="TrackballControls.js"></script>
<script src="cannon.js"></script>
<script src="Detector.js"></script>
<script src="OrbitControls.js"></script>
<script src="OBJLoader.js"></script>
<script src="MTLLoader.js"></script>
</head>
<body>
<script>
if (!Detector.webgl) {
Detector.addGetWebGLMessage();
}
var container;
var camera, controls, scene, renderer;
var lighting, ambient, keyLight, fillLight, backLight;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
/* Camera */
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 50;
/* Scene */
scene = new THREE.Scene();
lighting = false;
ambient = new THREE.AmbientLight(0xffffff, 1.0);
scene.add(ambient);
// keyLight = new THREE.DirectionalLight(new THREE.Color('hsl(30, 100%, 75%)'), 1.0);
// keyLight.position.set(-100, 0, 100);
// fillLight = new THREE.DirectionalLight(new THREE.Color('hsl(240, 100%, 75%)'), 0.75);
// fillLight.position.set(100, 0, 100);
// backLight = new THREE.DirectionalLight(0xffffff, 1.0);
// backLight.position.set(100, 0, -100).normalize();
/* Model */
// var mtlLoader = new THREE.MTLLoader();
// mtlLoader.setBaseUrl('assets/');
// mtlLoader.setPath('assets/');
// mtlLoader.load('mtlfile.mtl', function(materials) {
// materials.preload();
// materials.materials.default.map.magFilter = THREE.NearestFilter;
// materials.materials.default.map.minFilter = THREE.LinearFilter;
var objLoader = new THREE.OBJLoader();
// objLoader.setMaterials(materials);
objLoader.setPath('assets/');
objLoader.load('objectfile.obj', function(object) {
object.traverse(function(child) {
if (child.isMesh) {
var wireframeGeomtry = new THREE.WireframeGeometry(child.geometry);
var wireframeMaterial = new THREE.LineBasicMaterial({
color: 0xeeeeee
});
var wireframe = new THREE.LineSegments(wireframeGeomtry, wireframeMaterial);
// add to child so we get same orientation
child.add(wireframe);
// to parent of child. Using attach keeps our orietation
child.parent.attach(wireframe);
// remove child (we don't want child)
child.parent.remove(child);
}
});
scene.add(object);
});
// });
/* Renderer */
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color("hsl(0, 0%, 10%)"));
container.appendChild(renderer.domElement);
/* Controls */
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.enableZoom = true;
controls.autoRotate = true;
/* Events */
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
render();
}
function render() {
renderer.render(scene, camera);
}
</script>
</body>
</html>
So this is the entire code..
Its just the basic obj loader ..
I don't know whether the problem is in the code or model .
It shows as a fully filled white model
object.traverse(function(child) {
if (child.isMesh) {
child.material.wireframe = true;
}
}
This worked for me
objLoader.load('Objectfile.obj', function(object) {
object.traverse(function(child) {
if (child.isMesh) {
var wireframeGeomtry = new THREE.WireframeGeometry(child.geometry);
var wireframeMaterial = new THREE.LineBasicMaterial({
color: 0xffffff
});
var wireframe = new THREE.LineSegments(wireframeGeomtry, wireframeMaterial);
// add to child so we get same orientation
child.add(wireframe);
// to parent of child. Using attach keeps our orietation
child.parent.attach(wireframe);
// remove child (we don't want child)
child.parent.remove(child);
}
});
scene.add(object);
});
Example:
html, body {
margin: 0;
height: 100%;
}
#c {
width: 100%;
height: 100%;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/controls/OrbitControls.js';
import {OBJLoader2} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/loaders/OBJLoader2.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 10, 20);
const controls = new OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
{
const objLoader = new OBJLoader2();
objLoader.load('https://threejsfundamentals.org/threejs/resources/models/windmill/windmill.obj', (root) => {
root.traverse(child => {
if (child.isMesh) {
var wireframeGeomtry = new THREE.WireframeGeometry(child.geometry);
var wireframeMaterial = new THREE.LineBasicMaterial({
color: 0xffffff
});
var wireframe = new THREE.LineSegments(wireframeGeomtry, wireframeMaterial);
child.add(wireframe);
child.parent.attach(wireframe);
child.parent.remove(child);
}
});
scene.add(root);
});
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
You can also do it the way manthrax suggested except you need to check if child.material is an array or not, then for each material remove the map (so the texture is not used) and set emissive to some color (you might also want to set color to black)`.
html, body {
margin: 0;
height: 100%;
}
#c {
width: 100%;
height: 100%;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/controls/OrbitControls.js';
import {OBJLoader2} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/loaders/OBJLoader2.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 10, 20);
const controls = new OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
function makeWireframe(material) {
material.wireframe = true;
material.emissive.set('#0FF');
material.map = undefined;
}
{
const objLoader = new OBJLoader2();
objLoader.load('https://threejsfundamentals.org/threejs/resources/models/windmill/windmill.obj', (root) => {
root.traverse(child => {
if (child.isMesh) {
if (Array.isArray(child.material)) {
child.material.forEach(makeWireframe);
} else {
makeWireframe(child.material)
}
}
});
scene.add(root);
});
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>

How does raycasting in three.js work with an offscreen canvas?

I can't seem to get raycasting to work in an offscreen canvas.
A click event sends data to the worker like this:
var r = document.getElementById('webGL').getBoundingClientRect()
offscreen.postMessage({
action: 'set_scene',
mesh: mesh.toJSON(),
camera: camera.toJSON(),
canvasSize: {
width: document.getElementById('webGL').clientWidth,
height: document.getElementById('webGL').clientHeight
},
coordinates: { x: e.originalEvent.clientX - r.x, y: e.originalEvent.clientY - r.y },
time: (new Date())
});

while the worker looks like this:
self.importScripts( './three.min.js' );
var loader = new THREE.ObjectLoader();
var scene = new THREE.Scene();
self.onmessage = function(e) {
// var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
var canvas = new OffscreenCanvas(e.data.canvasSize.width, e.data.canvasSize.height);
var renderer = new THREE.WebGLRenderer( { antialias: true, canvas: canvas, preserveDrawingBuffer: true } );
Promise.all([
(new Promise(function(resolve, reject) {
loader.parse(
e.data.mesh,
function ( obj ) {
resolve( obj );
})
})),
(new Promise(function(resolve, reject) {
loader.parse(
e.data.camera,
function ( obj ) {
resolve( obj );
})
}))
]).then(obj => {
var mesh, camera
[mesh, camera] = obj;
scene.add( mesh );
renderer.render( scene, camera );
var raycaster = new THREE.Raycaster();
p = { x: e.data.coordinates.x, y: e.data.coordinates.y };
var m = {};
m.x = (p.x) / e.data.canvasSize.width * 2 - 1;
m.y = 1 - (p.y) / e.data.canvasSize.height * 2;
raycaster.setFromCamera( m, camera );
var intersects = raycaster.intersectObjects( [ mesh ], true );
return intersects;
}).then(r => {
self.postMessage(r);
}).catch(e => {
console.log(e);
})
}
Same code onscreen works ok, and the values resulting from the transforms check out ok.
Is it possible to do such a thing at all, or what am I getting wrong?
I don't see the issue in your code but there is absolutely nothing special about picking offscreen. Here's a working example to prove it. It doesn't have any three or camera or mesh in the main page, only in the worker.
All the main page does is start the worker, transfer control of the canvas to the worker, then send resize and mouse events to the worker. That's it. Otherwise the code in the worker is 99% the same as the code would be in the main page. The only major difference is resizeCanvasToDisplaySize gets the display size from state.width and state.height. The non-offscreen version code comes from here
You need to post more code.
function main() {
const canvas = document.querySelector("#c");
if (!canvas.transferControlToOffscreen) {
alert('no offscreen canvas support');
return;
}
const offscreen = canvas.transferControlToOffscreen();
const workerScript = document.querySelector('#foo').text;
const blob = new Blob([workerScript], {type: 'application/javascript'});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
worker.postMessage({type: 'init', canvas: offscreen}, [offscreen]);
function sendSize() {
worker.postMessage({
type: 'size',
width: canvas.clientWidth,
height: canvas.clientHeight,
});
}
sendSize();
window.addEventListener('resize', sendSize);
function getNormaizedMousePosition(element, e) {
const rect = element.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
return {
x: x / canvas.clientWidth * 2 - 1,
y: y / canvas.clientHeight * -2 + 1,
}
}
canvas.addEventListener('mousemove', (e) => {
const pos = getNormaizedMousePosition(canvas, e);
worker.postMessage({
type: 'mousemove',
x: pos.x,
y: pos.y,
});
});
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="foo" id="foo">
'use strict';
importScripts('https://threejsfundamentals.org/threejs/resources/threejs/r103/three.min.js');
/* global THREE */
const state = {
width: 300,
height: 150,
mouse: {
x: -2,
y: -2,
},
};
function init(data) {
const {canvas} = data;
const renderer = new THREE.WebGLRenderer({
canvas
});
state.width = canvas.width;
state.height = canvas.height;
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 4;
const scene = new THREE.Scene();
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function makeInstance(geometry, color, x) {
const material = new THREE.MeshPhongMaterial({
color
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.position.x = x;
return cube;
}
const cubes = [
makeInstance(geometry, 0x44aa88, 0),
makeInstance(geometry, 0x8844aa, -2),
makeInstance(geometry, 0xaa8844, 2),
];
class PickHelper {
constructor() {
this.raycaster = new THREE.Raycaster();
this.pickedObject = null;
this.pickedObjectSavedColor = 0;
}
pick(normalizedPosition, scene, camera, time) {
// restore the color if there is a picked object
if (this.pickedObject) {
this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
this.pickedObject = undefined;
}
// cast a ray through the frustum
this.raycaster.setFromCamera(normalizedPosition, camera);
// get the list of objects the ray intersected
const intersectedObjects = this.raycaster.intersectObjects(scene.children);
if (intersectedObjects.length) {
// pick the first object. It's the closest one
this.pickedObject = intersectedObjects[0].object;
// save its color
this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
// set its emissive color to flashing red/yellow
this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
}
}
}
const pickHelper = new PickHelper();
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = state.width;
const height = state.height;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.width / canvas.height;
camera.updateProjectionMatrix();
}
cubes.forEach((cube, ndx) => {
const speed = 1 + ndx * .1;
const rot = time * speed;
cube.rotation.x = rot;
cube.rotation.y = rot;
});
pickHelper.pick(state.mouse, scene, camera, time);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
function size(data) {
state.width = data.width;
state.height = data.height;
}
function mousemove(data) {
state.mouse.x = data.x;
state.mouse.y = data.y;
}
const handlers = {
init,
size,
mousemove,
};
self.onmessage = function(e) {
const fn = handlers[e.data.type];
if (!fn) {
throw new Error('no handler for type: ' + e.data.type);
}
fn(e.data);
};
</script>
For your particular case though you shouldn't need a camera. Send the ray. You don't need the canvas size either. Picking in three.js is CPU based
function main() {
const canvas = document.querySelector("#c");
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 60;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 200;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 4;
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function makeInstance(geometry, color, x, name) {
const material = new THREE.MeshPhongMaterial({
color
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.name = name;
cube.position.x = x;
cube.rotation.x = x;
cube.rotation.z = x + .7;
return cube;
}
makeInstance(geometry, 0x44aa88, 0, 'cyan cube');
makeInstance(geometry, 0x8844aa, -2, 'purple cube');
makeInstance(geometry, 0xaa8844, 2, 'brown cube');
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.width / canvas.height;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
}
render();
window.addEventListener('resize', render);
const workerScript = document.querySelector('#foo').text;
const blob = new Blob([workerScript], {type: 'application/javascript'});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
const msgElem = document.querySelector('#msg');
worker.onmessage = (e) => {
msgElem.textContent = e.data;
};
worker.postMessage({type: 'init', scene: scene.toJSON()});
function getNormaizedMousePosition(element, e) {
const rect = element.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
return {
x: x / canvas.clientWidth * 2 - 1,
y: y / canvas.clientHeight * -2 + 1,
}
}
const raycaster = new THREE.Raycaster();
canvas.addEventListener('mousemove', (e) => {
const pos = getNormaizedMousePosition(canvas, e);
raycaster.setFromCamera(pos, camera);
worker.postMessage({
type: 'intersect',
origin: raycaster.ray.origin.toArray(),
direction: raycaster.ray.direction.toArray(),
});
});
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
#msg {
position: absolute;
left: 1em;
top: 1em;
}
<canvas id="c"></canvas>
<div id="msg"></div>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r103/three.min.js"></script>
<script type="foo" id="foo">
'use strict';
importScripts('https://threejsfundamentals.org/threejs/resources/threejs/r103/three.min.js');
/* global THREE */
function loadObject(json) {
return new Promise((resolve) => {
const loader = new THREE.ObjectLoader();
loader.parse(json, resolve);
});
}
const renderer = new THREE.WebGLRenderer({
canvas: new OffscreenCanvas(1, 1),
});
// settings not important
const camera = new THREE.PerspectiveCamera(1, 1, 0.1, 100);
const raycaster = new THREE.Raycaster();
let scene;
let lastIntersectedObject;
async function init(data) {
scene = await loadObject(data.scene);
// we only need to render once to init the scene
renderer.render(scene, camera);
}
function intersect(data) {
raycaster.set(
new THREE.Vector3(...data.origin),
new THREE.Vector3(...data.direction));
const intersections = raycaster.intersectObjects(scene.children);
const intersectedObject = intersections.length
? intersections[0].object
: null;
if (intersectedObject !== lastIntersectedObject) {
lastIntersectedObject = intersectedObject;
log('intersection:', lastIntersectedObject ? lastIntersectedObject.name : 'none');
}
}
const handlers = {
init,
intersect,
};
self.onmessage = function(e) {
const fn = handlers[e.data.type];
if (!fn) {
throw new Error('no handler for type: ' + e.data.type);
}
fn(e.data);
};
function log(...args) {
postMessage([...args].join(' '));
}
</script>

Particles follow texture

I have a sphere created with particles in three.js that works perfectly. Now I wanted to put these particles on top of a texture that I have of a world map simulating a 3D planet, I searched the internet but I did not find any information on how to do it, when I put the texture instead of it being outside it ends up getting inside each particle, how could I do that? Any idea ? Thank you all
here is my code
$( document ).ready(function() {
var globe = document.getElementById('globe')
var Maxwidth = window.innerWidth
var Maxheight = window.innerHeight
var scene = new THREE.Scene();
var renderer = new THREE.WebGLRenderer({antilias:true});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(Maxwidth,Maxheight)
globe.appendChild(renderer.domElement)
var camera = new THREE.PerspectiveCamera(60, Maxwidth / Maxheight,1,1000);
camera.position.z = 50;
var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.25;
controls.panningMode = THREE.HorizontalPanning; // default is
THREE.ScreenSpacePanning
controls.maxPolarAngle = Math.PI / 2;
var geometry = new THREE.SphereGeometry( 200, 42, 42 );
geometry.widthSegments = 42;
var colors = [];
for( var i = 0; i < geometry.vertices.length; i++ ) {
// random color
colors[i] = new THREE.Color();
//colors[i].setHSV( Math.random(), 1.0, 1.0 );
}
geometry.colors = colors;
// texture
var texture = new THREE.Texture( generateTexture( ) );
texture.needsUpdate = true; // important
// particle system material
var material = new THREE.ParticleBasicMaterial( {
size: 5,
map: texture,
blending: THREE.AdditiveBlending, // required
depthTest: false, // required
transparent: true,
opacity: 0.7,
vertexColors: true // optional
} );
material.map = THREE.ImageUtils.loadTexture('../img/point_picker.png')
material.anisotropy = 0;
material.magFilter = THREE.NearestFilter;
material.minFilter = THREE.NearestFilter;
var union = new THREE.ParticleSystem( geometry, material );
function generateTexture( ) {
var size = 128;
var canvas = document.createElement( 'canvas' );
canvas.width = size;
canvas.height = size;
var context = canvas.getContext( '2d' );
var centerX = size / 2;
var centerY = size / 2;
var radius = size / 2;
context.beginPath();
context.arc( centerX, centerY, radius, 0, 2 * Math.PI, false );
context.fillStyle = "#FFFFFF";
context.fill();
return canvas;
}
scene.add(union)
renderer.setClearColor(0x2675AD)
renderer.render(scene,camera)
controls.update();
function render(delta){
requestAnimationFrame(render);
renderer.render(scene,camera)
union.rotation.y += 0.0009
}
render()
});
I need something like this
So, this is the option I was talking about in my comment:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(1.25, 7, 7);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0x080808);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geom = new THREE.SphereBufferGeometry(5, 120, 60);
var colors = [];
var color = new THREE.Color();
var q = 0xffffff * 0.25;
for (let i = 0; i < geom.attributes.position.count; i++) {
color.set(Math.random() * q + q * 3);
color.toArray(colors, i * 3);
}
geom.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3));
var loader = new THREE.TextureLoader();
loader.setCrossOrigin('');
var texture = loader.load('https://learningthreejs.com/data/2013-09-16-how-to-make-the-earth-in-webgl/demo/bower_components/threex.planets/images/earthspec1k.jpg');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(1, 1);
var disk = loader.load('https://threejs.org/examples/textures/sprites/circle.png');
var points = new THREE.Points(geom, new THREE.ShaderMaterial({
vertexColors: THREE.VertexColors,
uniforms: {
visibility: {
value: texture
},
shift: {
value: 0
},
shape: {
value: disk
},
size: {
value: 0.125
},
scale: {
value: window.innerHeight / 2
}
},
vertexShader: `
uniform float scale;
uniform float size;
varying vec2 vUv;
varying vec3 vColor;
void main() {
vUv = uv;
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( scale / length( mvPosition.xyz ) );
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform sampler2D visibility;
uniform float shift;
uniform sampler2D shape;
varying vec2 vUv;
varying vec3 vColor;
void main() {
vec2 uv = vUv;
uv.x += shift;
vec4 v = texture2D(visibility, uv);
if (length(v.rgb) > 1.0) discard;
gl_FragColor = vec4( vColor, 1.0 );
vec4 shapeData = texture2D( shape, gl_PointCoord );
if (shapeData.a < 0.5) discard;
gl_FragColor = gl_FragColor * shapeData;
}
`,
transparent: true
}));
scene.add(points);
var blackGlobe = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({
color: 0x000000
}));
blackGlobe.scale.setScalar(0.99);
points.add(blackGlobe);
var clock = new THREE.Clock();
var time = 0;
render();
function render() {
requestAnimationFrame(render);
time += clock.getDelta();
points.material.uniforms.shift.value = time * 0.1;
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.91.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.91.0/examples/js/controls/OrbitControls.js"></script>

Persistence postprocessing in three.js with 2 renderTargets

I am trying to implement this effect. As it is explained in the video, I have to make 2 extra renderTargets, blend the current image with renderTarget #1 into renderTarget #2, but I am having difficulties with implementing it in three.js. You can check my code here
let w = window.innerWidth
let h = window.innerHeight
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
const clock = new THREE.Clock()
let frontBuffer = createRenderTarget()
let backBuffer = frontBuffer.clone()
let readBuffer = frontBuffer
let writeBuffer = backBuffer
const renderScene = new THREE.Scene()
const renderCamera = new THREE.OrthographicCamera(-w / 2, w / 2, -h / 2, h / 2, -1000, 1000)
const renderMaterial = new THREE.ShaderMaterial({
uniforms: {
tDiffuse: { value: writeBuffer.texture }
},
vertexShader: `
varying vec2 vUv;
void main () {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vUv = uv;
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main () {
gl_FragColor = texture2D(tDiffuse, vUv);
}
`
})
const renderMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(w, h),
renderMaterial
)
renderMesh.rotation.x += Math.PI
renderScene.add(renderMesh)
let timeElapsed = 0
let shape
setMainScene()
renderFrame()
function createRenderTarget () {
let type = THREE.FloatType
if( renderer.extensions.get( 'OES_texture_float_linear' ) === null ) type = THREE.HalfFloatType
let renderTarget = new THREE.WebGLRenderTarget( 1, 1, {
type,
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping,
format: THREE.RGBAFormat,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
stencilBuffer: false,
depthBuffer: true
})
renderTarget.texture.generateMipmaps = false
renderTarget.setSize(w, h)
return renderTarget
}
function swapBuffers () {
if (readBuffer === frontBuffer) {
readBuffer = backBuffer
writeBuffer = frontBuffer
} else {
readBuffer = frontBuffer
writeBuffer = backBuffer
}
}
function setMainScene () {
renderer.setSize(w, h)
renderer.setClearColor(0x111111)
renderer.setPixelRatio(window.devicePixelRatio || 1)
document.body.appendChild(renderer.domElement)
camera.position.set(0, 20, 100)
camera.lookAt(new THREE.Vector3())
shape = new THREE.Mesh(
new THREE.SphereBufferGeometry(10, 20, 20),
new THREE.MeshBasicMaterial({ color: 0xFF0000 })
)
scene.add(shape)
}
function renderFrame () {
requestAnimationFrame(renderFrame)
renderer.render(scene, camera, writeBuffer)
renderer.render(renderScene, renderCamera)
swapBuffers()
timeElapsed += clock.getDelta()
shape.position.x = Math.sin(timeElapsed) * 20.0
shape.position.y = Math.cos(timeElapsed * Math.PI) * 20.0
}
* { margin: 0; padding: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
First, I create my two extra framebuffers:
let frontBuffer = createRenderTarget()
let backBuffer = frontBuffer.clone()
let readBuffer = frontBuffer
let writeBuffer = backBuffer
function createRenderTarget () {
let type = THREE.FloatType
if( renderer.extensions.get( 'OES_texture_float_linear' ) === null ) type = THREE.HalfFloatType
let renderTarget = new THREE.WebGLRenderTarget( 1, 1, {
type,
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping,
format: THREE.RGBAFormat,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
stencilBuffer: false,
depthBuffer: true
})
renderTarget.texture.generateMipmaps = false
renderTarget.setSize(w, h)
return renderTarget
}
Then I create an extra scene, a plane (to which I will render my main scene) covering the screen and a orthographic camera. I pass the result image of the main scene render as a uniform to my post-processing plane:
const renderScene = new THREE.Scene()
const renderCamera = new THREE.OrthographicCamera(-w / 2, w / 2, -h / 2, h / 2, -1000, 1000)
const renderMaterial = new THREE.ShaderMaterial({
uniforms: {
tDiffuse: { value: writeBuffer.texture }
},
vertexShader: `
varying vec2 vUv;
void main () {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vUv = uv;
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main () {
gl_FragColor = texture2D(tDiffuse, vUv);
}
`
})
Finally, in my animation loop, I first render the main scene to the current fbo and then render my post-processing plane and I swap my buffers:
function swapBuffers () {
if (readBuffer === frontBuffer) {
readBuffer = backBuffer
writeBuffer = frontBuffer
} else {
readBuffer = frontBuffer
writeBuffer = backBuffer
}
}
function renderFrame () {
requestAnimationFrame(renderFrame)
renderer.render(scene, camera, writeBuffer)
renderer.render(renderScene, renderCamera)
swapBuffers()
timeElapsed += clock.getDelta()
shape.position.x = Math.sin(timeElapsed) * 20.0
shape.position.y = Math.cos(timeElapsed * Math.PI) * 20.0
}
This is all fine and good and I can see my main scene render shown on the post-processing plane, but I can't understand how to blend it with the previous framebuffer. I guess I am very wrong in my current implementation, but information is scarce and I simply can't wrap my head around how to achieve this blending.
I tried passing both of my buffers as textures and then blending between them in GLSL, like this:
// js
uniforms: {
tDiffuse1: { value: writeBuffer.texture },
tDiffuse2: { value: readBuffer.texture }
}
// glsl
gl_FragColor = mix(texture2D(tDiffuse1, vUv), texture2D(tDiffuse2, vUv), 0.5);
But visually I don't see any blending going on.
You need 3 render targets. Let's call them sceneTarget, previousTarget, resultTarget
Step 1: Render your scene to the sceneTarget.
You now have your scene in sceneTarget.texture
Step 2: Blend sceneTarget.texture with previousTarget.texture into resultTarget
This one you need 2 textures as input like you mentioned at the bottom of your question. You need to update the material uniforms to use the correct textures every frame
renderMaterial.uniforms.tDiffuse1.value = previousTarget.texture;
renderMaterial.uniforms.tDiffuse2.value = sceneTarget.texture;
Now you have a blended result in resultTarget.texture
Step 3: render resultTarget.texture to the canvas.
Now you can actually see the result.
Step 4: swap resultTarget and previousTarget
let w = window.innerWidth
let h = window.innerHeight
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
const clock = new THREE.Clock()
let sceneTarget = createRenderTarget()
let previousTarget = sceneTarget.clone();
let resultTarget = sceneTarget.clone();
const blendScene = new THREE.Scene();
const blendCamera = new THREE.OrthographicCamera(-w/2, w/2, -h/2, h/2, -1000, 1000);
const blendMaterial = new THREE.ShaderMaterial({
uniforms: {
tDiffuse1: { value: previousTarget.texture },
tDiffuse2: { value: sceneTarget.texture },
},
vertexShader: `
varying vec2 vUv;
void main () {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vUv = uv;
}
`,
fragmentShader: `
uniform sampler2D tDiffuse1;
uniform sampler2D tDiffuse2;
varying vec2 vUv;
void main () {
gl_FragColor = mix(texture2D(tDiffuse1, vUv), texture2D(tDiffuse2, vUv), 0.25);
}
`,
});
const blendMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(w, h),
blendMaterial
);
blendMesh.rotation.x = Math.PI;
blendScene.add(blendMesh);
const resultScene = new THREE.Scene();
const resultCamera = new THREE.OrthographicCamera(-w/2, w/2, -h/2, h/2, -1000, 1000);
const resultMaterial = new THREE.MeshBasicMaterial({
map: resultTarget.texture,
});
const resultMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(w, h),
resultMaterial
);
resultMesh.rotation.x = Math.PI;
resultScene.add(resultMesh);
let shape
setMainScene()
renderFrame(0)
function createRenderTarget () {
let type = THREE.FloatType
if( renderer.extensions.get( 'OES_texture_float_linear' ) === null ) type = THREE.HalfFloatType
let renderTarget = new THREE.WebGLRenderTarget( 1, 1, {
type,
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping,
format: THREE.RGBAFormat,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
stencilBuffer: false,
depthBuffer: true
})
renderTarget.texture.generateMipmaps = false
renderTarget.setSize(w, h)
return renderTarget
}
function swapBuffers () {
const temp = previousTarget;
previousTarget = resultTarget;
resultTarget = temp;
}
function setMainScene () {
renderer.setSize(w, h)
renderer.setClearColor(0x111111)
renderer.setPixelRatio(window.devicePixelRatio || 1)
document.body.appendChild(renderer.domElement)
camera.position.set(0, 20, 100);
camera.lookAt(new THREE.Vector3());
shape = new THREE.Mesh(
new THREE.SphereBufferGeometry(10, 20, 20),
new THREE.MeshBasicMaterial({ color: 0xFF0000 })
);
scene.add(shape);
}
function renderFrame (timeElapsed) {
timeElapsed *= 0.001;
renderer.render(scene, camera, sceneTarget);
blendMaterial.uniforms.tDiffuse1.value = previousTarget.texture;
blendMaterial.uniforms.tDiffuse2.value = sceneTarget.texture;
renderer.render(blendScene, blendCamera, resultTarget);
resultMaterial.map = resultTarget.texture;
renderer.render(resultScene, resultCamera);
swapBuffers();
shape.position.x = Math.sin(timeElapsed) * 20.0;
shape.position.y = Math.cos(timeElapsed * Math.PI) * 20.0;
requestAnimationFrame(renderFrame);
}
* { margin: 0; padding: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
Let me also add that's not really a good persistence affect. I'm not sure what the best one is. The problem with the one above is the higher you set the persistence the less you see of the current frame.
A better one, though it requires choosing a fade out color, would be something like this. Only 2 targets needed, previousTarget and currentTarget
Render previousTarget.texture to currentTarget with a shader
that fades to a certain color. mix(tex, color, 0.05) or something like that.
Render the scene to currentTarget as well
Render currentTarget.texture to canvas
Swap currentTarget and previousTarget
let w = window.innerWidth
let h = window.innerHeight
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
const clock = new THREE.Clock()
let currentTarget = createRenderTarget()
let previousTarget = currentTarget.clone();
const fadeScene = new THREE.Scene();
const fadeCamera = new THREE.OrthographicCamera(-w/2, w/2, -h/2, h/2, -1000, 1000);
const fadeMaterial = new THREE.ShaderMaterial({
uniforms: {
tDiffuse: { value: previousTarget.texture },
},
vertexShader: `
varying vec2 vUv;
void main () {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vUv = uv;
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main () {
vec4 fadeColor = vec4(0,0,0,1);
gl_FragColor = mix(texture2D(tDiffuse, vUv), fadeColor, 0.05);
}
`,
});
const fadeMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(w, h),
fadeMaterial
);
fadeMesh.rotation.x = Math.PI;
fadeScene.add(fadeMesh);
const resultScene = new THREE.Scene();
const resultCamera = new THREE.OrthographicCamera(-w/2, w/2, -h/2, h/2, -1000, 1000);
const resultMaterial = new THREE.MeshBasicMaterial({
map: currentTarget.texture,
});
const resultMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(w, h),
resultMaterial
);
resultMesh.rotation.x = Math.PI;
resultScene.add(resultMesh);
let shape
setMainScene()
renderFrame(0)
function createRenderTarget () {
let type = THREE.FloatType
if( renderer.extensions.get( 'OES_texture_float_linear' ) === null ) type = THREE.HalfFloatType
let renderTarget = new THREE.WebGLRenderTarget( 1, 1, {
type,
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping,
format: THREE.RGBAFormat,
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
stencilBuffer: false,
depthBuffer: true
})
renderTarget.texture.generateMipmaps = false
renderTarget.setSize(w, h)
return renderTarget
}
function swapBuffers () {
const temp = previousTarget;
previousTarget = currentTarget;
currentTarget = temp;
}
function setMainScene () {
renderer.setSize(w, h)
renderer.setClearColor(0x111111)
renderer.setPixelRatio(window.devicePixelRatio || 1)
renderer.autoClearColor = false;
document.body.appendChild(renderer.domElement)
camera.position.set(0, 20, 100);
camera.lookAt(new THREE.Vector3());
shape = new THREE.Mesh(
new THREE.SphereBufferGeometry(10, 20, 20),
new THREE.MeshBasicMaterial({ color: 0xFF0000 })
);
scene.add(shape);
}
function renderFrame (timeElapsed) {
timeElapsed *= 0.001;
fadeMaterial.uniforms.tDiffuse.value = previousTarget.texture;
renderer.render(fadeScene, fadeCamera, currentTarget);
renderer.render(scene, camera, currentTarget);
resultMaterial.map = currentTarget.texture;
renderer.render(resultScene, resultCamera);
swapBuffers();
shape.position.x = Math.sin(timeElapsed) * 20.0;
shape.position.y = Math.cos(timeElapsed * Math.PI) * 20.0;
requestAnimationFrame(renderFrame);
}
* { margin: 0; padding: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>

Resources