My Three.js project consists in making a snow fall(with some particles) inside a snowball(a sphereGeometry), a classic Xmas snowball. I have created the particles using THREE.BufferGeometry() and I initialised them giving an initial_position for each parameter(x, y, z).
Is there a way to make them visible only inside the sphere? I resolved the problem giving the particles outside the sphere the same colour of the background, but it doesn't work perfectly.
Is there a way to make the particles outside the sphere not visible? Maybe making them transparent.
Otherwise how could I initialise the particles as a spherical cap?
Thanks!
This is how I am initialising particles positions(as a parallelepiped):
for(i=0; i<n; i++){
init_pos_y[i] = 50 + (Math.random()-0.5)*20;
init_pos_x[i] = (Math.random()-0.5)*100;
init_pos_z[i] = (Math.random()-0.5)*100;
acceleration[i] = Math.random()*1;
In the Vertex Shader this is how I am making the particles fall and giving them colour(and changing its opacity depending on its position inside or outside the sphere):
void main(){
vec3 p = position;
p.x = initial_position_x;
p.z = initial_position_z;
if (initial_position_y - time * acceleration > -32.8 + min_level){
p.y = initial_position_y - time * acceleration;
}
else{
p.y = -33.8 + min_level;
}
float opacity;
if (p.x*p.x + p.y*p.y + p.z*p.z > 2490.0){
opacity = 0.40;
vColor = vec4( customColor, opacity );
}
else{
opacity = 1.0;
vColor = vec4( customColor2, opacity );
}
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
vUv = projectionMatrix * vec4(p, 1.0);
gl_PointSize = 3.0*acceleration;
}
First, you need to put your points in a formation of cylinder (randomly inside a circle and randomly on height). For that, see function setInCircle().
Then you need to modify the code of shader. I prefer to do it with .onBeforeCompile(), thus you can modify necessery parts, keeping all the other functionalities of the material.
In the shader, you change y-value with adding the distance (time * speed), dividing it by 10 (mod() function), thus you put it in a cycle to go from top to bottom in range of 5 to -5.
The last thing is to check if the length of the given transformed vector is less or equal to given radius, passing the result in a varying to the fragment shader, where you discard a pixel if the result if less than 0.5.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.setScalar(10);
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 pts = [];
var radius = 5;
var pointsCount = 5000;
for (i = 0; i < pointsCount; i++) {
let v2 = setInCircle().multiplyScalar(radius);
let v3 = new THREE.Vector3(v2.x, THREE.Math.randFloat(-radius, radius), v2.y);
pts.push(v3);
}
var geom = new THREE.BufferGeometry().setFromPoints(pts);
var uniforms = {
speed: {
value: 1
},
time: {
value: 0
},
radius: {
value: radius
}
}
var mat = new THREE.PointsMaterial({
color: "magenta",
size: 0.05
});
mat.onBeforeCompile = shader => {
shader.uniforms.speed = uniforms.speed;
shader.uniforms.time = uniforms.time;
shader.uniforms.radius = uniforms.radius;
shader.vertexShader = `
uniform float speed;
uniform float time;
uniform float radius;
varying float vVisible;
` + shader.vertexShader;
//console.log(shader.vertexShader);
shader.vertexShader = shader.vertexShader.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
transformed.y = mod((transformed.y - speed * time) - 5., 10.) - 5.;
vVisible = length(transformed) <= radius ? 1.: 0.;
`
);
shader.fragmentShader = `
varying float vVisible;
` + shader.fragmentShader;
console.log(shader.fragmentShader);
shader.fragmentShader = shader.fragmentShader.replace(
`void main() {`,
`void main() {
if (vVisible < 0.5) discard;
`
);
}
var points = new THREE.Points(geom, mat);
scene.add(points);
function setInCircle() {
let v = new THREE.Vector2();
v.set(
THREE.Math.randFloat(-1, 1),
THREE.Math.randFloat(-1, 1)
)
return v.length() <= 1 ? v : setInCircle();
}
var clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
uniforms.time.value = clock.getElapsedTime();
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>
Related
I currently have this: Codepen (embedded snippet below).
I would like to be able to change the color progressively after the scanline, something like this:
After searching online and trying to find something in three.js / WebGL I failed to procure what I wanted, probably because I don't quite know what I should search for.
Could you help me with a solution or pointing me in the right direction?
I have considered the following possibilities:
Having a second green circle and a dynamic mask that reveals it after the scanline.
How to create a mask in three.js that can show a slice of an increasing angle θ?
CircleGeometry has parameters to create a slice with angle θ. But constantly changing the geometry of my mesh doesn't sound very smart.
Adding tiny circle slices after the scanline passes so it creates the impression of revealing a circle but it's actually just adding tiny slices.
P.S. - I am using three.js because later there will be 3d elements to this project.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const innerRadius = 1;
const outerRadius = innerRadius*2;
const barLenght = innerRadius;
// create scanline
const outerMaterial = new THREE.MeshBasicMaterial({color: 0x34ebd2});
const outerCircle = new THREE.Mesh(new THREE.CircleGeometry(outerRadius, 60), outerMaterial);
scene.add(outerCircle);
// Create innerCircle
const innerMaterial = new THREE.MeshBasicMaterial({color: 0x0000ff});
const innerCircle = new THREE.Mesh(new THREE.CircleGeometry(innerRadius, 60), innerMaterial);
scene.add(innerCircle);
// create static line
const staticLine = new THREE.Mesh(new THREE.PlaneGeometry(0.05, barLenght), new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide}));
scene.add(staticLine);
// create scan line
const scanLine = new THREE.Mesh(new THREE.PlaneGeometry(0.05, barLenght), new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide}));
scene.add(scanLine);
// position static line
staticLine.position.y = innerRadius + barLenght/2;
// position scan line
scanLine.position.y = innerRadius + barLenght/2;
// create pivot to rotate dateline
const pivot = new THREE.Group();
pivot.position.set( 0.0, 0.0, 0 );
pivot.add(scanLine);
scene.add(pivot);
camera.position.z = 5;
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
pivot.rotation.z -= 0.005;
}
animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
This is a 2-in-1 example (fragment and vertex shader implementations) of progressive arcs, just from the scratch. Use it as a starting point.
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.132.2";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.132.2/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(-5, 3, 8);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
// fragment shader option
let g = new THREE.CircleGeometry(5, 64);
let m = new THREE.MeshBasicMaterial({
color: 0x7f7f7f,
side: THREE.DoubleSide,
onBeforeCompile: shader => {
shader.uniforms.time = m.userData.uniforms.time;
shader.uniforms.currColor = m.userData.uniforms.currColor;
shader.uniforms.prevColor = m.userData.uniforms.prevColor;
shader.fragmentShader = `
uniform float time;
uniform vec3 currColor;
uniform vec3 prevColor;
${shader.fragmentShader}
`.replace(
`#include <color_fragment>`,
`#include <color_fragment>
vec2 cUv = vUv - 0.5;
float dist = length(cUv);
vec3 col = prevColor;
float ang = mod(atan(cUv.y, cUv.x) + PI * 3.5, PI2);
float aRatio = 1. - ang / PI2;
float slice = 1. - step(time, aRatio);
col = mix(prevColor, currColor, slice);
float innerCirc = 1. - step(0.25, dist);
col = mix(col, diffuseColor.rgb, innerCirc);
diffuseColor.rgb = col;
`
);
console.log(shader.fragmentShader);
}
})
m.defines = {
"USE_UV": " "
};
m.userData = {
uniforms: {
time: {
value: 0.5
},
currColor: {
value: new THREE.Color(0xff00ff)
},
prevColor: {
value: new THREE.Color(0x00ffff)
}
}
}
let o = new THREE.Mesh(g, m);
scene.add(o);
// vertex shader option
let g2 = new THREE.PlaneGeometry(1, 1, 180, 1);
let m2 = new THREE.MeshBasicMaterial({
color: 0xffff00,
wireframe: true,
onBeforeCompile: shader => {
shader.uniforms.rMin = m2.userData.uniforms.rMin;
shader.uniforms.rMax = m2.userData.uniforms.rMax;
shader.uniforms.arcRatio = m2.userData.uniforms.arcRatio;
shader.vertexShader = `
uniform float rMin;
uniform float rMax;
uniform float arcRatio;
mat2 rot(float a){return mat2(cos(a), -sin(a), sin(a), cos(a));}
${shader.vertexShader}
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
float rDiff = rMax - rMin;
float r = rMin + (rDiff * uv.y);
float ang = PI2 * uv.x * arcRatio;
transformed.xy = rot(ang) * vec2(0., r);
`
);
console.log(shader.vertexShader);
}
});
m2.userData = {
uniforms: {
rMin: {value: 2.5},
rMax: {value: 5},
arcRatio: {value: 0.25} // 0..1
}
}
let o2 = new THREE.Mesh(g2, m2);
o2.position.z = 2;
scene.add(o2);
let clock = new THREE.Clock();
window.addEventListener("resize", onResize);
renderer.setAnimationLoop(_ => {
let t = (clock.getElapsedTime() * 0.1) % 1;
m.userData.uniforms.time.value = t;
m2.userData.uniforms.arcRatio.value = t;
renderer.render(scene, camera);
})
function onResize(){
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
}
</script>
This question already has an answer here:
three.js - drawing two overlapping transparent spheres and hiding intersection
(1 answer)
Closed 3 years ago.
how to hide/disvisible intersection of two object3D by three.js?
for example:
there are two spheres are 'S-Red' and 'S-Blue'
because S-Red is transparent so looks like:
but I hope can display like this
You can set opacity of sphere pixels in fragment shader:
body, canvas {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/controls/TransformControls.js"></script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, innerWidth/innerHeight, 0.01, 1000);
camera.position.set(5,5,0);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth,innerHeight);
document.body.appendChild(renderer.domElement);
let orbit = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(500, 100, 0x666666, 0x444444));
let s1 = sphere(3, 2, 0)
let s2 = sphere(3, -2, 1)
let u1 = s1.material.uniforms, u2 = s2.material.uniforms;
requestAnimationFrame( render );
function sphere(radius, position, color){
color = color.toFixed(1)
var geometry = new THREE.SphereGeometry(radius, 50, 50);
var material = new THREE.ShaderMaterial({
transparent: true,
depthWrite: false,
side: THREE.DoubleSide,
uniforms: {c: {type: "3f"}, o: {type: "3f"}},
vertexShader: `
varying vec3 p;
void main() {
// transfer vertex position to fragment shader,
// this value is interpolated by gpu hardware between pixels of triangle,
// containing this vertex
p = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`,
fragmentShader: `
varying vec3 p; // position of current pixel relative to sphere center
uniform vec3 c; // center of current sphere
uniform vec3 o; // center of opposite sphere
void main() {
vec3 a = abs(p)*50.0;
float opacity = a.x<1. || a.y<1. || a.z<1. ? 0.8 : 0.3;
// here is test of shpere overlapping
opacity = distance(o, p + c) < 3.0 ? 0.0 : opacity;
gl_FragColor = vec4(vec3(${color}, 0.0, 1.0 - ${color}), opacity);
}`
});
let mesh = new THREE.Mesh(geometry, material);
mesh.translateX(position)
scene.add(mesh);
let control = new THREE.TransformControls(camera, renderer.domElement);
control.addEventListener('dragging-changed', e => orbit.enabled = !e.value);
scene.add(control);
control.attach(mesh)
return mesh;
}
function render() {
requestAnimationFrame( render );
let p1 = s1.position, p2 = s2.position;
u2.o.value = u1.c.value = [p1.x, p1.y, p1.z];
u1.o.value = u2.c.value = [p2.x, p2.y, p2.z];
u1.c.needUpdate = u1.o.needUpdate =
u2.c.needUpdate = u2.o.needUpdate = true;
renderer.render( scene, camera );
}
</script>
I'm working on a Three.js scene in which I'd like to update some textures after some time. I'm finding that updating the textures is very slow, however, and drags FPS to only 1-2 FPS for several seconds (when updating just a single texture).
Is there anything one can do to expedite texture updates? Any insights others can offer on this question would be very appreciated.
To see this behavior, click the window of the example below. This will load the first texture update (another click will trigger the second texture update). If you try to zoom after one of these clicks, you'll find the screen freezes and the FPS will drop terribly. Does anyone know how to fix this problem?
<html>
<head>
<style>
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
</style>
</head>
<body>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js'></script>
<script src='https://rawgit.com/YaleDHLab/pix-plot/master/assets/js/trackball-controls.js'></script>
<script src='https://rawgit.com/mrdoob/stats.js/master/build/stats.min.js'></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec3 position; // sets the blueprint's vertex positions
attribute vec3 translation; // x y translation offsets for an instance
attribute float texIdx; // the texture index to access
varying float vTexIdx;
void main() {
// set point position
vec3 pos = position + translation;
vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_Position = projected;
// assign the varyings
vTexIdx = texIdx;
// use the delta between the point position and camera position to size point
float xDelta = pow(projected[0] - cameraPosition[0], 2.0);
float yDelta = pow(projected[1] - cameraPosition[1], 2.0);
float zDelta = pow(projected[2] - cameraPosition[2], 2.0);
float delta = pow(xDelta + yDelta + zDelta, 0.5);
gl_PointSize = 40000.0 / delta;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
uniform sampler2D a;
uniform sampler2D b;
varying float vTexIdx;
void main() {
int textureIndex = int(vTexIdx);
vec2 uv = vec2(gl_PointCoord.x, gl_PointCoord.y);
if (textureIndex == 0) {
gl_FragColor = texture2D(a, uv);
} else if (textureIndex == 1) {
gl_FragColor = texture2D(b, uv);
}
}
</script>
<script>
/**
* Generate a scene object with a background color
**/
function getScene() {
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa);
return scene;
}
/**
* Generate the camera to be used in the scene
**/
function getCamera() {
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100000);
camera.position.set(0, 1, -6000);
return camera;
}
/**
* Generate the renderer to be used in the scene
**/
function getRenderer() {
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
// Add support for retina displays
renderer.setPixelRatio(window.devicePixelRatio);
// Specify the size of the canvas
renderer.setSize(window.innerWidth, window.innerHeight);
// Add the canvas to the DOM
document.body.appendChild(renderer.domElement);
return renderer;
}
/**
* Generate the controls to be used in the scene
**/
function getControls(camera, renderer) {
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.zoomSpeed = 0.4;
controls.panSpeed = 0.4;
return controls;
}
/**
* Generate the points for the scene
**/
function addPoints(scene) {
var BA = THREE.BufferAttribute;
var IBA = THREE.InstancedBufferAttribute;
var geometry = new THREE.InstancedBufferGeometry();
// add data for each observation
var n = 10000; // number of observations
var rootN = n**(1/2);
var cellSize = 20;
var translation = new Float32Array( n * 3 );
var texIdx = new Float32Array( n );
var translationIterator = 0;
var texIterator = 0;
for (var i=0; i<n*3; i++) {
var x = Math.random() * n - (n/2);
var y = Math.random() * n - (n/2);
translation[translationIterator++] = x;
translation[translationIterator++] = y;
translation[translationIterator++] = Math.random() * n - (n/2);
texIdx[texIterator++] = (x + y) > (n/8) ? 1 : 0;
}
var positionAttr = new BA(new Float32Array( [0, 0, 0] ), 3);
var translationAttr = new IBA(translation, 3, 1);
var texIdxAttr = new IBA(texIdx, 1, 1);
positionAttr.dynamic = true;
translationAttr.dynamic = true;
texIdxAttr.dynamic = true;
geometry.addAttribute('position', positionAttr);
geometry.addAttribute('translation', translationAttr);
geometry.addAttribute('texIdx', texIdxAttr);
var canvases = [
getElem('canvas', { width: 16384, height: 16384, }),
getElem('canvas', { width: 16384, height: 16384, }),
]
var textures = [
getTexture( canvases[0] ),
getTexture( canvases[1] ),
];
var material = new THREE.RawShaderMaterial({
uniforms: {
a: {
type: 't',
value: textures[0],
},
b: {
type: 't',
value: textures[1],
}
},
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
});
var mesh = new THREE.Points(geometry, material);
mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
scene.add(mesh);
// on the first window click, paint red points
// on the second window click, paint blue points
var clicks = 0;
window.addEventListener('click', function() {
if (clicks == 0 || clicks == 1) {
var canvas = canvases[clicks];
var ctx = canvas.getContext('2d');
ctx.fillStyle = clicks == 0 ? 'red' : 'blue';
ctx.rect(0, 0, 16384, 16384);
ctx.fill();
textures[clicks].needsUpdate = true;
clicks++;
}
})
}
function getTexture(canvas) {
var tex = new THREE.Texture(canvas);
tex.needsUpdate = true;
tex.flipY = false;
return tex;
}
/**
* Create an element
**/
function getElem(tag, obj) {
var obj = obj || {};
var elem = document.createElement(tag);
Object.keys(obj).forEach(function(attr) {
elem[attr] = obj[attr];
})
return elem;
}
/**
* Add stats
**/
function getStats() {
var stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '65px';
stats.domElement.style.right = '5px';
stats.domElement.style.left = 'initial';
document.body.appendChild(stats.domElement);
return stats;
}
/**
* Render!
**/
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
controls.update();
stats.update();
};
/**
* Main
**/
var stats = getStats();
var scene = getScene();
var camera = getCamera();
var renderer = getRenderer();
var controls = getControls(camera, renderer);
addPoints(scene);
render();
</script>
</body>
</html>
Your canvases are 16384 by 16384. That's basically insanely large.
For RGBA format, that is 1073741824 bytes.. a gigabyte of texture data that is getting sent to your GPU from the CPU when you set that texture.needsUpdate = true
You will definitely notice this getting uploaded to the card.
If your use case absolutely requires textures that large.. then you may need to consider doing incremental updates via gl.texSubImage2D, or using a bunch of smaller textures and only updating one of the per frame, or only updating those textures at the start of your app, and not thereafter.
For reference, there are very few cases i've seen where textures > 4k per side are needed.
And that is about 1/16th the size of your textures.
This has nothing to do with three.js btw. It's a fundamental characteristic of GPU/CPU interaction. Uploads and state changes are slow and have to be carefully orchestrated and monitored.
I created two particles with a threejs BufferGeometry, I want to click on each particle shows the corresponding image.
But when I clicked on the particle the image was shown and another particle covered it.
I want to know how to get the particles out of the control of the hierarchy and keep the clicked particles always on top.
code:`
var scene, camera, renderer,controls;
var points;
var shaderMaterial;
var particleCount = 2;
function init () {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 15;
camera.position.y = 16;
camera.position.z = 35;
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
var light = new THREE.AmbientLight( 0xcccccc );
scene.add(light);
document.body.appendChild(renderer.domElement);
createParticles();
createGrid();
render();
document.querySelector('canvas').addEventListener( 'click', interactive, false );
}
function createParticles () {
var geometry = new THREE.BufferGeometry();
var positions = new Float32Array( particleCount * 3 );
var sizes = new Float32Array( particleCount );
var pop = new Float32Array( particleCount);
for (var i = 0, i3 = 0; i < particleCount; i ++, i3 += 3) {
positions[i3 + 0] = i* 10;
positions[i3 + 1] = 0.1;
positions[i3 + 2] = 1;
sizes[i] = 15;
pop[i] = 0.0;
}
geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.addAttribute( 'size', new THREE.BufferAttribute( sizes, 1 ) );
geometry.addAttribute( 'pop', new THREE.BufferAttribute( pop, 1 ) );
shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
'u_time': {type: 'f', value: 1.0},
'u_texture_0': { value: new THREE.TextureLoader().load('https://avatars2.githubusercontent.com/u/5829050?s=256&v=4') }},
vertexShader: document.getElementById( 'vs' ).textContent,
fragmentShader: document.getElementById( 'fs' ).textContent,
// blending: THREE.AdditiveBlending,
depthTest: false,
transparent: true
});
shaderMaterial.uniforms['u_texture_0'].value.flipY = false;
points = new THREE.Points(geometry, shaderMaterial);
scene.add(points);
}
var raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 5;
var touch = new THREE.Vector2();
var intersects, INTERSECTED;
var beforeIndex;
function interactive (event) {
touch.x = ( event.clientX / window.innerWidth ) * 2 - 1;
touch.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
points.geometry.computeBoundingSphere();
camera.updateMatrixWorld();
var vector = new THREE.Vector3(touch.x, touch.y, 0.5 ).unproject(camera);
raycaster.set(camera.position, vector.sub(camera.position ).normalize());
raycaster.setFromCamera( touch, camera );
intersects = raycaster.intersectObject(points);
if ( intersects.length > 0 ) {
if ( INTERSECTED != intersects[ 0 ].index ) {
INTERSECTED = intersects[ 0 ].index;
if (beforeIndex != INTERSECTED) {
points.geometry.attributes.pop.array[ beforeIndex ] = 0.0;
}
points.geometry.attributes.pop.array[ INTERSECTED ] = 1.0;
beforeIndex = INTERSECTED;
}
}
points.geometry.attributes.size.needsUpdate = true;
points.geometry.attributes.pop.needsUpdate = true;
}
function createGrid () {
var helper = new THREE.GridHelper( 100, 20, 0x303030, 0x303030 );
scene.add( helper );
}
function render () {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
init();
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
background: #000;
}
canvas {
display: block;
}
<script src="https://threejs.org/build/three.js"></script>
<script id="fs" type="x-shader/x-fragment">
precision highp float;
uniform sampler2D u_texture_0;
uniform float u_time;
varying float u_pop;
void main () {
vec2 uv = gl_PointCoord.xy;
vec4 rval = texture2D(u_texture_0,uv);
vec2 posToCenter = (uv - vec2(.5, .5)) * 2.0;
float distanceToCenter = length(posToCenter);
float fadeOpacity = 1. - smoothstep(0.8, 1., distanceToCenter);
float opacity = (1. - step(0.8, distanceToCenter)) + fadeOpacity;
vec3 bgColor = mix(vec3(255., 255., 255.), vec3(252., 222., 184.), distanceToCenter) / 255.;
vec4 color = vec4(mix(bgColor, rval.rgb, u_pop), 1.);
color.a = opacity;
gl_FragColor = color;
}
</script>
<script type="x-shader/x-vertex" id="vs">
attribute float size;
attribute float pop;
varying float u_pop;
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 300.0 / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
u_pop = pop;
}
</script>
`
You have misunderstanding about how 3D works, therefore, you use wrong concepts and terminology. There is no "Z-Index" in 3D. There is Z-buffer or Depth-buffer (two names, same thing), which reflects object distance from the render's point of view (camera, virtual observer). Naturarly, the purpose of the depth-buffer with depth-testing is to prevent farest objects to be rendered in front of closest ones (this also allow to optimize, by preventing unseen pixels to be computed).
Also, the background to foreground display is not controled by any hierarchy (unless the engine deliberately implements such feature), objects are simply rendered in order they are supplied. If the Depth-testing is disabled, the latest rendered object will be displayed in front of all previously rendered ones. In 3D scene, the hierarchy is relative to transformations, not display order (unless objects are rendered in scene's hierarchy order without depth-testing).
To achieve what you want in robust way, you'll have to disable the depth-desting and manually control the order which of sprites are rendered, to ensure the one which must be in "front", is the last rendered one. This is a pretty low-level manipulation, and unless Three.JS allow you to control that (which I doubt), you'll probably have to changes your tactic, or implements your own WebGL engine.
I am currently using the MeshPhongMaterial provided by Three.js to create a simple scene with basic water. I would like for the water material to have the Hard Light blending mode that can be found in applications such as Photoshop. How can I achieve the Hard Light blending modes below on the right?
The right halves of the images above are set to Hard Light in Photoshop. I am trying to recreate that Hard Light blend mode in Three.js.
One lead I have come across is to completely reimplement the MeshPhongMaterial's fragment and vertex shader, but this will take me some time as I am quite new to this.
What is the way to implement a Hard Light blending mode for a material in Three.js?
/*
* Scene config
**/
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0xffffff);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(0, 500, 1000);
camera.lookAt(scene.position);
/*
* Scene lights
**/
var spotlight = new THREE.SpotLight(0x999999, 0.1);
spotlight.castShadow = true;
spotlight.shadowDarkness = 0.75;
spotlight.position.set(0, 500, 0);
scene.add(spotlight);
var pointlight = new THREE.PointLight(0x999999, 0.5);
pointlight.position.set(75, 50, 0);
scene.add(pointlight);
var hemiLight = new THREE.HemisphereLight(0xffce7a, 0x000000, 1.25);
hemiLight.position.y = 75;
hemiLight.position.z = 500;
scene.add(hemiLight);
/*
* Scene objects
*/
/* Water */
var waterGeo = new THREE.PlaneGeometry(1000, 1000, 50, 50);
var waterMat = new THREE.MeshPhongMaterial({
color: 0x00aeff,
emissive: 0x0023b9,
shading: THREE.FlatShading,
shininess: 60,
specular: 30,
transparent: true
});
for (var j = 0; j < waterGeo.vertices.length; j++) {
waterGeo.vertices[j].x = waterGeo.vertices[j].x + ((Math.random() * Math.random()) * 30);
waterGeo.vertices[j].y = waterGeo.vertices[j].y + ((Math.random() * Math.random()) * 20);
}
var waterObj = new THREE.Mesh(waterGeo, waterMat);
waterObj.rotation.x = -Math.PI / 2;
scene.add(waterObj);
/* Floor */
var floorGeo = new THREE.PlaneGeometry(1000, 1000, 50, 50);
var floorMat = new THREE.MeshPhongMaterial({
color: 0xe9b379,
emissive: 0x442c10,
shading: THREE.FlatShading
});
for (var j = 0; j < floorGeo.vertices.length; j++) {
floorGeo.vertices[j].x = floorGeo.vertices[j].x + ((Math.random() * Math.random()) * 30);
floorGeo.vertices[j].y = floorGeo.vertices[j].y + ((Math.random() * Math.random()) * 20);
floorGeo.vertices[j].z = floorGeo.vertices[j].z + ((Math.random() * Math.random()) * 20);
}
var floorObj = new THREE.Mesh(floorGeo, floorMat);
floorObj.rotation.x = -Math.PI / 2;
floorObj.position.y = -75;
scene.add(floorObj);
/*
* Scene render
**/
var count = 0;
function render() {
requestAnimationFrame(render);
var particle, i = 0;
for (var ix = 0; ix < 50; ix++) {
for (var iy = 0; iy < 50; iy++) {
waterObj.geometry.vertices[i++].z = (Math.sin((ix + count) * 2) * 3) +
(Math.cos((iy + count) * 1.5) * 6);
waterObj.geometry.verticesNeedUpdate = true;
}
}
count += 0.05;
renderer.render(scene, camera);
}
render();
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
I don't think you're going to get the effect you want.
How do you generate the first image? I assume you just made fuzzy oval in photoshop and picked "hard light"?
If you want the same thing in three.js you'll need to generate a fuzzy oval and apply it in 2d using a post processing effect in three.js
You could generate such an oval by making a 2nd scene in three.js, adding the lights and shining them on a black plane that has no waves that's at the same position as the water is in the original scene. Render that to a rendertarget. You probably want only the spotlight and maybe point light in that scene. In your current scene remove the spotlight for sure. Render that to another render target.
When you're done combine the scenes using a post processing effect that implements hard light
// pseudo code
vec3 partA = texture2D(sceneTexture, texcoord);
vec3 partB = texture2D(lightTexture, texcoord);
vec3 line1 = 2.0 * partA * partB;
vec3 line2 = 1.0 - (1.0 - partA) * (1.0 - partB);
gl_FragCoord = vec4(mix(line2, line1, step(0.5, partA)), 1);
I ended up doing it in the following way thanks to gman's excellent answer. View the code snippet below to see it in action.
As gman described:
I created a WebGLRenderTarget to which the scene is rendered to.
The WebGLRenderTarget is then passed to the ShaderMaterial's uniforms as a texture, together with the window.innerWidth, window.innerHeight and color.
The respective texture coordinates, in relation to the current fragment, are calculated by dividing gl_FragCoord by the window's width and height.
The fragment can now sample what is on screen from the WebGLRenderTarget texture and combine that with the color of the object to output the correct gl_FragColor.
So far it works great. The only thing I am currently looking into is to create a separate scene containing only the objects that are necessary for blending, perhaps cloned. I assume that would be more performant. Currently I am toggling the visibility of the object to be blended in the render loop, before and after it is sent to the WebGLRenderTarget. For a larger scene with more objects, that probably doesn't make much sense and would complicate things.
var conf = {
'Color A': '#cc6633',
'Color B': '#0099ff'
};
var GUI = new dat.GUI();
var A_COLOR = GUI.addColor(conf, 'Color A');
A_COLOR.onChange(function(val) {
A_OBJ.material.uniforms.color = {
type: "c",
value: new THREE.Color(val)
};
A_OBJ.material.needsUpdate = true;
});
var B_COLOR = GUI.addColor(conf, 'Color B');
B_COLOR.onChange(function(val) {
B_OBJ.material.uniforms.color = {
type: "c",
value: new THREE.Color(val)
};
B_OBJ.material.needsUpdate = true;
});
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x888888);
renderer.setSize(window.innerWidth, window.innerHeight);
var target = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {format: THREE.RGBFormat});
document.body.appendChild(renderer.domElement);
camera.position.set(0, 0, 50);
camera.lookAt(scene.position);
var A_GEO = new THREE.PlaneGeometry(20, 20);
var B_GEO = new THREE.PlaneGeometry(20, 20);
var A_MAT = new THREE.ShaderMaterial({
uniforms: {
color: {
type: "c",
value: new THREE.Color(0xcc6633)
}
},
vertexShader: document.getElementById('vertexShaderA').innerHTML,
fragmentShader: document.getElementById('fragmentShaderA').innerHTML
});
var B_MAT = new THREE.ShaderMaterial({
uniforms: {
color: {
type: "c",
value: new THREE.Color(0x0099ff)
},
window: {
type: "v2",
value: new THREE.Vector2(window.innerWidth, window.innerHeight)
},
target: {
type: "t",
value: target
}
},
vertexShader: document.getElementById('vertexShaderB').innerHTML,
fragmentShader: document.getElementById('fragmentShaderB').innerHTML
});
var A_OBJ = new THREE.Mesh(A_GEO, A_MAT);
var B_OBJ = new THREE.Mesh(B_GEO, B_MAT);
A_OBJ.position.set(-5, -5, 0);
B_OBJ.position.set(5, 5, 0);
scene.add(A_OBJ);
scene.add(B_OBJ);
function render() {
requestAnimationFrame(render);
B_OBJ.visible = false;
renderer.render(scene, camera, target, true);
B_OBJ.visible = true;
renderer.render(scene, camera);
}
render();
body { margin: 0 }
canvas { display: block }
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.min.js"></script>
<script type="x-shader/x-vertex" id="vertexShaderA">
uniform vec3 color;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentShaderA">
uniform vec3 color;
void main() {
gl_FragColor = vec4(color, 1.0);
}
</script>
<script type="x-shader/x-vertex" id="vertexShaderB">
uniform vec3 color;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentShaderB">
uniform vec3 color;
uniform vec2 window;
uniform sampler2D target;
void main() {
vec2 targetCoords = gl_FragCoord.xy / window.xy;
vec4 a = texture2D(target, targetCoords);
vec4 b = vec4(color, 1.0);
vec4 multiply = 2.0 * a * b;
vec4 screen = 1.0 - 2.0 * (1.0 - a) * (1.0 - b);
gl_FragColor = vec4(mix(screen, multiply, step(0.5, a)));
}
</script>