Forward and backward alpha blending for raymarching

When implementing raymatching by scanning a ray to accumulating scene's colors, containing semi-transparent colors (for example, scene with clouds), it's required to properly combine colors using alpha blending method. Here we discuss how to do it and obtain corresponding formula.
(So, here we propose that objects has "uniform" ambient lighting and some transparency and all we need to accumulate their colors).


The forward alpha blending formula, used "everywhere" - in OpenGL, VJ software, photo and video editors, can be formulated in the following way:

Formula for "forward" alpha blending

----------------------------------
Let C is a current RGB color, c is a new RGB color and alpha is a new color's opaqueness (alpha is a float value from 0 to 1).

Then updating rule for C is the following:
    C = (1-alpha)*C + alpha*c  
----------------------------------
So, the more alpha, the more c affects C.

This is a forward blending formula, which works for situation then a new color is rendered over old color. In a photo editor, it means what new layer is rendered over previous layer.

But, in raymarching we has the opposite situation: we propagate ray starting from near clip plane to the scene's back. We shouldn't reverse ray scanning, because we want to stop such forward ray propagation when accululated density achieves some threshold,  which reduces computation cost a lot (exception is when scene constsis of very transparent objects, so threshold is not achieved).

Hence, for applying raymarching, we need to use "reversed" alpha blending formula.
Let obtain it.

Let C(k) - accumulated color on the step k, c(k), alpha(k) - new color and its opaqueness on the same step.
By the forward alpha blending formula,
C(k) = (1-alpha(k))*C(k-1) + c(k).

Move to the previous step and write what C(k-1) is:
C(k) = (1-alpha(k))*((1-alpha(k-1))*C(k-2) + c(k-1)) + c(k)
= (1-alpha(k)*(1-alpha(k-1))*C(k-2) + (1-alpha(k))*c(k-1) + c(k).

So, we see what (1-alpha(k))*... - can be accumulated from the first step.
In this way, we get the following method (it can be proved by induction):

Formula for "reversed" alpha blending

----------------------------------
Start:
C = 0
T = 1 - float value, which accululates transparency.
Update:
C = C + A * alpha * c
T = T * (1 - alpha)
----------------------------------
Here T is transparency
If T becomes less some threshold, say, 0.05 or 0.1, we could stop scanning the ray (because the color is almoth opaque end can't change radically).

So, the shader's code for this can look as a following:

Shader's code

//------------------------------------
vec4 C = vec4(0,0,0,1);   //store T as C.a
vec3 p = origin;  //p - point for scanning tha ray, origin - ray's origin


for(int i=0; i<nbSample; i++) {   //nbSample - number of steps along ray
        vec4 c =  scene(p);    //scene function returns scene's color at point p
        if (c.a>0.) {           
            C.rgb += C.rgb * (C.a * c.a);
            C.a *= (1-c.a);
            if (C.a < 0.05) break;  //stop scanning if transparency less 0.05
        }
        p += dir*step; //move point along ray (step can increased, but here is fixed)
}
C.a = 1;   //finally, set resulted color's opaqueness to maximum to put it into the screen
//If you need to put the resulted scene over some other layer, then use instead this formula:
//C.a = 1-C.a;   //that is, C.a now means opaqueness

//(output of a shader)
fragColor = C;

//------------------------------------














Comments

  1. I'm literally just signing in here to fix your atrocious and unreadable choice of 'C' for accumulatedColor and c currentcolor.


    ```

    // T is the transparency, whereas if T is a very small number, we are fully opaque, like (1-alpha)
    vec4 accumulatedColor = vec4(0,0,0,1); //store T as accumulatedColor.a
    vec3 p = origin; //p - point for scanning tha ray, origin - ray's origin


    for(int i=0; i0.) {
    accumulatedColor.rgb += accumulatedColor.rgb * (accumulatedColor.a * currentColor.a);
    accumulatedColor.a *= (1-currentColor.a);
    if (accumulatedColor.a < 0.05) break; //stop scanning if transparency less 0.05
    }
    p += dir*step; //move point along ray (step can increased, but here is fixed)
    }
    accumulatedColor.a = 1; //finally, set resulted color's opaqueness to maximum to put it into the screen
    //If you need to put the resulted scene over some other layer, then use instead this formula:
    //accumulatedColor.a = 1-accumulatedColor.a; //that is, accumulatedColor.a now means opaqueness

    //(output of a shader)
    fragColor = accumulatedColor;

    //------------------------------------

    ```

    ReplyDelete

Post a Comment

Popular posts from this blog

Computing ray origin and direction from Model View Projection matrices for raymarching

Forward, Deferred and Raytracing rendering in openFrameworks and web