FBO Particles with Cumulative Movement - three.js

Link to thread threejs discourse: https://discourse.threejs.org/t/fbo-particles-with-cumulative-movement/7221
This is difficult for me to explain because of my limited knowledge on the subject, but I'm gonna do my best..
At this point, I have a basic FBO particle system in place that works. The following is how it's set up:
var FBO = function( exports ){
var scene, orthoCamera, rtt;
exports.init = function( width, height, renderer, simulationMaterial, renderMaterial ){
var gl = renderer.getContext();
//1 we need FLOAT Textures to store positions
//https://github.com/KhronosGroup/WebGL/blob/master/sdk/tests/conformance/extensions/oes-texture-float.html
if (!gl.getExtension("OES_texture_float")){
throw new Error( "float textures not supported" );
}
//2 we need to access textures from within the vertex shader
//https://github.com/KhronosGroup/WebGL/blob/90ceaac0c4546b1aad634a6a5c4d2dfae9f4d124/conformance-suites/1.0.0/extra/webgl-info.html
if( gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) == 0 ) {
throw new Error( "vertex shader cannot read textures" );
}
//3 rtt setup
scene = new THREE.Scene();
orthoCamera = new THREE.OrthographicCamera(-1,1,1,-1,1/Math.pow( 2, 53 ),1 );
//4 create a target texture
var options = {
minFilter: THREE.NearestFilter,//important as we want to sample square pixels
magFilter: THREE.NearestFilter,//
format: THREE.RGBAFormat,//180407 changed to RGBAFormat
type:THREE.FloatType//important as we need precise coordinates (not ints)
};
rtt = new THREE.WebGLRenderTarget( width,height, options);
//5 the simulation:
//create a bi-unit quadrilateral and uses the simulation material to update the Float Texture
var geom = new THREE.BufferGeometry();
geom.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array([ -1,-1,0, 1,-1,0, 1,1,0, -1,-1, 0, 1, 1, 0, -1,1,0 ]), 3 ) );
geom.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array([ 0,1, 1,1, 1,0, 0,1, 1,0, 0,0 ]), 2 ) );
scene.add( new THREE.Mesh( geom, simulationMaterial ) );
//6 the particles:
//create a vertex buffer of size width * height with normalized coordinates
var l = (width * height );
var vertices = new Float32Array( l * 3 );
for ( var i = 0; i < l; i++ ) {
var i3 = i * 3;
vertices[ i3 ] = ( i % width ) / width ;
vertices[ i3 + 1 ] = ( i / width ) / height;
}
//create the particles geometry
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
//the rendermaterial is used to render the particles
exports.particles = new THREE.Points( geometry, renderMaterial );
exports.particles.frustumCulled = false;
exports.renderer = renderer;
};
//7 update loop
exports.update = function(){
//1 update the simulation and render the result in a target texture
// exports.renderer.render( scene, orthoCamera, rtt, true );
exports.renderer.setRenderTarget( rtt );
exports.renderer.render( scene, orthoCamera );
exports.renderer.setRenderTarget( null );
//2 use the result of the swap as the new position for the particles' renderer
// had to add .texture on the end of rtt for r103
exports.particles.material.uniforms.positions.value = rtt.texture;
};
return exports;
}({});
The following are the shaders it uses:
<script type="x-shader/x-vertex" id="simulation_vs">
//vertex shader
varying vec2 vUv;
void main() {
vUv = vec2(uv.x, uv.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="simulation_fs">
//fragment Shader
uniform sampler2D positions;//DATA Texture containing original positions
varying vec2 vUv;
void main() {
//basic simulation: displays the particles in place.
vec3 pos = texture2D( positions, vUv ).rgb;
// we can move the particle here
gl_FragColor = vec4( pos,1.0 );
}
</script>
<script type="x-shader/x-vertex" id="render_vs">
//vertex shader
uniform sampler2D positions;//RenderTarget containing the transformed positions
uniform float pointSize;//size
void main() {
//the mesh is a nomrliazed square so the uvs = the xy positions of the vertices
vec3 pos = texture2D( positions, position.xy ).xyz;
//pos now contains a 3D position in space, we can use it as a regular vertex
//regular projection of our position
gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );
//sets the point size
gl_PointSize = pointSize;
}
</script>
<script type="x-shader/x-fragment" id="render_fs">
//fragment shader
void main()
{
gl_FragColor = vec4( vec3( 1. ), .25 );
}
</script>
I understand that I would move the particles in the "simulation_fs", but if I move a particle in that shader, if I try to do something like this,
pos.x += 1.0;
it will still only shift it one unit from the original texture position. I want the movement to be cumulative.
Would using a 2nd set of simulation shaders allow me to move the particles in a cumulative way? Is that a practical solution?

For cumulative movement, you need to use uniforms:
Look into passing a uniform named time to your vertex shader. Then you can update the time once per frame, and you can use that to animate your vertex positions. For example:
position.x = 2.0 * time; // Increment linearly
position.x = sin(time); // Sin wave back-forth animation
Without a changing variable, your vertex animations will be static from one frame to the next.

Related

Texture atlas offset/repeat works for meshes but is ignored for point system particles

I am using a texture atlas to hold a sequence of images. When mapping to a mesh with MeshLambertMaterial, using Texture.offset and Texture.repeat works beautifully to cut the subtexture out of the entire image.
However, using the exact same texture instance for a PointCloudMaterial renders the particles with the entire atlas, not just the selected subimage.
I tried to follow the three.js source code, but the documentation is scarce.
Is there a workaround for this better than using canvases to chop up the image?
Edit: As requested, a work-in-progress is available at http://jnm2.com/minesweeper/.
THREE.PointCloudMaterial has been renamed THREE.PointsMaterial.
THREE.PointCloud has been renamed THREE.Points.
You want to use a sprite sheet with your point cloud.
Instead of using PointsMaterial with your Points, you can create a custom ShaderMaterial instead.
The custom ShaderMaterial can access your sprite sheet and use a different sub-image for each particle.
To do so, use a shader like this one:
<script type="x-shader/x-vertex" id="vertexshader">
attribute vec2 offset;
varying vec2 vOffset;
void main() {
vOffset = offset;
gl_PointSize = 25.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D texture;
uniform vec2 repeat;
varying vec2 vOffset;
void main() {
vec2 uv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );
vec4 tex = texture2D( texture, uv * repeat + vOffset );
if ( tex.a < 0.5 ) discard;
gl_FragColor = tex;
}
</script>
Set your BufferGeometry like so:
// vertices
var geometry = new THREE.SphereGeometry( 100, 16, 12 );
geometry.mergeVertices(); // to remove duplicate vertices at the poles
var vertices = geometry.vertices;
// geometry
geometry = new THREE.BufferGeometry();
// attributes
numVertices = vertices.length;
var positions = new Float32Array( numVertices * 3 ); // 3 coordinates per point
var offsets = new Float32Array( numVertices * 2 ); // 2 coordinates per point
geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.addAttribute( 'offset', new THREE.BufferAttribute( offsets, 2 ) );
// populate positions
geometry.attributes.position.copyVector3sArray( vertices );
// populate offsets
var offset = new THREE.Vector2();
for ( var i = 0, index = 0, l = numVertices; i < l; i ++, index += 2 ) {
offset.set( THREE.Math.randInt( 1, 3 ), THREE.Math.randInt( 2, 3 ) ).multiplyScalar( 0.25 ); // sprite sheet: 4 rows x 4 cols
offsets[ index ] = offset.x;
offsets[ index + 1 ] = offset.y;
}
And your ShaderMaterial like so:
// uniforms
uniforms = {
texture: { value: texture },
repeat: { value: new THREE.Vector2( 0.25, 0.25 ) }
};
// material
var material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
transparent: true
} );
// point cloud
pointCloud = new THREE.Points( geometry, material );
scene.add( pointCloud );
fiddle: http://jsfiddle.net/myy7x4zd/10/
three.js r.84

