three.jsglsllightingparticle-systemfbo

ThreeJS FBO Particle Lighting and Shadows


Update: I decided to implement a simple lighting solution from this link, http://blog.edankwan.com/post/three-js-advanced-tips-shadow, suggested by ScieCode. It's the first part of the linked page. The result can be seen in the picture at the bottom of this post.

The linked page does include information on how to cast shadows using a custom shader material in ThreeJS as well, but I feel it's more suited for meshes and basic particle systems. I'm working with an FBO particle system.

I have a particle system in place using FBOs. It's built using shader materials in ThreeJS. The particles become towering cumulus clouds. How can I apply lighting and shadows to these particles as if they were one solid mesh?

Below are the last set of shaders that render the particles to the screen:

To be honest, I don't even fully understand how the vertex shader is outputting the positions. I found this example code and suited it for what I'm doing. I understand most of it, but the tail end of the vertex shader is fuzzy to me.

Vertex:

precision highp float;

attribute vec2 id;
attribute vec3 p_color;
attribute float p_size;

uniform vec2 dimensions;
uniform float size;

uniform sampler2D infoTexture;
uniform sampler2D originalTexture;
uniform sampler2D positionTexture;
uniform sampler2D previousPositionTexture;
uniform sampler2D velocityTexture;

varying vec2 vUv;
varying float life;
varying vec2 vSpeed;
varying float vSize;

float modI(float a,float b) {
    float m=a-floor((a+0.5)/b)*b;
    return floor(m+0.5);
}

const float PI = 3.14159265359;

void main() {

    float size_modifier = 1. * p_size;

    float ptr = id.x;
    float u = modI( ptr, dimensions.x ) / dimensions.x;
    float v = ( ptr / dimensions.x ) / dimensions.y;
    vec2 puv = vec2( u, v );

    vec4 velocity = texture2D( velocityTexture, puv );
    // vSpeed = .1 * ( projectionMatrix * modelViewMatrix * vec4( velocity.xyz, 1. ) ).xy;

    vec4 i = texture2D( infoTexture, puv );

    vec4 prev_pos = texture2D( previousPositionTexture, puv );
    vec4 origin_pos = texture2D( originalTexture, puv );

    vec4 c = texture2D( positionTexture, puv );
    vec3 p = c.xyz;
    vUv = uv;
    float life_time = 1000.;
    life = 1. - ( c.a / life_time );
    // if( c.a == 0. ) life = 0.;

    if( velocity.a != 1. ){

        size_modifier = .0;

    }

    vec4 modified = modelViewMatrix * vec4( p, 1. );
    // float a = -atan( vSpeed.y, vSpeed.x ) - .5 * PI;
    // float l = clamp( length( vSpeed ), .5, 4. );
    // mat2 rot = mat2( cos( a ), -sin( a ), sin( a ), cos( a ) );

    // modified.xyz += size * i.x * 10. * vec3( rot * position.xy, 0. );
    // modified.xyz += size * size_modifier * i.x * 10. * vec3( rot * position.xy, 0. );
    modified.xyz += size * size_modifier * i.x * 10. * vec3( position.xy, 0. );
    gl_Position = projectionMatrix * modified;

}

Fragment:

precision highp float;

varying float vSize;
varying vec2 vUv;
varying float life;
varying vec2 vSpeed;
varying vec3 p_color_f;

uniform float useTriangles;

const vec2 barycenter = vec2( .5, .6666 );

void main() {

    // render circles
    float d = smoothstep( .5, .55, 1. - 2. * length( vUv - barycenter ) );
    if( d <= 0. ) discard;

    vec3 frag_c = vec3( p_color_f );

    gl_FragColor = vec4( frag_c, 1.);

}

How I added the shader chunks to the custom shader:

particleMaterial = new THREE.ShaderMaterial( {
    uniforms: THREE.UniformsUtils.merge([
        THREE.UniformsLib.shadowmap,
        THREE.UniformsLib.lights,
        THREE.UniformsLib.ambient,
        {
            size: { type: 'f', value: 1 },
            useTriangles: { type: 'f', value: 0 },
            originalTexture: { type: 't', value: texture },
            infoTexture: { type: 't', value: texture2 },
            positionTexture: { type: 't', value: positionSim.fbos[ 0 ] },
            previousPositionTexture: { type: 't', value: positionSim.fbos[ 1 ] },
            velocityTexture: { type: 't', value: velocitySim.fbos[ 1 ] },
            dimensions: { type: 'v2', value: dimensions },
            cameraPosition: { type: 'v3', value: new THREE.Vector3() },
            prevModelViewMatrix: { type: 'm4', value: new THREE.Matrix4() },
            prevProjectionMatrix: { type: 'm4', value: new THREE.Matrix4() }
        } ]),
    vertexShader: document.getElementById( 'particle-vs' ).textContent,
    fragmentShader: document.getElementById( 'particle-fs' ).textContent,
    transparent: false,
    side: THREE.DoubleSide,
    depthTest: true,
    depthWrite: true,
    lights: true
    // blending: THREE.NoBlending
} );

Basic lighting result: enter image description here

Even that added a lot of depth to the storm.


Solution

  • It's not exactly clear how you want the shadows or lighting to behave. If you could make it more clear, I would gladly edit this response to best answer your questions.

    With that being said, there are two resources that could be viable in your case:

    In order to simulate correct lighting, you need to import light_pars ShaderChunks and all of its dependencies. With that, you can get access to light intensity on each particle vertex position. You can use that information on whichever way you seem fit.