How to reduce three.js CPU/GPU usage in browser - performance

At the moment I have an animated globe which rotates and the the dots on the globe randomly change colour. It works fine but if left in the background it slows down my laptop a lot. Are there any changes I could make that would reduce how much memory it is using?
In the task manager on chrome I can see it's using 12% CPU and 128MB of GPU memory when the tab is active. Is that normal for three.js or does the code need to be changed?
ngAfterViewInit() {
if(this.enabled) {
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.rotateSpeed = 0.5;
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.5;
this.controls.rotationSpeed = 0.3;
this.controls.enableZoom = false;
this.controls.autoRotate = true;
this.controls.autoRotateSpeed = -1;
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.rendererContainer.nativeElement.appendChild(this.renderer.domElement);
this.animate();
const timerId = setInterval(() => this.updateColor(), 650);
}
}
private get enabled(): boolean {
if(this._enabled!==undefined) {
return this._enabled;
}
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
this._enabled = gl && gl instanceof WebGLRenderingContext;
return this._enabled;
}
private initGlobe(): void {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
this.camera.position.set(0, 5, 15);
this.camera.lookAt(this.scene.position);
this.renderer = new THREE.WebGLRenderer({
antialias: true
});
this.renderer.setClearColor('rgb(55, 44, 80)');
this.geom = new THREE.SphereBufferGeometry(6, 350, 90);
this.colors = [];
this.color = new THREE.Color();
this.colorList = ['rgb(123, 120, 194)'];
for (let i = 0; i < this.geom.attributes.position.count; i++) {
this.color.set(this.colorList[THREE.Math.randInt(0, this.colorList.length - 1)]);
this.color.toArray(this.colors, i * 3);
}
this.geom.addAttribute('color', new THREE.BufferAttribute(new Float32Array(this.colors), 3));
this.geom.addAttribute('colorRestore', new THREE.BufferAttribute(new Float32Array(this.colors), 3));
this.loader = new THREE.TextureLoader();
this.loader.setCrossOrigin('');
this.texture = this.loader.load('/assets/globe-dot.jpg');
this.texture.wrapS = THREE.RepeatWrapping;
this.texture.wrapT = THREE.RepeatWrapping;
this.texture.repeat.set(1, 1);
const oval = this.loader.load('/assets/circle.png');
this.points = new THREE.Points(this.geom, new THREE.ShaderMaterial({
vertexColors: THREE.VertexColors,
uniforms: {
visibility: {
value: this.texture
},
shift: {
value: 0
},
shape: {
value: oval
},
size: {
value: 0.4
},
scale: {
value: 300
}
},
vertexShader: `
uniform float scale;
uniform float size;
varying vec2 vUv;
varying vec3 vColor;
void main() {
vUv = uv;
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4( position, 0.99 );
gl_PointSize = size * ( scale / length( mvPosition.xyz )) * (0.3 + sin(uv.y * 3.1415926) * 0.35 );
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, 0.9 );
vec4 shapeData = texture2D( shape, gl_PointCoord );
if (shapeData.a < 0.0625) discard;
gl_FragColor = gl_FragColor * shapeData;
}
`,
transparent: false
}));
this.points.sizeAttenuation = false;
this.scene.add(this.points);
this.globe = new THREE.Mesh(this.geom, new THREE.MeshBasicMaterial({
color: 'rgb(65, 54, 88)', transparent: true, opacity: 0.5
}));
this.globe.scale.setScalar(0.99);
this.points.add(this.globe);
this.scene.add(this.globe);
}
animate() {
this.controls.update();
this.renderer.render(this.scene, this.camera);
this.animationQueue.push(this.animate);
window.requestAnimationFrame(_ => this.nextAnimation());
}
nextAnimation() {
try {
const animation = this.animationQueue.shift();
if (animation instanceof Function) {
animation.bind(this)();
}
} catch (e) {
console.error(e);
}
}
updateColor() {
for (let i = 0; i < this.usedIndices.length; i++) {
let idx = this.usedIndices[i];
this.geom.attributes.color.copyAt(idx, this.geom.attributes.colorRestore, idx);
}
for (let i = 0; i < this.pointsUsed; i++) {
let idx = THREE.Math.randInt(0, this.geom.attributes.color.count - 1);
if (idx%5 == 0 && idx%1 == 0) {
this.geom.attributes.color.setXYZ(idx, 0.9, 0.3, 0);
}
else {
this.geom.attributes.color.setXYZ(idx, 1, 1, 1);
}
this.usedIndices[i] = idx;
}
this.geom.attributes.color.needsUpdate = true;
I looked at other questions which suggest merging the meshes but I'm not sure that would work here. Thanks!

It depends on what you mean by "background"
If by "background" you mean "not the front tab" then, if you're using requestAnimationFrame (which you are) then if your page is not the front tab of the browser or if you minimize the browser window the browser will stop sending you animation frame events and your page should stop completely.
If by "background" you mean the front tab but of a window that's not minimized and is also not the front window then you can use the blur and focus events to stop the page completely.
Example: NOTE: blur events don't seem to work in an iframe so it won't work in the snippet below but if you copy it to a file it should work
let requestId;
function start() {
if (!requestId) {
requestId = requestAnimationFrame(animate);
}
}
function stop() {
console.log('stop');
if (requestId) {
cancelAnimationFrame(requestId);
requestId = undefined;
}
}
const ctx = document.querySelector("canvas").getContext('2d');
function animate(time) {
requestId = undefined;
ctx.save();
ctx.translate(
150 + 150 * Math.cos(time * 0.001),
75 + 75 * Math.sin(time * 0.003),
);
ctx.scale(
Math.cos(time * 0.005),
Math.cos(time * 0.007),
);
ctx.fillStyle = `hsl(${time % 360},100%,50%)`;
ctx.fillRect(-50, 50, 100, 100);
ctx.restore();
start();
}
start();
window.addEventListener('blur', stop);
window.addEventListener('focus', start);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
Of course rather than stopping completely on blur you could throttle your app your self. Only render every 5th frame or render less things, etc...

Related

Port shader with multiple buffers from shadertoy

Im trying to understand how to implement multiple buffers in three.js by porting a shader from shadertoy with help of this thread.
https://discourse.threejs.org/t/help-porting-shadertoy-to-threejs/
I tried to rewrite it for js but it doesnt compile.
Here is the code:
https://codepen.io/haangglide/pen/BaKXmLX
It is based on this one:
https://www.shadertoy.com/view/4sG3WV
My understanding of using buffers is:
create a bufferscene
bufferAscene = new THREE.Scene();
create a texture
textureA = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter
});
create a shadermaterial where you define the uniforms for passing to the shader
bufferA = new THREE.ShaderMaterial({
uniforms: {
iFrame: { value: 0 },
iResolution: { value: resolution },
iMouse: { value: mousePosition },
iChannel0: { value: textureA.texture },
iChannel1: { value: textureB.texture }
},
vertexShader: VERTEX_SHADER,
fragmentShader: BUFFER_A_FRAG,
});
create a PlaneBufferGeometry and create a mesh from the geometry and buffermaterial
new THREE.Mesh(planeA, bufferA)
add it to the Scene
bufferAscene.add(new THREE.Mesh(planeA, bufferA));
In the render:
update the uniforms
bufferA.uniforms.iChannel0.value = textureA
I dont really understand the swap though.
If anyone can help me to get the application to compile it would be very much apreciated!
Here is a live example that ported the original TS code to JavaScript.
// Port from Shadertoy to THREE.js: https://www.shadertoy.com/view/4sG3WV
const VERTEX_SHADER = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
const BUFFER_A_FRAG = `
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform vec2 iResolution;
uniform float iFrame;
varying vec2 vUv;
#define mousedata(a,b) texture2D( iChannel1, (0.5+vec2(a,b)) / iResolution.xy, -0.0 )
#define backbuffer(uv) texture2D( iChannel0, uv ).xy
float lineDist(vec2 p, vec2 start, vec2 end, float width) {
vec2 dir = start - end;
float lngth = length(dir);
dir /= lngth;
vec2 proj = max(0.0, min(lngth, dot((start - p), dir))) * dir;
return length( (start - p) - proj ) - (width / 2.0);
}
void main() {
vec2 uv = vUv;
vec2 col = uv;
if (iFrame > 2.) {
col = texture2D(iChannel0,uv).xy;
vec2 mouse = iMouse.xy/iResolution.xy;
vec2 p_mouse = mousedata(2.,0.).xy;
if (mousedata(4.,0.).x > 0.) {
col = backbuffer(uv+((p_mouse-mouse)*clamp(1.-(lineDist(uv,mouse,p_mouse,0.)*20.),0.,1.)*.7));
}
}
gl_FragColor = vec4(col,0.0,1.0);
}
`;
const BUFFER_B_FRAG = `
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform vec3 iResolution;
varying vec2 vUv;
bool pixelAt(vec2 coord, float a, float b) {
return (floor(coord.x) == a && floor(coord.y) == b);
}
vec4 backbuffer(float a,float b) {
return texture2D( iChannel0, (0.5+vec2(a,b)) / iResolution.xy, -100.0 );
}
void main( ) {
vec2 uv = vUv;// / iResolution.xy;
vec4 color = texture2D(iChannel0,uv);
if (pixelAt(gl_FragCoord.xy,0.,0.)) { //Surface position
gl_FragColor = vec4(backbuffer(0.,0.).rg+(backbuffer(4.,0.).r*(backbuffer(2.,0.).rg-backbuffer(1.,0.).rg)),0.,1.);
} else if (pixelAt(gl_FragCoord.xy,1.,0.)) { //New mouse position
gl_FragColor = vec4(iMouse.xy/iResolution.xy,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,2.,0.)) { //Old mouse position
gl_FragColor = vec4(backbuffer(1.,0.).rg,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,3.,0.)) { //New mouse holded
gl_FragColor = vec4(clamp(iMouse.z,0.,1.),0.,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,4.,0.)) { //Old mouse holded
gl_FragColor = vec4(backbuffer(3.,0.).r,0.,0.,1.);
} else {
gl_FragColor = vec4(0.,0.,0.,1.);
}
}
`;
const BUFFER_FINAL_FRAG = `
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
vec2 a = texture2D(iChannel1,uv).xy;
gl_FragColor = vec4(texture2D(iChannel0,a).rgb,1.0);
}
`;
class App {
constructor() {
this.width = 1024;
this.height = 512;
this.renderer = new THREE.WebGLRenderer();
this.loader = new THREE.TextureLoader();
this.mousePosition = new THREE.Vector4();
this.orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
this.counter = 0;
this.renderer.setSize(this.width, this.height);
document.body.appendChild(this.renderer.domElement);
this.renderer.domElement.addEventListener('mousedown', () => {
this.mousePosition.setZ(1);
this.counter = 0;
});
this.renderer.domElement.addEventListener('mouseup', () => {
this.mousePosition.setZ(0);
});
this.renderer.domElement.addEventListener('mousemove', event => {
this.mousePosition.setX(event.clientX);
this.mousePosition.setY(this.height - event.clientY);
});
this.targetA = new BufferManager(this.renderer, {
width: this.width,
height: this.height
});
this.targetB = new BufferManager(this.renderer, {
width: this.width,
height: this.height
});
this.targetC = new BufferManager(this.renderer, {
width: this.width,
height: this.height
});
}
start() {
const resolution = new THREE.Vector3(this.width, this.height, window.devicePixelRatio);
const channel0 = this.loader.load('https://res.cloudinary.com/di4jisedp/image/upload/v1523722553/wallpaper.jpg');
this.loader.setCrossOrigin('');
this.bufferA = new BufferShader(BUFFER_A_FRAG, {
iFrame: {
value: 0
},
iResolution: {
value: resolution
},
iMouse: {
value: this.mousePosition
},
iChannel0: {
value: null
},
iChannel1: {
value: null
}
});
this.bufferB = new BufferShader(BUFFER_B_FRAG, {
iFrame: {
value: 0
},
iResolution: {
value: resolution
},
iMouse: {
value: this.mousePosition
},
iChannel0: {
value: null
}
});
this.bufferImage = new BufferShader(BUFFER_FINAL_FRAG, {
iResolution: {
value: resolution
},
iMouse: {
value: this.mousePosition
},
iChannel0: {
value: channel0
},
iChannel1: {
value: null
}
});
this.animate();
}
animate() {
requestAnimationFrame(() => {
this.bufferA.uniforms['iFrame'].value = this.counter++;
this.bufferA.uniforms['iChannel0'].value = this.targetA.readBuffer.texture;
this.bufferA.uniforms['iChannel1'].value = this.targetB.readBuffer.texture;
this.targetA.render(this.bufferA.scene, this.orthoCamera);
this.bufferB.uniforms['iChannel0'].value = this.targetB.readBuffer.texture;
this.targetB.render(this.bufferB.scene, this.orthoCamera);
this.bufferImage.uniforms['iChannel1'].value = this.targetA.readBuffer.texture;
this.targetC.render(this.bufferImage.scene, this.orthoCamera, true);
this.animate();
});
}
}
class BufferShader {
constructor(fragmentShader, uniforms = {}) {
this.uniforms = uniforms;
this.material = new THREE.ShaderMaterial({
fragmentShader: fragmentShader,
vertexShader: VERTEX_SHADER,
uniforms: uniforms
});
this.scene = new THREE.Scene();
this.scene.add(
new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this.material)
);
}
}
class BufferManager {
constructor(renderer, size) {
this.renderer = renderer;
this.readBuffer = new THREE.WebGLRenderTarget(size.width, size.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
type: THREE.FloatType,
stencilBuffer: false
});
this.writeBuffer = this.readBuffer.clone();
}
swap() {
const temp = this.readBuffer;
this.readBuffer = this.writeBuffer;
this.writeBuffer = temp;
}
render(scene, camera, toScreen = false) {
if (toScreen) {
this.renderer.render(scene, camera);
} else {
this.renderer.setRenderTarget(this.writeBuffer);
this.renderer.clear();
this.renderer.render(scene, camera)
this.renderer.setRenderTarget(null);
}
this.swap();
}
}
document.addEventListener('DOMContentLoaded', () => {
(new App()).start();
});
body {
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.121.1/build/three.js"></script>

three.js How to have 2 textures on one object, the second being transparent according to a grayscale png?

I need to have an object with a texture I can change the color of, and another one on top of the first one for colored details.
Here are some pictures to describe this :
The result I need : Fig1 and 2.
Fig3 is the texture of the background.
Fig4 is the alpha texture of the details.
I know how to do this with lightwave for example, it's called texture layers. But I can't figure it out in threejs.
Thank you.
You can use THREE.ShaderMaterial() to mix those textures, using .r channel for the value of mixing from the texture with the pattern:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var c1 = document.createElement("canvas");
c1.width = 128;
c1.height = 128;
var ctx1 = c1.getContext("2d");
ctx1.fillStyle = "gray";
ctx1.fillRect(0, 0, 128, 128);
var tex1 = new THREE.CanvasTexture(c1); // texture of a solid color
var c2 = document.createElement("canvas");
c2.width = 128;
c2.height = 128;
var ctx2 = c2.getContext("2d");
ctx2.fillStyle = "black";
ctx2.fillRect(0, 0, 128, 128);
ctx2.strokeStyle = "white";
ctx2.moveTo(50, -20);
ctx2.lineTo(100, 148);
ctx2.lineWidth = 20;
ctx2.stroke();
var tex2 = new THREE.CanvasTexture(c2); // texture with a pattern
var planeGeom = new THREE.PlaneBufferGeometry(10, 10);
var planeMat = new THREE.ShaderMaterial({
uniforms: {
tex1: {
value: tex1
},
tex2: {
value: tex2
},
color: {
value: new THREE.Color() //color of the pattern
}
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform vec3 color;
varying vec2 vUv;
void main() {
vec3 c1 = texture2D(tex1, vUv).rgb;
float m = texture2D(tex2, vUv).r;
vec3 col = mix(c1, color, m);
gl_FragColor = vec4(col, 1);
}
`
});
var plane = new THREE.Mesh(planeGeom, planeMat);
scene.add(plane);
var clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
let t = (clock.getElapsedTime() * 0.125) % 1;
planeMat.uniforms.color.value.setHSL(t, 1, 0.5);
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>

Warp / curve all vertices around a pivot point / axis (Three.js / GLSL)

I'm trying to work out how to warp all coordinates in a Three.js scene around a specific pivot point / axis. The best way to describe it is as if I was to place a tube somewhere in the scene and everything else in the scene would curve around that axis and keep the same distance from that axis.
If it helps, this diagram is what I'm trying to achieve. The top part is as if you were looking at the scene from the side and the bottom part is as if you were looking at it from a perspective. The red dot / line is where the pivot point is.
To further complicate matters, I'd like to stop the curve / warp from wrapping back on itself, so the curve stops when it's horizontal or vertical like the top-right example in the diagram.
Any insight into how to achieve this using GLSL shaders, ideally in Three.js but I'll try to translate if they can be described clearly otherwise?
I'm also open to alternative approaches to this as I'm unsure how best to describe what I'm after. Basically I want an inverted "curved world" effect where the scene is bending up and away from you.
First I'd do it in 2D just like your top diagram.
I have no idea if this is the correct way to do this or even a good way but, doing it in 2D seemed easier than 3D and besides the effect you want is actually a 2D. X is not changing at all, only Y, and Z so solving it in 2D seems like it would lead to solution.
Basically we choose a radius for a circle. At that radius for every unit of X past the circle's center we want to wrap one horizontal unit to one unit around the circle. Given the radius we know the distance around the circle is 2 * PI * radius so we can easily compute how far to rotate around our circle to get one unit. It's just 1 / circumference * Math.PI * 2 We do that for some specified distance past the circle's center
const m4 = twgl.m4;
const v3 = twgl.v3;
const ctx = document.querySelector('canvas').getContext('2d');
const gui = new dat.GUI();
resizeToDisplaySize(ctx.canvas);
const g = {
rotationPoint: {x: 100, y: ctx.canvas.height / 2 - 50},
radius: 50,
range: 60,
};
gui.add(g.rotationPoint, 'x', 0, ctx.canvas.width).onChange(render);
gui.add(g.rotationPoint, 'y', 0, ctx.canvas.height).onChange(render);
gui.add(g, 'radius', 1, 100).onChange(render);
gui.add(g, 'range', 0, 300).onChange(render);
render();
window.addEventListener('resize', render);
function render() {
resizeToDisplaySize(ctx.canvas);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const start = g.rotationPoint.x;
const curveAmount = g.range / g.radius;
const y = ctx.canvas.height / 2;
drawDot(ctx, g.rotationPoint.x, g.rotationPoint.y, 'red');
ctx.beginPath();
ctx.arc(g.rotationPoint.x, g.rotationPoint.y, g.radius, 0, Math.PI * 2, false);
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.fillStyle = 'black';
const invRange = g.range > 0 ? 1 / g.range : 0; // so we don't divide by 0
for (let x = 0; x < ctx.canvas.width; x += 5) {
for (let yy = 0; yy <= 30; yy += 10) {
const sign = Math.sign(g.rotationPoint.y - y);
const amountToApplyCurve = clamp((x - start) * invRange, 0, 1);
let mat = m4.identity();
mat = m4.translate(mat, [g.rotationPoint.x, g.rotationPoint.y, 0]);
mat = m4.rotateZ(mat, curveAmount * amountToApplyCurve * sign);
mat = m4.translate(mat, [-g.rotationPoint.x, -g.rotationPoint.y, 0]);
const origP = [x, y + yy, 0];
origP[0] += -g.range * amountToApplyCurve;
const newP = m4.transformPoint(mat, origP);
drawDot(ctx, newP[0], newP[1], 'black');
}
}
}
function drawDot(ctx, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x - 1, y - 1, 3, 3);
}
function clamp(v, min, max) {
return Math.min(max, Math.max(v, min));
}
function resizeToDisplaySize(canvas) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<!-- using twgl just for its math library -->
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>
Notice the only place that matches perfectly is when the radius touches a line of points. Inside the radius things will get pinched, outside they'll get stretched.
Putting that in a shader in the Z direction for actual use
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('canvas'),
});
const gui = new dat.GUI();
const scene = new THREE.Scene();
const fov = 75;
const aspect = 2; // the canvas default
const zNear = 1;
const zFar = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
function lookSide() {
camera.position.set(-170, 35, 210);
camera.lookAt(0, 25, 210);
}
function lookIn() {
camera.position.set(0, 35, -50);
camera.lookAt(0, 25, 0);
}
{
scene.add(new THREE.HemisphereLight(0xaaaaaa, 0x444444, .5));
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-1, 20, 4 - 15);
scene.add(light);
}
const point = function() {
const material = new THREE.MeshPhongMaterial({
color: 'red',
emissive: 'hsl(0,50%,25%)',
wireframe: true,
});
const radiusTop = 1;
const radiusBottom = 1;
const height = 0.001;
const radialSegments = 32;
const geo = new THREE.CylinderBufferGeometry(
radiusTop, radiusBottom, height, radialSegments);
const sphere = new THREE.Mesh(geo, material);
sphere.rotation.z = Math.PI * .5;
const mesh = new THREE.Object3D();
mesh.add(sphere);
scene.add(mesh);
mesh.position.y = 88;
mesh.position.z = 200;
return {
point: mesh,
rep: sphere,
};
}();
const vs = `
// -------------------------------------- [ VS ] ---
#define PI radians(180.0)
uniform mat4 center;
uniform mat4 invCenter;
uniform float range;
uniform float radius;
varying vec3 vNormal;
mat4 rotZ(float angleInRadians) {
float s = sin(angleInRadians);
float c = cos(angleInRadians);
return mat4(
c,-s, 0, 0,
s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
}
mat4 rotX(float angleInRadians) {
float s = sin(angleInRadians);
float c = cos(angleInRadians);
return mat4(
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1);
}
void main() {
float curveAmount = range / radius;
float invRange = range > 0.0 ? 1.0 / range : 0.0;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vec4 point = invCenter * mvPosition;
float amountToApplyCurve = clamp(point.z * invRange, 0.0, 1.0);
float s = sign(point.y);
mat4 mat = rotX(curveAmount * amountToApplyCurve * s);
point = center * mat * (point + vec4(0, 0, -range * amountToApplyCurve, 0));
vNormal = mat3(mat) * normalMatrix * normal;
gl_Position = projectionMatrix * point;
}
`;
const fs = `
// -------------------------------------- [ FS ] ---
varying vec3 vNormal;
uniform vec3 color;
void main() {
vec3 light = vec3( 0.5, 2.2, 1.0 );
light = normalize( light );
float dProd = dot( vNormal, light ) * 0.5 + 0.5;
gl_FragColor = vec4( vec3( dProd ) * vec3( color ), 1.0 );
}
`;
const centerUniforms = {
radius: { value: 0 },
range: { value: 0 },
center: { value: new THREE.Matrix4() },
invCenter: { value: new THREE.Matrix4() },
};
function addUniforms(uniforms) {
return Object.assign(uniforms, centerUniforms);
}
{
const uniforms = addUniforms({
color: { value: new THREE.Color('hsl(100,50%,50%)') },
});
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: vs,
fragmentShader: fs,
});
const planeGeo = new THREE.PlaneBufferGeometry(1000, 1000, 100, 100);
const mesh = new THREE.Mesh(planeGeo, material);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
}
{
const uniforms = addUniforms({
color: { value: new THREE.Color('hsl(180,50%,50%)' ) },
});
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: vs,
fragmentShader: fs,
});
const boxGeo = new THREE.BoxBufferGeometry(10, 10, 10, 20, 20, 20);
for (let x = -41; x <= 41; x += 2) {
for (let z = 0; z <= 40; z += 2) {
const base = new THREE.Object3D();
const mesh = new THREE.Mesh(boxGeo, material);
mesh.position.set(0, 5, 0);
base.position.set(x * 10, 0, z * 10);
base.scale.y = 1 + Math.random() * 2;
base.add(mesh);
scene.add(base);
}
}
}
const g = {
radius: 59,
range: 60,
side: true,
};
class DegRadHelper {
constructor(obj, prop) {
this.obj = obj;
this.prop = prop;
}
get v() {
return THREE.Math.radToDeg(this.obj[this.prop]);
}
set v(v) {
this.obj[this.prop] = THREE.Math.degToRad(v);
}
}
gui.add(point.point.position, 'z', -300, 300).onChange(render);
gui.add(point.point.position, 'y', -150, 300).onChange(render);
gui.add(g, 'radius', 1, 100).onChange(render);
gui.add(g, 'range', 0, 300).onChange(render);
gui.add(g, 'side').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'x'), 'v', -180, 180).name('rotX').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'y'), 'v', -180, 180).name('rotY').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'z'), 'v', -180, 180).name('rotZ').onChange(render);
render();
window.addEventListener('resize', render);
function render() {
if (resizeToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
if (g.side) {
lookSide();
} else {
lookIn();
}
camera.updateMatrixWorld();
point.rep.scale.set(g.radius, g.radius, g.radius);
point.point.updateMatrixWorld();
centerUniforms.center.value.multiplyMatrices(
camera.matrixWorldInverse, point.point.matrixWorld);
centerUniforms.invCenter.value.getInverse(centerUniforms.center.value);
centerUniforms.range.value = g.range;
centerUniforms.radius.value = g.radius;
renderer.render(scene, camera);
}
function resizeToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needUpdate = canvas.width !== width || canvas.height !== height;
if (needUpdate) {
renderer.setSize(width, height, false);
}
return needUpdate;
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>
Honestly I have a feeling there's an easier way I'm missing but for the moment it seems to kind of be working.

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