three.js texture across InstanceGeometry

I'm using InstanceGeometry to render thousands of base geometries (boxes) in a scene. It's efficient and uses only 1 material/texture, but repeats the image texture for every instance.
I'm trying to figure out how to have the texture spread out over x number of instances. Say for example there's 8 box instances, I'd like to 1/8 of the texture to appear on every box.
I think the transformUV function on THREE.Texture is what I'd want to use, but I'm not sure how to use it in this context. OR, would the texture mapping happen in the Shader itself?
UPDATE
My own code is pretty involved and uses the built-in three.js materials adapted for instances, so let's just use one of the three.js examples as a starting point: https://github.com/mrdoob/three.js/blob/master/examples/webgl_buffergeometry_instancing_dynamic.html
also pasted in brief below..
Vertex shader:
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec3 offset;
attribute vec2 uv;
attribute vec4 orientation;
varying vec2 vUv;
// http://www.geeks3d.com/20141201/how-to-rotate-a-vertex-by-a-quaternion-in-glsl/
vec3 applyQuaternionToVector( vec4 q, vec3 v ){
return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );
}
void main() {
vec3 vPosition = applyQuaternionToVector( orientation, position );
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( offset + vPosition, 1.0 );
}
Fragment shader
precision highp float;
uniform sampler2D map;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D( map, vUv );
}
JS:
var instances = 50;
var bufferGeometry = new THREE.BoxBufferGeometry( 2, 2, 2 );
var geometry = new THREE.InstancedBufferGeometry();
geometry.index = bufferGeometry.index;
geometry.attributes.position = bufferGeometry.attributes.position;
geometry.attributes.uv = bufferGeometry.attributes.uv;
// per instance data
var offsets = [];
var orientations = [];
var vector = new THREE.Vector4();
var x, y, z, w;
for ( var i = 0; i < instances; i ++ ) {
// offsets
x = Math.random() * 100 - 50;
y = Math.random() * 100 - 50;
z = Math.random() * 100 - 50;
vector.set( x, y, z, 0 ).normalize();
vector.multiplyScalar( 5 );
offsets.push( x + vector.x, y + vector.y, z + vector.z );
// orientations
x = Math.random() * 2 - 1;
y = Math.random() * 2 - 1;
z = Math.random() * 2 - 1;
w = Math.random() * 2 - 1;
vector.set( x, y, z, w ).normalize();
orientations.push( vector.x, vector.y, vector.z, vector.w );
}
offsetAttribute = new THREE.InstancedBufferAttribute( new Float32Array( offsets ), 3 );
orientationAttribute = new THREE.InstancedBufferAttribute( new Float32Array( orientations ), 4 ).setDynamic( true );
geometry.addAttribute( 'offset', offsetAttribute );
geometry.addAttribute( 'orientation', orientationAttribute );
// material
var material = new THREE.ShaderMaterial( {
uniforms: {
map: { value: new THREE.TextureLoader().load( 'textures/crate.gif' ) } },
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
You're going to have to create an additional custom attribute that holds the offset of UVs, just like you're creating an attribute that holds the x, y, z offsets, but with u, v.
First, you add it in JavaScript:
var uvOffsets = [];
var u, v;
for ( var i = 0; i < instances; i ++ ) {
//... inside the loop
u = Math.random(); // I'm assigning random, but you can do the math...
v = Math.random(); // ... to make it discrete 1/8th amounts
uvOffsets.push(u, v);
}
// Add new attribute to BufferGeometry
var uvOffsetAttribute = new THREE.InstancedBufferAttribute( new Float32Array( uvOffsets ), 2 );
geometry.addAttribute( 'uvOffset', uvOffsetAttribute );
Then, in your Vertex shader:
// [...]
attribute vec2 uv;
attribute vec2 uvOffset;
varying vec2 vUv;
void main() {
vec3 vPosition = applyQuaternionToVector( orientation, position );
// Divide uvs by 8, and add assigned offsets
vUv = (uv / 8.0) + uvOffset;
gl_Position = projectionMatrix * modelViewMatrix * vec4( offset + vPosition, 1.0 );
}
Finally, in your frag shader:
precision highp float;
uniform sampler2D map;
uniform vec2 uvOffset;
varying vec2 vUv; // <- these UVs have been transformed by vertex shader.
void main() {
gl_FragColor = texture2D( map, vUv ); // <- Transformation is applied to texture
}

How to increase line thickness in three.js edges geometry using shaders?

I'm trying to replicate the effect shown in this Three.js example but instead of showing the wireframe and an opaque box, I'd like to show just the edges without any faces (like what is shown when using the THREE.EdgesGeometry.) I know that setting the linewidth property doesn't work and that using shaders is necessary but I'm not really sure where to begin. For reference, these are the shaders being used in the above Three.js example:
Vertex Shader:
attribute vec3 center;
varying vec3 vCenter;
void main() {
vCenter = center;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
Fragment Shader:
varying vec3 vCenter;
float edgeFactorTri() {
vec3 d = fwidth( vCenter.xyz );
vec3 a3 = smoothstep( vec3( 0.0 ), d * 1.5, vCenter.xyz );
return min( min( a3.x, a3.y ), a3.z );
}
void main() {
gl_FragColor.rgb = mix( vec3( 1.0 ), vec3( 0.2 ), edgeFactorTri() );
gl_FragColor.a = 1.0;
}
I've gotten as far as figuring out that changing what d gets multiplied by (1.5 in the example) is what determines the thickness of the line but I'm completely lost as to how the vCenter variable is actually used (it's a vec3 that is either [1, 0, 0], [0, 1, 0] or [0, 0, 1]) or what I could use to make the THREE.EdgesGeometry render with thicker lines like in the example.
Here is what happens when I try rendering the edges geometry with these shaders:
<script type="x-shader/x-vertex" id="vertexShader">
attribute vec3 center;
varying vec3 vCenter;
void main() {
vCenter = center;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
varying vec3 vCenter;
uniform float lineWidth;
float edgeFactorTri() {
float newWidth = lineWidth + 0.5;
vec3 d = fwidth( vCenter.xyz );
vec3 a3 = smoothstep( vec3( 0.0 ), d * newWidth, vCenter.xyz );
return min( min( a3.x, a3.y ), a3.z );
}
void main() {
gl_FragColor.rgb = mix( vec3( 1.0 ), vec3( 0.2 ), edgeFactorTri() );
gl_FragColor.a = 1.0;
}
</script>
Javascript:
size = 150
geometry = new THREE.BoxGeometry(size, size, size);
material = new THREE.MeshBasicMaterial({ wireframe: true });
mesh = new THREE.Mesh(geometry, material);
mesh.position.x = -150;
scene.add(mesh);
//
// geometry = new THREE.BufferGeometry().fromGeometry(new THREE.BoxGeometry(size, size, size));
geometry = new THREE.EdgesGeometry(new THREE.BoxGeometry(size, size, size));
setupAttributes(geometry);
material = new THREE.ShaderMaterial({
uniforms: { lineWidth: { value: 10 } },
vertexShader: document.getElementById("vertexShader").textContent,
fragmentShader: document.getElementById("fragmentShader").textContent
});
material.extensions.derivatives = true;
mesh = new THREE.Mesh(geometry, material);
mesh.position.x = 150;
scene.add(mesh);
//
geometry = new THREE.BufferGeometry().fromGeometry(new THREE.SphereGeometry(size / 2, 32, 16));
setupAttributes(geometry);
material = new THREE.ShaderMaterial({
uniforms: { lineWidth: { value: 1 } },
vertexShader: document.getElementById("vertexShader").textContent,
fragmentShader: document.getElementById("fragmentShader").textContent
});
material.extensions.derivatives = true;
mesh = new THREE.Mesh(geometry, material);
mesh.position.x = -150;
scene.add(mesh);
jsFiddle
As you can see in the fiddle, this is not what I'm looking for, but I don't have a good enough grasp on how the shaders work to know where I'm going wrong or if this approach would work for what I want.
I've looked into this answer but I'm not sure how to use it as a ShaderMaterial and I can't use it as a shader pass (here are the shaders he uses for his answer.)
I've also looked into THREE.MeshLine and this issue doesn't seem to have been resolved.
Any guidance would be greatly appreciated!
You want to modify this three.js example so the mesh is rendered as a thick wireframe.
The solution is to modify the shader and discard fragments in the center portion of each face -- that is, discard fragments not close to an edge.
You can do that like so:
void main() {
float factor = edgeFactorTri();
if ( factor > 0.8 ) discard; // cutoff value is somewhat arbitrary
gl_FragColor.rgb = mix( vec3( 1.0 ), vec3( 0.2 ), factor );
gl_FragColor.a = 1.0;
}
You can also set material.side = THREE.DoubleSide if you want.
updated fiddle: https://jsfiddle.net/vy0we5wb/4.
three.js r.89

Shadow won't update when geometry is changed using VertexShader

I'm using a custom shader to curve a plane. My custom shader extends the Lambert shader so it supports lights and shadows. It all works as expected, but when the vertexShader changes the geometry of the plane, the shadow doesn't update. Is there anything I'm missing to flag that the geometry has updated in my vertexShader and the shadow needs to change?
[Here is a screenshot of the problem. The plane is curved with a vertexShader, but the shadow doesn't update][1]
[1]: http://i.stack.imgur.com/6kfCF.png
Here is the demo/code: http://dev.cartelle.nl/curve/
If you drag the "bendAngle" slider you can see that the shadow doesn't update.
One work-around I thought was to get the bounding box of my curved plane. Then use those points and create a new Mesh/Box and use that object to cast the shadow. But then I wasn't sure how to get the coordinates of the new curved geometry. When I would check geometry.boundingBox after the shader was applied, it would also just give me the original coordinates every time.
Thanks
Johnny
If you are modifying the geometry positions in the vertex shader, and you are casting shadows, you need to specify a custom depth material so the shadows will respond to the modified positions.
In your custom depth material's vertex shader, you modify the vertex positions in the same way you modified them in the material's vertex shader.
An example of a custom depth material can be seen in this three.js example, (although vertices are not modfied in the vertex shader in that example; they are modified on the CPU).
In your case, you would create a vertex shader for the custom depth material using a pattern like so:
<script type="x-shader/x-vertex" id="vertexShaderDepth">
uniform float bendAngle;
uniform vec2 bounds;
uniform float bendOffset;
uniform float bendAxisAngle;
vec3 bendIt( vec3 ip, float ba, vec2 b, float o, float a ) {
// your code here
return ip;
}
void main() {
vec3 p = bendIt( position, bendAngle, bounds, bendOffset, bendAxisAngle );
vec4 mvPosition = modelViewMatrix * vec4( p, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
And fragment shader like this:
<script type="x-shader/x-fragment" id="fragmentShaderDepth">
vec4 pack_depth( const in float depth ) {
const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );
const vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );
vec4 res = fract( depth * bit_shift );
res -= res.xxyz * bit_mask;
return res;
}
void main() {
gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );
}
</script>
Then in your javascript, you specify the custom depth material:
uniforms = {};
uniforms.bendAngle = { type: "f", value: properties.bendAngle };
uniforms.bendOffset = { type: "f", value: properties.offset };
uniforms.bendAxisAngle = { type: "f", value: properties.bendAxisAngle };
uniforms.bounds = { type: "v2", value: new THREE.Vector2( - 8, 16 ) };
var vertexShader = document.getElementById( 'vertexShaderDepth' ).textContent;
var fragmentShader = document.getElementById( 'fragmentShaderDepth' ).textContent;
myObject.customDepthMaterial = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader
} );
three.js r.74

How to tween 10,000+ particles in Three.js?

I have a THREE.Points object consisting of many (10,000+) vertices (a.k.a. particles).
However, I run into performance problems when I try to tween the location of the individual particles. This is expected given that I am using the following code which loops through all the particles and assigns each a tween.
var duration = 500;
for( var i = 0; i < particles.geometry.vertices.length; i++ ){
// http://threejs.org/examples/css3d_sprites.html
var currentVertex = particles.geometry.vertices[i];
new TWEEN.Tween( currentVertex )
.to(
{
x: newVertices[i].x,
y: newVertices[i].y,
z: newVertices[i].z,
},
duration * ( Math.random() + 1 )
)
.easing( TWEEN.Easing.Exponential.InOut )
.onUpdate( function(){
particles.geometry.verticesNeedUpdate = true;
})
.start();
}
Is there a better way to approach this?
I do not mind if all the particles are updated in one draw call to their new inbetween positions.
You are probably never going to get the performance you want animating that many particles in javascript alone.
Your best bet is probably moving your animation code to a shader so it is handled by the GPU which should easily be able to give you the performance you want.
There is a blog post of how to do this with code examples at : Animating a Million Letters Using Three.js
Got it running after chewing on it for a while.
Solution was a combination of using Buffer Geometry (as seen here) and using shaders as suggested by #2pha.
The tweening function was moved to the vertex shader where it was possible to fake per pixel tweening. The various data needed by the tween function was stored in the ShaderMaterial uniforms and BufferGeometry attributes.
Some pseudo code,
// Buffer Geometry
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( bPositions, 3 ) );
geometry.addAttribute( 'color', new THREE.BufferAttribute( bColors, 3 ) );
geometry.addAttribute( 'targetPosition', new THREE.BufferAttribute( bPositions2, 3 ) );
// Shader Material
var material = new THREE.ShaderMaterial({
uniforms: {
elapsedTime : {
type: "f",
value: 0.0
},
duration : {
type: "f",
value: 0.0
}
},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
});
// Vertex Shader
uniform float elapsedTime;
uniform float duration;
attribute vec3 targetPosition;
float exponentialInOut( float k ){
// https://github.com/tweenjs/tween.js/blob/master/src/Tween.js
if( k <= 0.0 ){
return 0.0;
}
else if( k >= 1.0 ){
return 1.0;
}
else if( ( k *= 2.0 ) < 1.0 ){
return 0.5 * pow( 1024.0, k - 1.0 );
}
return 0.5 * ( - pow( 2.0, - 10.0 * ( k - 1.0 ) ) + 2.0 );
}
void main(){
// calculate time value (also vary duration of each particle)
float t = elapsedTime / ( duration * ( 1.0 + randomNum.x ) );
// calculate progress
float progress = exponentialInOut( t );
// calculate new position (simple linear interpolation)
vec3 delta = targetPosition - position;
vec3 newPosition = position + delta * progress;
// something
gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
}

Resources