1
votes

EDIT: Updated the JSFiddle link as it wasn't rendering correctly in Chrome on Windows 7.

Context

I'm playing around with particles in THREE.JS and using a frame buffer / render target (double buffered) to write positions to a texture. This texture is affected by its own ShaderMaterial, and then read by the PointCloud's ShaderMaterial to position the particles. All well and good so far; everything works as expected.

What I'm trying to do now is use my scene's depth texture to see if any of the particles are intersecting my scene's geometry.

The first thing I did was to reference my depth texture in the PointCloud's fragment shader, using gl_FragCoord.xy / screenResolution.xy to generate my uv for the depth texture lookup.

There's a JSFiddle of this here. It's working well - when a particle is behind something in the scene, I tell the particle to be rendered red, not white.

My issue arises when I try to do the same depth comparison in the position texture shader. In the draw fragment shader, I can use the value of gl_FragCoord to get the particle's position in screen space and use that for the depth uv lookup, since in the draw vertex shader I use the modelViewMatrix and projectionMatrix to set the value of gl_Position.

I've tried doing this in the position fragment shader, but to no avail. By the way, what I'm aiming to do with this is particle collision with the scene on the GPU.

So... the question (finally!):

  • Given a texture where each pixel/texel is a world-space 3d vector representing a particle's position, how can I project this vector to screen-space in the fragment shader, with the end goal of using the .xy properties of this vector as a uv lookup in the depth texture?

What I've tried

  • In the position texture shader, using the same transformations as the draw shader to transform a particle's position to (what I think is) screen-space using the model-view and projection matrices:

    // Position texture's fragment shader:
    void main() {
        vec2 uv = gl_FragCoord.xy / textureResolution.xy;
        vec4 particlePosition = texture2D( tPosition, uv );
    
        vec2 screenspacePosition = modelViewMatrix * projectionMatrix * vec4( particlePosition, 1.0 );
        vec2 depthUV = vec2( screenspacePosition.xy / screenResolution.xy );
        float depth = texture2D( tDepth, depthUV ).x;
    
        if( depth < screenspacePosition.z ) {
            // Particle is behind something in the scene,
            // so do something...
        }
    
        gl_FragColor = vec4( particlePosition.xyz, 1.0 );  
    }
    
  • Variations on a theme of the above:

    • Offsetting the depth's uv by doing 0.5 - depthUV
    • Using the tPosition texture resolution instead of the screen resolution to scale the depthUV.
    • Another depth uv variation: doing depthUV = (depthUV - 1.0) * 2.0;. This helps a little, but the scale is completely off.

Help! And thanks in advance.

1
Is your depth texture normalized? I.e. should you be dividing screenspacePosition.z by screenspacePosition.w?ovikoomikko
Yep, the depth texture isn't the issue (IMO). Rendering the depth texture to an on-screen quad shows everything as it should be (grayscale, 0.0 to 1.0).SquareFeet
Quick update: I think my problems lie in the modelView and/or projection matrices being passed by THREE to the position FBO/RTT shader. Manually passing the correct matrices as uniforms seems to help it a little (i.e it's now comparing depths properly, but the scale of the depth-texture when using a particle position as UV is too small. Hmm!SquareFeet
Ah-ha! I think I've found the issue - it was to do with the matrices. I'll make a new JSFiddle and post it as an answer with as much detail as I can just in case anyone else has this problem...SquareFeet

1 Answers

0
votes

After a lot of experimentation and research, I narrowed the issue down to the values of modelViewMatrix and projectionMatrix that THREE.js automatically assigns when one creates an instance of THREE.ShaderMaterial.

What I wanted to do was working absolutely fine when in my 'draw' shaders, where the modelViewMatrix for these shaders was set (by THREE.js) to:

new THREE.Matrix4().multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld)

It appears that when one creates a ShaderMaterial to render values to a texture (and thus not attached to an object in the scene/world), the object.matrixWorld is essentially an identity matrix. What I needed to do was to make my position texture shaders have the same modelViewMatrix value as my draw shaders (which were attached to an object in the scene/world).

Once that was in place, the only other thing to do was make sure I was transforming a particle's position to screen-space correctly. I wrote some helper functions in GLSL to do this:

    // Transform a worldspace coordinate to a clipspace coordinate
    // Note that `mvpMatrix` is: `projectionMatrix * modelViewMatrix`
    vec4 worldToClip( vec3 v, mat4 mvpMatrix ) {
        return ( mvpMatrix * vec4( v, 1.0 ) );
    }

    // Transform a clipspace coordinate to a screenspace one.
    vec3 clipToScreen( vec4 v ) {
        return ( vec3( v.xyz ) / ( v.w * 2.0 ) );
    }

    // Transform a screenspace coordinate to a 2d vector for
    // use as a texture UV lookup.
    vec2 screenToUV( vec2 v ) {
        return 0.5 - vec2( v.xy ) * -1.0;
    }

I've made a JSFiddle to show this in action, here. I've commented it (probably too much) so hopefully it explains what is going on well enough for people that aren't familiar with this kind of stuff to understand.

Quick note about the fiddle: it doesn't look all that impressive, as all I'm doing is emulating what depthTest: true would do were that property set on the PointCloud, albeit in this example I'm setting the y position of particles that have collided with scene geometry to 70.0, so that's what the white band is near the top of the rendering screen. Eventually, I'll do this calculation in a velocity texture shader, so I can do proper collision response.

Hope this helps someone :)

EDIT: Here's a version of this implemented with a (possibly buggy) collision response.