1. Why Ray Marching

前言

作為這段時(shí)間的搞事用項(xiàng)目,在這里暫且記錄下一路學(xué)習(xí)過(guò)來(lái)的心得體會(huì),畢竟我現(xiàn)在才剛?cè)腴T(mén)。

Ray Marching是什么

假定大家對(duì)光線(xiàn)跟蹤(Ray Tracing)都十分熟悉,那么Ray Marching可以看做Ray Tracing的一種實(shí)現(xiàn),主要針對(duì)“光線(xiàn)與物體求交”這一步,每次讓光線(xiàn)前進(jìn)一定步長(zhǎng),并檢測(cè)當(dāng)前光線(xiàn)是否位于物體表面,據(jù)此調(diào)整光線(xiàn)前進(jìn)幅度,直到抵達(dá)物體表面,再按照一般光線(xiàn)追蹤的方法計(jì)算顏色值。

為什么要用Ray Marching

咋看起來(lái)Ray Marching好像比主流的Ray Tracing實(shí)現(xiàn)粗暴多了,光線(xiàn)不知道得走多少步才能碰到物體表面,效率應(yīng)該也比主流Ray Tracing低不少的樣子。鑒于目前光線(xiàn)跟蹤效率并不高(不過(guò)在不久的未來(lái)還是有希望應(yīng)用在高質(zhì)量游戲渲染上的,題外話(huà)題外話(huà)),這玩意好像沒(méi)啥卵用的樣子。

是否真的如此呢?我們看下面一張圖:

https://www.shadertoy.com/view/4ttSWf,作者主頁(yè)http://www.iquilezles.org/www/articles/raymarchingdf/raymarchingdf.htm

這是使用Ray Marching實(shí)現(xiàn)的森林地貌的實(shí)時(shí)渲染,諸多樹(shù)木、復(fù)雜地形、軟陰影、虛焦、云彩等等有著非常不錯(cuò)的渲染效果,而且在一般的集顯上也能跑到將近10的fps(可以想象在gtx1080上就能跑實(shí)時(shí)光線(xiàn)追蹤游戲了)。僅僅使用一張代表地形的紋理,整個(gè)畫(huà)面基本是由計(jì)算機(jī)實(shí)時(shí)生成的??梢韵胂螅瑯訄?chǎng)景倘若用傳統(tǒng)三角面片建模的話(huà)必然會(huì)十分復(fù)雜,而且如此渲染效果恐怕也是很難達(dá)到的。

Ray Marching可以用來(lái)渲染參數(shù)化模型,即將整個(gè)模型看作三維空間中一個(gè)函數(shù)圖像,再利用Ray Marching將圖像畫(huà)出來(lái)。如此以來(lái)就可以用極少的參數(shù)描述整個(gè)模型,同時(shí)完全不放過(guò)任何一個(gè)細(xì)節(jié):上圖中地形是通過(guò)類(lèi)似Perlin Noise(wiki)的方法生成的,而樹(shù)木則是在一個(gè)球上添加若干Perlin Noise生成。

我們注意到,在光線(xiàn)追蹤算法中,只要“光線(xiàn)與物體求交”這一步的速度不受很大影響的話(huà),模型的復(fù)雜程度跟算法效率實(shí)際上是無(wú)關(guān)的,我們做的不過(guò)是一個(gè)像素一個(gè)像素填充顏色而已。比如我們可以渲染無(wú)限復(fù)雜的分形:

from http://blog.hvidtfeldts.net/index.php/2012/05/distance-estimated-3d-fractals-part-viii-epilogue/

而且正如上面所見(jiàn),利用Ray Marching渲染軟陰影、虛焦、漫反射、環(huán)境光遮蔽等比傳統(tǒng)光線(xiàn)跟蹤更為方便快捷,在實(shí)時(shí)渲染的情況下可以獲得不錯(cuò)的圖像質(zhì)量。

目前,Ray Marching的應(yīng)用主要局限于volume field的渲染上:例如在醫(yī)學(xué)圖像處理中,將CT機(jī)上獲得的若干斷層照片渲染成三維模型;或是渲染云彩、火焰、煙霧等效果。Ray Marching的機(jī)理使得我們可以對(duì)volume field做采樣,以離散值的疊加逼近一團(tuán)連續(xù)體積場(chǎng)的外觀。本系列在之后也會(huì)討論這樣的體積場(chǎng)的渲染(如果不會(huì)坑的話(huà))。

順便一提,把神經(jīng)網(wǎng)絡(luò)和Ray Marching弄在一起也不是沒(méi)有可能的。(純屬瞎猜)(畢設(shè)已經(jīng)被GAN弄得很惡心了,不要再給我提神經(jīng)網(wǎng)絡(luò)這家伙)

Distance Field

