Improved Area Lighting in WebGL & ThreeJS

前端 未结 5 620
旧巷少年郎
旧巷少年郎 2020-12-22 19:04

I have been working on an area lighting implementation in WebGL similar to this demo:

http://threejs.org/examples/webgldeferred_arealights.html

The above imp

5条回答
  •  再見小時候
    2020-12-22 19:56

    The good news is there is a solution; but first the bad news.

    Your approach of using the point that maximizes the dot product is fundamentally flawed, and not physically plausible.

    In your first illustration above, suppose that your area light consisted of only the left half.

    The "purple" point -- the one that maximizes the dot-product for the left half -- is the same as the point that maximizes the dot-product for both halves combined.

    Therefore, if one were to use your proposed solution, one would conclude that the left half of the area light emits the same radiation as the entire light. Obviously, that is impossible.

    The solution for computing the total amount of light that the area light casts on a given point is rather complicated, but for reference, you can find an explanation in the 1994 paper The Irradiance Jacobian for Partially Occluded Polyhedral Sources here.

    I suggest you look at Figure 1, and a few paragraphs of Section 1.2 -- and then stop. :-)

    To make it easy, I have coded a very simple shader that implements the solution using the three.js WebGLRenderer -- not the deferred one.

    EDIT: Here is an updated fiddle: http://jsfiddle.net/hh74z2ft/1/

    enter image description here

    The core of the fragment shader is quite simple

    // direction vectors from point to area light corners
    
    for( int i = 0; i < NVERTS; i ++ ) {
    
        lPosition[ i ] = viewMatrix * lightMatrixWorld * vec4( lightverts[ i ], 1.0 ); // in camera space
    
        lVector[ i ] = normalize( lPosition[ i ].xyz + vViewPosition.xyz ); // dir from vertex to areaLight
    
    }
    
    // vector irradiance at point
    
    vec3 lightVec = vec3( 0.0 );
    
    for( int i = 0; i < NVERTS; i ++ ) {
    
        vec3 v0 = lVector[ i ];
        vec3 v1 = lVector[ int( mod( float( i + 1 ), float( NVERTS ) ) ) ]; // ugh...
    
        lightVec += acos( dot( v0, v1 ) ) * normalize( cross( v0, v1 ) );
    
    }
    
    // irradiance factor at point
    
    float factor = max( dot( lightVec, normal ), 0.0 ) / ( 2.0 * 3.14159265 );
    

    More Good News:

    1. This approach is physically correct.
    2. Attenuation is handled automatically. ( Be aware that smaller lights will require a larger intensity value. )
    3. In theory, this approach should work with arbitrary polygons, not just rectangular ones.

    Caveats:

    1. I have only implemented the diffuse component, because that is what your question addresses.
    2. You will have to implement the specular component using a reasonable heuristic -- similar to what you already have coded, I expect.
    3. This simple example does not handle the case where the area light is "partially below the horizon" -- i.e. not all 4 vertices are above the plane of the face.
    4. Since WebGLRenderer does not support area lights, you can't "add the light to the scene" and expect it to work. This is why I pass all necessary data into the custom shader. ( WebGLDeferredRenderer does support area lights, of course. )
    5. Shadows are not supported.

    three.js r.73

提交回复
热议问题