使用Ray Matching實現雲彩渲染

  這裏先貼一個我實現的雲彩效果,使用Direct3D11,並在pixel shader中使用RayMatching方法實現的,感興趣的可以下載源代碼查看,繪製效果如下:
簡單雲層渲染

實現過程主要使用perlin noise來進行雲彩建模,然後根據密度和高度決定雲彩的顏色,最後使用ray matching算法進行顏色的累積。

雲彩繪製部分

由於代碼中有很多與雲彩繪製無關的部分,所以我這裏記錄以下雲彩繪製部分代碼的介紹:
1. 代碼實現主要在pixel shaderGet3DCloudColor函數裏,大致爲:

float3 Get3DCloudColor(float2 xy)
{
    return cloudColor;
}

xy爲屏幕座標,xy中的y要提前進行歸一化,範圍爲(-1,1);
該函數返回屏幕某一像素的顏色;
2. 首先設置相機矩陣

float3 eye = float3(0, 0, -10);
float3 at = float3(0, 6, 0);
matrix View = SetViewMatrix(eye, at);

我這裏將相機矩陣計算放在了SetViewMatrix函數裏,該函數使用eye、at以及默認沿Y軸向上的up向量來計算。
3. 沿像素投射光線

//set ray
Ray ray;
ray.dir = normalize(float3(xy, 1.732));     //y方向fov爲60度
ray.pos = float3(0, 0, 0);

ray.dir = mul(float4(ray.dir, 0.0), View).xyz;      //變換射線位置和方向
ray.pos = mul(float4(ray.pos, 1.0), View).xyz;

其中Ray爲包含pos、dir兩個float3類型的結構體。首先構造出從原點發射的射線,然後使用View矩陣將射線變換至相機位置。
4. 設置雲層高度,並測試射線是否與雲層相交

//cloud height range: (6, 10)
//採用相交測試,對未相交像素,返回背景色
float2 range = float2(6, 10);
float len = 0;
float3 bgcolor = GetSkyColor(xy, ray.dir, sunDir);
if (abs(ray.dir.y) <= DELTA)        //與雲層平行
{
    return bgcolor;
}

len = (range.x - ray.pos.y) / ray.dir.y;
if (len < 0)    //視線遠離雲層
{
    return bgcolor;
}

ray.pos += len *ray.dir;

雲層高度爲(6,10),若射線遠離雲層或與雲層平行,則返回背景色,即太陽與藍天的顏色。
5. 使用步進方法進行雲層顏色的累積

//在雲層內進行步進
float alpha = 0;
float3 sumCol = (float3)0;
float mid = (range.x + range.y) / 2;
float width = range.y - range.x;
float t = 0.1;
for (int i = 0; i < 40; i++)
{
    if (ray.pos.y > range.y || alpha >= 1)  //穿出雲層或顏色累積足夠
    {
        break;
    }
    else        //步進累計顏色
    {
        t += min(0.1, t*0.05);
        ray.pos += ray.dir*t;
        float density = FractalNoise3D(ray.pos, 0.2);
        float densityDif = FractalNoise3D(ray.pos + 0.5*sunDir, 0.2);
        float dist = abs(ray.pos.y - mid);
        density *= smoothstep(0, 1, width/2 - dist);        //根據高度調整密度
        densityDif *= smoothstep(0.2, 2, width / 2 - dist);

        float dif = clamp(0, 1, density - densityDif);      //本點與光線方向某點的密度差
        float3 localCol = GetCloudVoxelColor(density, dif) * density;

        sumCol += localCol * (1 - alpha);
        alpha += density * (1 - alpha);

    }
}
  • 由於是從雲層的低端開始步進,所以必須對雲層的低端密度進行處理,因爲我們構建的噪音模型是全場的,在低端採樣可能會有突變;我們採用以下代碼處理雲層邊緣density *= smoothstep(0, 1, width/2 - dist);
  • 另外我們還計算了採樣點沿光照方向偏移一段距離後的密度,根據偏移點與原採樣點的密度差,來判斷採樣點是否在靠近光照方向的一側。若靠近光照方向,雲層顏色會偏亮
  • 知道密度與密度差,我們就可以計算雲層採樣點的顏色。這裏使用GetCloudVoxelColor函數來計算,其定義爲:
float3 GetCloudVoxelColor(float density, float dist)
{
    float3 light = lerp(float3(1.0, 0.98, 0.94)*1.1, float3(0.75, 0.75, 0.75), dist*0.1);
    float3 cloud = lerp(float3(0.95, 0.95, 0.95), float3(0.3, 0.2, 0.2), density*density);
    return cloud*light;
}

該函數比較簡單,light使用密度差來確定,本身顏色使用密度來確定,然後返回兩者的積;
好吧,我知道這並不符合物理現象,以及光照的原理,但近似的效果還算可以。

  • 最後使用從後向前的混合方法進行雲層顏色的累積。
    6. 將雲層顏色與其它顏色進行融合
alpha = clamp(0, 1, alpha);
sumCol += bgcolor*(1 - alpha);

float3 fogColor = float3(0.94, 1.00, 0.94);
float factor = clamp(0, 1, (distance(eye, ray.pos) - 20.0)/ 80.0);
sumCol = lerp(sumCol, fogColor, factor);

return sumCol;

首先是將雲層累積顏色與背景顏色(也就是天空顏色)進行混合;
然後根據距離來添加霧效;
最後最終的像素顏色。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章