penumbra shadows in raymarched SDFs

http://www.iquilezles.org/www/articles/rmshadows/rmshadows.htm

intro
one of the advantes of distance fields, is that they naturally provide global information. this means that when shading a point, one can easily explore the surrounding geometry by simply querying the distance functin. Unlike in a classic rasterizer (REYES or scaline based), , where one has to bake the global data somehow as a preprocess for later consumtion (in a shadowmap, depthmap, pointcloud…), or in a raytracer where finding global information must be done by sampling the geometry by raycasting, in a distance field the information is ready for use at shading time, pretty much for free (“free” with many quotes of course). This means that many of the more realistic shading and ilumination techniques are easy to implement with distance fields. And this is even more true when sampling/rendering distance fields with a raymarcher. In this article we are going to exploit this nice properties to render soft shadows with penumbra, for free, when doing raymarching based rendering.
在这里插入图片描述
soft shadow and penumbra computed for free

在这里插入图片描述
classic shadow raycasting

the trick
so, let’s assume u have a distance field encoded in function float map(vec3 p). u can see how to construct some basic distance functions here. http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
just let us assume, for simplicity, that this map() function contains all of the world you are rendering, and that all objects are allowed to cast shadows in all other objects. then, the easy way to compute shadowing information at a shading point, is to raymarch along the light vector, as far as the distance from the light to the shading point is, until an intersection is found. u can probably do that with some code like this:

float shadow( in vec3 ro, in vec3 rd, float mint, float maxt )
{
    for( float t=mint; t<maxt; )
    {
        float h = map(ro + rd*t);
        if( h<0.001 )
            return 0.0;
        t += h;
    }
    return 1.0;
}

this code works beautifully, and produces nice and accurate sharp shadows, as seen in the top-rightmost image in the articles. now, we can add only one line of code and make this look much better!

The trick is to think what happens when a shadow ray doesn’t hit any object, but was just pretty close to do so. Then, perhaps, you want to put that point you are shading under penumbra. Probably, the closest your point was to hit an object, the darker you want to make it. Also, the closest this happened from the point you are shading, the darker too. Well, it happens that as we raymarched our shadow ray, both these distances where available to us! Of course he first one is h in the code above, and the second one is t. So, we can simply compute a penumbra factor for every step point in our marching process and take the darkest of all penumbras. In 2019 a few Shadertoy users noticed that one can also offset and bias the shadow computation by a half to get inner penumbra as well. The final code looks like this:

float softshadow( in vec3 ro, in vec3 rd, float mint, float maxt, float w )
{
    float s = 1.0;
    for( float t=mint; t<maxt; )
    {
        float h = map(ro + rd*t);
        s = min( s, 0.5+0.5*h/(w*t) );
        if( s<0.0 ) break;
        t += h;
    }
    s = max(s,0.0);
    return s*s*(3.0-2.0*s); // smoothstep
}

You can optimize the 0.5 multiplication and addition by shifting the problem to the -1…1 range instead of 0…1. You can find a reference implementation here: https://www.shadertoy.com/view/WdyXRD.
在这里插入图片描述
This simple modification is enough to generate the much nicer left image in the begining of this page. As you can see, the improvement is massive: not only you get soft shadows, but they even behave realistically as when shadows are sharp next to ocluder and ocludee contact (see where the bridge touches the floor) and much softer penumbras when the ocluder is far from the ocluded point. This, at the cost of one division per marching point, which represents a zero cost relative to the evaluation of map().

The parameter w is the size of the light source, and controls how hard/soft the shadows are. See images on the right of this text to compare the same shadow rendered with different values of w.

So, basically, if you can do classic raymarched shadows, you can do soft shadows with penumbras too. For free!

The image below shows an example of the technique in action, in a raymarched procedural distance field:
在这里插入图片描述
soft penumbra shadows in action

and you can see a lot more examples of this technique in action in the raymarching with signed distance fields article.

An improvement
7 years after the publication of this technique, Sebastian Aaltonen published an improvement at his GDC presentation, that helps some of the banding artifacts you can get from this technique, especially for shadows from caster with sharp corners.

Beware that for this algorithm to be stable, we should be searching for penumbras exaustively along the ray. However since we are marching, chances are we miss the point along the ray that produces the darkest penumbra. That can manifest as light leaking in patterns that match the marching steps. In particular, sharp corners in the shadow casters are a usual source of missed darkes penunbras. Sebastian’s technique helps with the situation by computing the penumbra as h/t not just at the ray marching sampling positions, but at an estimation of the closest point from the surface to the marched ray at each iteration. Or in other words, by using the current sampling point and the previous one, his techniques computes a closest distance estimation by trangulating the information. The picture below shows the geometry of the situation:
在这里插入图片描述
The white arrow is the ray we are marching. The green dot is the current position along the ray, and the red dot is the previous position. The green and red circles represent the current and previous SDF unbounding spheres. One can estimate that the closest surface will be at a point close to where these two spheres meet (yellow line and pair of dots). The closest point along the ray will be the intersection of that yellow area with the actual ray (yellow dot in the center).

Lets call y the distance from the current point (green) to that closest point along the ray (yellow), and d the distance from that point to the estimated closest distance (half the length of the yellow line in the diagram above). Then, the code to compute these two quantities is pretty easy:

float y = r2*r2/(2.0*r1);
float d = sqrt(r2*r2-y*y);

where r1 and r2 are the radius of the red and green sphere, or in other words, the SDFs evaluation at the previous and current raymarch points. From these two quantities, we can improve our penumbra shadow estimation by doing:

float softshadow( in vec3 ro, in vec3 rd, float mint, float maxt, float k )
{
    float res = 1.0;
    float ph = 1e20;
    for( float t=mint; t<maxt; )
    {
        float h = map(ro + rd*t);
        if( h<0.001 )
            return 0.0;
        float y = h*h/(2.0*ph);
        float d = sqrt(h*h-y*y)
        res = min( res, k*d/max(0.0,t-y) );
        ph = h;
        t += h;
    }
    return res;
}

This produces better shadows in difficult cases, as can be seen in the comparison below:
在这里插入图片描述
You can find reference implementation for the improved method here: https://www.shadertoy.com/view/lsKcDD

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值