1
votes

I've got a ribbon-like mesh made out of a buffer geometry, the faces of which are invisible. I'm using a custom shader for this -- gl_FragColor = vec4(1.0,1.0,1.0,0.0).

As a 3DObject is moved along the ribbon, I raycast the index of the faces over which it is, and update the shader making them.

I'm not satisfied with the effect since it results in a pretty chunky animation. If I add more faces, then I get a serious performance hit.

Would there be any way for me to update the fragment's alpha based on the 3DObject position without having to resort to entire faces?

On a side note: I've also tried clipping the ribbon from view, based on x/z pixel positions. The effect is perfect, the performance is good, but I get undesired effects when the ribbon makes sharp turns.

1

1 Answers

2
votes

You can render only part of your BufferGeometry by setting drawRange like so:

mesh.geometry.setDrawRange( startIndex, count );

For more information and a live example see this answer.


EDIT - As mentioned in the comments below, another approach is to add an attribute to your geometry which represents the fractional distance each vertex is positioned along the geometry. This approach will require a custom ShaderMaterial, but you will be able to smoothly animate the drawing of your mesh.

Here is an example vertex shader and fragment shader

<script id="vertex_shader" type="x-shader/x-vertex">

attribute float distance;

varying float vDistance;
varying vec3 vNormal;
varying vec3 vViewPosition;

void main() {

    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );

    vDistance = distance;
    vNormal = normalize( normalMatrix * normal );
    vViewPosition = - mvPosition.xyz;

    gl_Position = projectionMatrix * mvPosition;

}

</script>

<script id="fragment_shader" type="x-shader/x-fragment">

uniform float fraction;

varying float vDistance;
varying vec3 vNormal;
varying vec3 vViewPosition;

void main() {

    if ( vDistance > fraction ) discard;

    vec3 color = vec3( 0.25, 0.5, 1.0 );

    // hack in a fake pointlight at camera location, plus ambient
    vec3 normal = normalize( vNormal );
    vec3 lightDir = normalize( vViewPosition );

    float dotProduct = max( dot( normal, lightDir ), 0.0 ) + 0.2;

    // trick to make the clipped ends appear solid
    gl_FragColor = ( gl_FrontFacing ) ? vec4( color * dotProduct, 1.0 ) : vec4( color, 1.0 );

}

</script>

Here is how to add an attribute to your BufferGeometry and instantiate the material.

// new attribute
var numVertices = geometry.attributes.position.count;
var distance = new Float32Array( numVertices * 1 ); // 1 value per vertex
geometry.addAttribute( 'distance', new THREE.BufferAttribute( distance, 1 ) );

// populate attribute
for ( var i = 0, l = numVertices; i < l; i ++ ) {

    // set new attribute
    distance[ i ] = ( geometry.attributes.position.getY( i ) + 10 ) / 20;

    // wiggle geometry a bit while we're at it
    var x = geometry.attributes.position.getX( i )
    var y = geometry.attributes.position.getY( i );
    geometry.attributes.position.setX( i, x + 2 * Math.sin( y ) );

}

// uniforms
var uniforms = {
    "fraction" : { value: 0 }
};

// material
var material = new THREE.ShaderMaterial( {
    uniforms        : uniforms,
    vertexShader    : document.getElementById( 'vertex_shader' ).textContent,
    fragmentShader  : document.getElementById( 'fragment_shader' ).textContent,
    side: THREE.DoubleSide
} );

// mesh
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );

Then, in your render loop, set the desired fraction to render:

mesh.material.uniforms.fraction.value = 0.5 * ( 1 + Math.cos( t ) );

fiddle: http://jsfiddle.net/m99aj10b/

fiddle shapshot

three.js r.76