為實(shí)現(xiàn)Ray Marching,我們需要引入距離場(chǎng)(distance field)這一重要概念。距離場(chǎng)是三維空間中一個(gè)標(biāo)量場(chǎng),任意一點(diǎn)的數(shù)值代表離該點(diǎn)最近的模型表面的點(diǎn)的距離下界。通過(guò)距離場(chǎng)可以很方便地估計(jì)光線(xiàn)應(yīng)當(dāng)前進(jìn)的幅度:只需讓光線(xiàn)前進(jìn)當(dāng)前點(diǎn)距離場(chǎng)大小的數(shù)值即可,即所謂Sphere Tracing。

Sphere Tracing

簡(jiǎn)單幾何圖形的距離場(chǎng)是很容易計(jì)算的:例如球的距離場(chǎng)就是到球心距離減去球半徑,立方體距離場(chǎng)就是到立方體六個(gè)面距離最小值。已知一個(gè)距離場(chǎng),通過(guò)一些簡(jiǎn)單的數(shù)學(xué)手段即可以對(duì)其進(jìn)行編輯,例如求交、求并、扭曲等等,而相同的編輯操作在三角面片上完成起來(lái)是相當(dāng)復(fù)雜的。對(duì)于一些復(fù)雜的模型,例如分形,它們的距離場(chǎng)也是可求的;而即使是類(lèi)似Perlin Noise這樣沒(méi)有數(shù)學(xué)表達(dá)式的模型,也可以通過(guò)某些手段對(duì)距離場(chǎng)做近似。接下來(lái),在實(shí)踐中我們將更加詳細(xì)討論距離場(chǎng)。

最簡(jiǎn)單的Ray Marching

為方便起見(jiàn),我使用shadertoy這一平臺(tái)編寫(xiě)Ray Marching的shader,雖然這個(gè)網(wǎng)站不是很好用,不過(guò)還是免去了學(xué)習(xí)webGL或者在桌面平臺(tái)上配環(huán)境的麻煩。該網(wǎng)站上還有不少這方面優(yōu)秀的作品,大家可以自行瀏覽學(xué)習(xí)。

下面我希望完成一個(gè)最簡(jiǎn)單的ray marching程序??紤]到球的distance field最簡(jiǎn)單,那我們就渲染個(gè)球好了。粗糙暴力的代碼如下:

float dist_estimator(in vec3 pos)
{
    return length(pos) - 1.0;
}

float intersects(in vec3 src, in vec3 drt)
{
    float t;
    for (int i = 0; i < 255; ++i)
    {
        float delta = dist_estimator(src + t * drt);
        t += delta;
        if (delta < 1e-5)
            return t;
    }
    return 1e+5;
}

vec4 trace(in vec3 src, in vec3 dst)
{
    vec3 drt = normalize(dst - src);
    vec3 light = vec3(1.5, 0.3, 0.3);
    float t = intersects(src, drt);
    if (t <= 1e+4)
    {
        vec3 surface_pos = src+ drt * t;
        float strenth = dot(-drt, normalize(light - surface_pos));
        return vec4(1, 1, 1, 1) * strenth;
    }
    else
        return vec4(0, 0, 0, 1);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (vec2(fragCoord.x - iResolution.x / 2.0, fragCoord.y - iResolution.y / 2.0) / max(iResolution.x, iResolution.y)) * 2.0;
    vec3 src = vec3(5.0, 0.0, 0.0);
    vec3 dst = vec3(4.0, uv * 0.4);
    fragColor = trace(src, dst);
}

距離場(chǎng)計(jì)算放在·dist_estimator·函數(shù)中;·intersects·函數(shù)為光線(xiàn)與模型求交;而·trace·函數(shù)則是普通的ray casting過(guò)程。渲染出來(lái)大概這個(gè)樣子:

渲染個(gè)球

看起來(lái)非常普通。不過(guò)我們可以修改一下距離場(chǎng)計(jì)算:

float dist_estimator(in vec3 pos)
{
    return sqrt(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z - 2.0 * sqrt(pos.x * pos.x + pos.y * pos.y) + 1.0) - 0.3;
}

大概就是一個(gè)圓環(huán)的公式。思路很簡(jiǎn)單,首先我們弄出xy平面上的一個(gè)圓的公式,再把這個(gè)圓的邊框給加上厚度。那么我們可以得到:

圓環(huán)

然而還是顯得很naive。有沒(méi)有更好玩一點(diǎn)的東西呢?當(dāng)然。

vec3 cylinder2cartesian(float r, float phi, float z)
{
    return vec3(r * cos(phi), r * sin(phi), z);
}

float dist_estimator(in vec3 pos)
{
    float r = length(pos);
    float phi = atan(pos.y, pos.x);
    float r1 = sin(1.5 * phi) + 1.5;
    float z1 = cos(1.5 * phi);
    float r2 = sin(1.5 * phi + PI) + 1.5;
    float z2 = cos(1.5 * phi + PI);
    vec3 p1 = cylinder2cartesian(r1, phi, z1);
    vec3 p2 = cylinder2cartesian(r2, phi, z2);
    return (min(distance(pos, p1), distance(pos, p2)) - 0.2) / 4.0;
}
正面觀
側(cè)面觀

第一篇文章就先以這個(gè)(大概)好玩的東西告一段落吧。全代碼奉上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容