Particles follow texture - three.js

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>

Related

Why is my mix in my shader not properly mixing in Threejs?

I have been messing with this for awhile and I cannot get the shader to mix properly. Currently it is displaying the two colors that are supposed to be mixed as two separate colors on each hemisphere. I am new to threejs and often certain details I am missing. I would appreciate any help regarding this code.
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer.setClearColor(0x000000);
const spotLight = new THREE.SpotLight(0xFFFFFF);
scene.add(spotLight);
spotLight.position.set(0, 0, 100);
spotLight.castShadow = true;
spotLight.angle = 0.2;
spotLight.intensity = 0.2;
camera.position.set(0.27, 0, 500);
//Black center
var geom = new THREE.SphereBufferGeometry(100, 32, 32);
var mat = new THREE.MeshPhongMaterial({
color: 0x000000
});
const material = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vPosition;
void main() {
vPosition = position;
vec4 localPosition = vec4(position, 1.0);
vec4 worldPosition = modelMatrix * localPosition;
vec4 viewPosition = viewMatrix * worldPosition;
vec4 clipPosition = projectionMatrix * viewPosition;
gl_Position = clipPosition;
}
`,
fragmentShader: `
varying vec3 vPosition;
void main() {
float depth = vPosition.x;
vec3 color1 = vec3(1., 1.0, 1.0);
vec3 color2 = vec3(0., .0, 1.0);
vec3 mixedColor = mix(color1, color2, depth);
gl_FragColor = vec4(mixedColor, 1.0);
}
`
});
var core = new THREE.Mesh(geom, material);
scene.add(core);
var geom = new THREE.SphereBufferGeometry(1, 15, 15);
var mat = new THREE.MeshBasicMaterial({
color: 0xffffff
});
var atoms = new THREE.Object3D();
scene.add(atoms);
for (var i = 0; i < 150; i++) {
var nucleus = new THREE.Mesh(geom, mat);
var size = Math.random() * 6 + 1.5;
nucleus.speedX = (Math.random() - 0.5) * 0.08;
nucleus.speedY = (Math.random() - 0.5) * 0.08;
nucleus.speedZ = (Math.random() - 0.5) * 0.08;
nucleus.applyMatrix4(new THREE.Matrix4().makeScale(size, size, size));
nucleus.applyMatrix4(new THREE.Matrix4().makeTranslation(0, 100 + Math.random() * 10, 0));
nucleus.applyMatrix4(new THREE.Matrix4().makeRotationX(Math.random() * (Math.PI * 2)));
nucleus.applyMatrix4(new THREE.Matrix4().makeRotationY(Math.random() * (Math.PI * 2)));
nucleus.applyMatrix4(new THREE.Matrix4().makeRotationZ(Math.random() * (Math.PI * 2)));
atoms.add(nucleus);
}
const _matrix = new THREE.Matrix4();
function updateNucleus(a) {
for (var i = 0; i < atoms.children.length; i++) {
var part = atoms.children[i];
part.applyMatrix4(_matrix.makeRotationX(part.speedX));
part.applyMatrix4(_matrix.makeRotationY(part.speedY));
part.applyMatrix4(_matrix.makeRotationZ(part.speedZ));
}
}
//Create scene
var necks = [];
var cubesObject = new THREE.Object3D();
scene.add(cubesObject);
function animate(a) {
requestAnimationFrame( animate );
updateNucleus(a);
renderer.render(scene,camera);
};
animate();
window.addEventListener('resize', function(){
camera.aspect = window.innerWidth / this.window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
})

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>

GLSL/THREE.js Math to turn a shape into a cube issue

I am making code to turn a sphere into a cube in a vertex shader, but it seems to turn into this weird shape, my logic was this:
The commented out code was the iterative version.
vec3 p = position;
if(true)
{
if(p.y<s&&p.y>-s){
p.x = -(p.x-s);//p.x-=(p.x-s)*t*0.1;
}
if(p.x<s&&p.x>-s){
p.y = -(p.y-s);//p.y-=(p.y-s)*t*0.1;
}
}
gl_Position = projectionMatrix * modelViewMatrix * vec4( p, 1.0 );
But then that turns this:
Into this:
Any help appreciated.
Use THREE.BoxBufferGeometry() as a base, then add another buffer attribute with coodinates for a sphere formation, then interpolate (mix) those coordinates of the box and the sphere in the shader:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100);
camera.position.set(1, 3, 5);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var side = 2;
var rad = Math.sqrt(3) * 0.5 * side; // radius of the sphere is a half of cube's diagonal
var geom = new THREE.BoxBufferGeometry(side, side, side, 10, 10, 10);
var pos = geom.attributes.position;
var spherePos = []; // array of coordinates for the sphere formation
var vec3 = new THREE.Vector3(); // vector for re-use
for (let i = 0; i < pos.count; i++) {
vec3.fromBufferAttribute(pos, i).setLength(rad); // create coordinate for the sphere formation
spherePos.push(vec3.x, vec3.y, vec3.z);
}
geom.addAttribute("spherePos", new THREE.BufferAttribute(new Float32Array(spherePos), 3));
var mat = new THREE.ShaderMaterial({
uniforms: {
mixShapes: {
value: 0
}
},
vertexShader: `
uniform float mixShapes;
attribute vec3 spherePos;
void main() {
vec3 pos = mix(position, spherePos, mixShapes); // interpolation between shapes
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0, 0.0, 1.0,1.0);
}
`,
wireframe: true
});
var shape = new THREE.Mesh(geom, mat);
scene.add(shape);
var gui = new dat.GUI();
gui.add(mat.uniforms.mixShapes, "value", 0.0, 1.0).name("mixShapes");
renderer.setAnimationLoop(() => {
renderer.render(scene, camera)
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"></script>
If you want to get a cube from a sphere, you can clamp vertices to min and max vectors of a bounding box you need (but accuracy of this approach depends on the amount of vertices of the sphere):
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100);
camera.position.set(1, 3, 5);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var side = 2;
var rad = Math.sqrt(3) * 0.5 * side; // radius of the sphere is a half of cube's diagonal
var geom = new THREE.SphereBufferGeometry(rad, 36, 36);
var mat = new THREE.ShaderMaterial({
uniforms: {
mixShapes: {
value: 0
}
},
vertexShader: `
uniform float mixShapes;
attribute vec3 spherePos;
void main() {
vec3 pos = clamp(position, vec3(${-side * 0.5}), vec3(${side * 0.5})); // clamp to min and max vectors
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0, 0.0, 1.0,1.0);
}
`,
wireframe: true
});
var shape = new THREE.Mesh(geom, mat);
scene.add(shape);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera)
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/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>

Buffer geometry custom attribute material leaking into other objects

I'm making a visualization and want soft edged particles to represent data points.
I've gathered code from various examples and have something working except for a transparency issue. I tried out the the new Stack Overflow snippet system and made a simple example of what I'm seeing.
As you'll notice, the sphere picks up the transparency from the particle system. Is there a way to turn this off - in my application, this is a required feature. I tried applying attributes to the material I use for the sphere but that didn't appear to help.
Any ideas what I'm doing wrong?
var camera, scene, renderer, controls;
var uniforms;
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
var ambient_light = new THREE.AmbientLight(0x333333);
scene.add(ambient_light);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 10, 1000);
camera.position.z = 80;
var num_particles = 5000;
var geometry = new THREE.BufferGeometry();
var positions = new Float32Array(num_particles * 3);
var colors = new Float32Array(num_particles * 3);
var sizes = new Float32Array(num_particles);
for (var i = 0; i < num_particles; i++) {
positions[3 * i + 0] = Math.random() * 100 - 50;
positions[3 * i + 1] = Math.random() * 100 - 50;
positions[3 * i + 2] = Math.random() * 100 - 50;
colors[3 * i + 0] = Math.random();
colors[3 * i + 1] = Math.random();
colors[3 * i + 2] = Math.random();
sizes[i] = 2.5;
}
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('customColor', new THREE.BufferAttribute(colors, 3));
geometry.addAttribute('size', new THREE.BufferAttribute(sizes, 1));
var attributes = {
size: {
type: 'f',
value: null
},
customColor: {
type: 'c',
value: null
}
};
uniforms = {
color: {
type: "c",
value: new THREE.Color(0xffffff)
},
texture: {
type: "t",
value: THREE.ImageUtils.loadTexture("data:image/png;base64,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")
}
};
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
attributes: attributes,
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
blending: THREE.AdditiveBlending,
depthTest: true,
depthWrite: false,
transparent: true
});
var pointcloud = new THREE.PointCloud(geometry, shaderMaterial);
scene.add(pointcloud);
scene.add(new THREE.Mesh(
new THREE.SphereGeometry(50.0, 16, 16),
new THREE.MeshNormalMaterial({ })
))
controls = new THREE.TrackballControls(camera);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
controls.update();
}
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float size;
attribute vec3 customColor;
varying vec3 vColor;
void main() {
vColor = customColor;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 300.0 / length( mvPosition.xyz ) );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color;
uniform sampler2D texture;
varying vec3 vColor;
void main() {
gl_FragColor = vec4( color * vColor, 1.0 );
gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord );
}
</script>
Edit: WestLangley's suggestion in comments of turning on depth test results in this: (Don't know how to add an image to a comment so adding here)
The point cloud material attribute I was missing was depthWrite:false and as #WestLangley correctly said, depthTest: true;
I don't know how to version the embedded code so I updated it to work with these values.

Resources