美文网首页
《地平线零》体积云UE复刻

《地平线零》体积云UE复刻

作者: 离原春草 | 来源:发表于2020-12-21 22:17 被阅读0次

在看过众多的体积云实现方案后,也想尝试自己建一个小demo跟大师们进行隔空对话,这里首先尝试在UE中对《地平线零》的体积云方案进行复刻,主要参考如下Siggraph15的PPT与GPU Pro 7的分享

1. 准备工作

根据两篇参考文章,我们需要准备体积云实现的相关贴图资源:
1. 噪声贴图
1.1 低频Worley噪声贴图,分辨率为128x128x128
R通道存储的是使用Worley噪声调制过的PerlinFBM
GBA通道存储的则是低频(012阶)的Worley噪声,根据GPU Pro7中的说法,这三个通道存储的是Worley噪声,但是从给出的贴图来看,实际上存储的是多个不同Cell Num的Worley噪声的叠加,且根据后面的使用方法来看,这三个通道后面还会按照一定的方式进行混合,为了避免运行时混合,这里直接在离线计算的时候先完成混合
1.2 高频Worley噪声贴图,分辨率为32x32x32
RGB三通道存储的是高频(345阶)的Worley噪声,其实现算法跟低频噪声贴图中的GBA通道的结果完全一致
出于渲染效率与带宽消耗考虑,这里将PW Noise,低频WorleyFBM与高频WorleyFBM都放到一张贴图中(分别占据RGB三通道),此贴图分辨率为128x128x128。

Perlin噪声的实现算法代码给出如下:

    float sum = 0.0;
    float frequency = pow(2, firstOctave);
    const float persistence = 0.6;
    float amplitude = pow(persistence, firstOctave);
    for(int i=firstOctave; i < firstOctave + accumOctaves;i++)
    {
        uvw += iTime * TimeScale;
        sum += perlin(uvw * frequency, SliceNum, Coverage, NoiseTexRes, WhiteNoise, WhiteNoiseSampler) * amplitude;
        frequency *= 2.0;
        amplitude *= persistence;
    }
    return sum;

相关函数实现给出如下:

float perlin(float3 uvw, float SliceNum, float Coverage, int NoiseTexRes, Texture2D WhiteNoise, SamplerState WhiteNoiseSampler)
{
    float3 pi = floor(uvw); 
    float3 pf = uvw - pi;
    
    float u = fade(pf.x);
    float v = fade(pf.y);
    float w = fade(pf.z);

    // SliceNum = 1.0f;
    float3 posOffsetArr[8] = 
    {
        float3(0, 0, 0),
        float3(1.0, 0, 0),
        float3(0, 1.0, 0),
        float3(1.0, 1.0, 0),
        float3(0, 0, 1.0 / SliceNum),
        float3(1.0, 0, 1.0 / SliceNum),
        float3(0, 1.0, 1.0 / SliceNum),
        float3(1.0, 1.0, 1.0 / SliceNum),
    };

    float3 posNoOffsetArr[8] = 
    {
        float3(0.0, 0.0, 0.0),
        float3(1.0, 0.0, 0.0),
        float3(0.0, 1.0, 0.0),
        float3(1.0, 1.0, 0.0),
        float3(0.0, 0.0, 1.0),
        float3(1.0, 0.0, 1.0),
        float3(0.0, 1.0, 1.0),
        float3(1.0, 1.0, 1.0),
    };
    
    return  COSInterpolation( 
                COSInterpolation( 
                    COSInterpolation(
                        grad3D(rand(pi + posNoOffsetArr[0], NoiseTexRes, WhiteNoise, WhiteNoiseSampler), pf - posOffsetArr[0]),
                        grad3D(rand(pi + posNoOffsetArr[1], NoiseTexRes, WhiteNoise, WhiteNoiseSampler), pf - posOffsetArr[1]), 
                        u ),
                    COSInterpolation(
                        grad3D(rand(pi + posNoOffsetArr[2], NoiseTexRes, WhiteNoise, WhiteNoiseSampler), pf - posOffsetArr[2]), 
                        grad3D(rand(pi + posNoOffsetArr[3], NoiseTexRes, WhiteNoise, WhiteNoiseSampler), pf - posOffsetArr[3]), 
                        u ), 
                    v ),
                 COSInterpolation( 
                    COSInterpolation( 
                        grad3D(rand(pi + posNoOffsetArr[4], NoiseTexRes, WhiteNoise, WhiteNoiseSampler), pf - posOffsetArr[4]), 
                        grad3D(rand(pi + posNoOffsetArr[5], NoiseTexRes, WhiteNoise, WhiteNoiseSampler), pf - posOffsetArr[5]), 
                        u ),
                    COSInterpolation( 
                        grad3D(rand(pi + posNoOffsetArr[6], NoiseTexRes, WhiteNoise, WhiteNoiseSampler), pf - posOffsetArr[6]), 
                        grad3D(rand(pi + posNoOffsetArr[7], NoiseTexRes, WhiteNoise, WhiteNoiseSampler), pf - posOffsetArr[7]), 
                        u ), 
                    v ), 
                w ) + Coverage;
}



float fade(float t) 
{
    return t;
}

float COSInterpolation(float x,float y,float n)
{
    return lerp(x, y, n);
    // float r = n*3.1415926;
    // float f = (1.0-cos(r))*0.5;
    // return x*(1.0-f)+y*f;
}


float grad3D(float hash, float3 pos) 
{
    float angle = 6.283185 * hash + 4.0 * pos.z * hash;
    return dot(float2(cos(angle), sin(angle)), pos.xy);

    // int h = int(1e4*hash) & 15;
    // float u = h<8 ? pos.x : pos.y,
    //    v = h<4 ? pos.y : h==12||h==14 ? pos.x : pos.z;
    // return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
}

Perlin噪声(Revert)结果如下图所示:

Perlin噪声

Worley噪声实现算法的代码给出如下:

    if(IsFBM > 0.001)
    {
        //Worley FBM
        float fFinalWorley = 0;
        float WorleyNoiseWeight[3] = {0.625, 0.25, 0.125};
        for(int nChannel = 0; nChannel < 3; nChannel ++)
        {
            int nStartOctave = firstOctave + nChannel  * OctaveSpan;
            float frequency = pow(2, nStartOctave);
            uvw += iTime * 0.04;
            float WorleyNoise = clamp(worley(uvw * frequency, CellNum, NoiseTexRes, WhiteNoise, WhiteNoiseSampler) * saturateness, 0.0, 1.0);
            if(MulCellNumAdd > 0.1f)
            {
                WorleyNoise += clamp(worley(uvw * frequency, CellNum * 2, NoiseTexRes, WhiteNoise, WhiteNoiseSampler) * saturateness, 0.0, 1.0);
                WorleyNoise /= 2.0;
            }
            fFinalWorley += WorleyNoise * WorleyNoiseWeight[nChannel];
        }

        return fFinalWorley;
    }
    else
    {
        float frequency = pow(2, firstOctave);
        uvw += iTime * 0.04;
        float fCurWorleyNoise = clamp(worley(uvw* frequency, CellNum, NoiseTexRes, WhiteNoise, WhiteNoiseSampler)  * saturateness, 0.0, 1.0);
        if(MulCellNumAdd > 0.1f)
        {
            fCurWorleyNoise += clamp(worley(uvw* frequency, CellNum * 2, NoiseTexRes, WhiteNoise, WhiteNoiseSampler)  * saturateness, 0.0, 1.0);
            fCurWorleyNoise /= 2.0f;
        }
        return fCurWorleyNoise;
    }

相应函数的代码给出如下:

float worley(float3 coord, int CellNum, int NoiseTexRes, Texture2D WhiteNoise, SamplerState WhiteNoiseSampler)
{
    float3 cell = floor(coord * CellNum);
    float dist = 10000.0;
    
    // Search in the surrounding 5x5x5 cell block
    for (int x = 0; x < 5; x++) 
    { 
        for (int y = 0; y < 5; y++) 
        {
            for(int z = 0; z < 5; z++)
            {
                float3 cell_point = get_cell_point(cell + float3(x-2, y-2, z-2), CellNum, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
                dist = min(dist, length(cell_point - coord));
            }
        }
    }
    return dist;
}

float3 get_cell_point(float3 cell, int CellNum, int NoiseTexRes, Texture2D WhiteNoise, SamplerState WhiteNoiseSampler) 
{
    float3 cell_base = cell / CellNum;
    float noise_x = rand(cell.xyz, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
    float noise_y = rand(cell.yzx, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
    float noise_z = rand(cell.zxy, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
    return cell_base + (0.5 + 1.5 * float3(noise_x, noise_y, noise_z)) / CellNum;
}

float rand(float3 pos, int NoiseTexRes, Texture2D WhiteNoise, SamplerState WhiteNoiseSampler)
{
    float2 uv = pos.xy + pos.z;
    return Texture2DSample(WhiteNoise, WhiteNoiseSampler, uv / NoiseTexRes).r;
}

输出贴图结果如下所示:


0阶 1阶 2阶

将三种贴图以{0.625, 0.25, 0.125}权重混合后的结果如下图所示:

FBM

GPU Pro 7中介绍,Perlin噪声与Worley噪声的结合是通过一个Remap完成的:

float Remap(float original_value , float original_min, float original_max , float new_min , float new_max)
{
    return new_min + ((original_value - original_min) / (original_max - original_min)) * (new_max - new_min);
}

但实际上这里测试发现,使用Remap得到的结果跟论文以及PPT中的示例结果不太一致:

Perlin Worley Remap

反而是将二者直接相加,得到的结果具有更高的相似性:

Perlin + Worley

1.3 Curl噪声贴图,分辨率为128x128,包含RGB三个通道,但是在GPU Pro 7的文档给出的代码片段只使用了RG两个通道的数据,用于对采样位置进行扰动,因此这里只给出2D Curl噪声的生成:

相关代码给出如下:

  //http://platforma-kooperativa.org/media/uploads/curl_noise_slides.pdf
  
float eps = 0.0001;
float x = uv.x;
float y = uv.y;
float fCoverage = 0.7f;
float TimeScale = 0.004f;
//Find rate of change in X direction
float n1 = potential(float3(x + eps, y, 0), firstOctave, accumOctaves, iTime, TimeScale, 1, fCoverage, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
float n2 = potential(float3(x - eps, y, 0), firstOctave, accumOctaves, iTime, TimeScale, 1, fCoverage, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
//Average to find approximate derivative
float a = (n1 - n2)/(2 * eps);

//Find rate of change in Y direction
float n3 = potential(float3(x, y + eps, 0), firstOctave, accumOctaves, iTime, TimeScale, 1, fCoverage, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
float n4 = potential(float3(x, y - eps, 0), firstOctave, accumOctaves, iTime, TimeScale, 1, fCoverage, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
//Average to find approximate derivative
float b = (n3 - n4)/(2 * eps);

//Curl
return float2(a, -b);

相关实现函数给出如下:

float potential(float3 uvw, int firstOctave, int accumOctaves, int iTime, float TimeScale, float SliceNum, float Coverage, int NoiseTexRes, Texture2D WhiteNoise, SamplerState WhiteNoiseSampler)
{
    float noise = perlinFBM(uvw, firstOctave, accumOctaves, iTime, TimeScale, SliceNum, Coverage, NoiseTexRes, WhiteNoise, WhiteNoiseSampler);
    return abs(1 - 2 * noise);
}

float perlinFBM(float3 uvw, int firstOctave, int accumOctaves, int iTime, float TimeScale, float SliceNum, float Coverage, int NoiseTexRes, Texture2D WhiteNoise, SamplerState WhiteNoiseSampler)
{
    float sum = 0.0;
    float frequency = pow(2, firstOctave);
    const float persistence = 0.6;
    float amplitude = pow(persistence, firstOctave);
    for(int i=firstOctave; i < firstOctave + accumOctaves;i++)
    {
        uvw += iTime * TimeScale;
        sum += perlin(uvw * frequency, SliceNum, Coverage, NoiseTexRes, WhiteNoise, WhiteNoiseSampler) * amplitude;
        frequency *= 2.0;
        amplitude *= persistence;
    }
    return sum;
}

结果贴图如下图所示:

R G

2. Height LUT贴图,分辨率为1x128,RGB三通道,每个通道对应一种不同类型的低空云:stratus(层云),startocumulus(层积云)以及cumulus(积云)

这张贴图直接使用PS进行制作,结果如下图所示:


R G B RGB

3. Weather Texture,虽然没有给出具体的分辨率,但是从使用逻辑推测,这张贴图分辨率不需要太高,这里直接使用128x128。另外,结合两篇文章,这张贴图包含三个通道,分别对应Coverage,Precipitation以及Cloud Type等三个参数,这三个参数都是运行时动态变化的,因此这个贴图应该也是运行时动态生成的,背后的具体实现算法没有给出,但是从结果上看跟Perlin噪声相似度非常高,因此这里尝试使用Perlin噪声来对其进行模拟。

这张贴图要具备如下几个特性:

  1. 能够随时间动态变化,在固定的参数(比如coverage以及octave等),添加上时间就能够实现这个特性,而时间的作用则是通过风力方向来表现
  2. 能够随天气的变化而变化,这里的天气变化无非就是雨天与晴天的变化,雨天可以通过云层密度达到一个阈值来进行模拟,而这个则可以通过一个随着时间缓慢变化的Perlin噪声贴图来进行模拟,其移动方向应该与风力方向保持一致
  3. 在没有云彩的地方,不应该出现下雨的情况,因此Precipitaion与CloudType参数应该具有一定的耦合性
  4. 要能实现覆盖率从极高到极低的自然过渡,

从上述描述来看,这三个通道的运动方向应该是一致的,后两个通道的运动速度可以与第一个通道做一些区分,以实现风力不动情况下的云层变动效果。

下面是按照这种做法输出的结果:

Coverage Precipitation Cloud Type RGB

到此为止,《地平线零》体积云方案所涉及到的输入贴图就算准备妥当了,下一步就开始进入渲染实现阶段。

相关文章

  • 《地平线零》体积云UE复刻

    在看过众多的体积云实现方案后,也想尝试自己建一个小demo跟大师们进行隔空对话,这里首先尝试在UE中对《地平线零》...

  • 2019-07-14

    0714记录 UE结构体:SCIndex, UseRBNum, SINR, RSRP ...

  • 积云

    一开始我的头顶有一块小乌云, 它随着我行走, 停在房间的上空, 后来它随我出了门, 云朵变成了镜子, 折射着身边人...

  • 积云

    厚厚的积云, 三三两两, 分散又聚集在鱼白的天空。 云随着风的意动, 幻出各式各样的物, 物在游动, 活在穹顶的画...

  • 积云

    睡眼惺忪的走出楼 眯眼看天 满是积云 沉沉地说着不开心 我问 为什么 它不说话 只是沉沉地 噢 是脏了吗 我立马忙...

  • 《知己》

    落日西风积云深 清酒半杯孤独饮 高山流水飘零叶 不见伯牙抚古琴

  • 淡积云

    你是那样的飘逸潇洒 你是那样的的纯净洁白 没有层积云的忧怨缠绵 没有积雨云的狂躁不安 没有毛卷云的高处不胜...

  • 观积云

    云低恋树山入天,氤氲葱郁有神仙 纱罩峰头峰峥蕴,登高直入凌霄间

  • 淡积云

    中午的天空很漂亮,很多人用手机记录了那一刻天空的美景,直到下午下班,抬头看看天,蓝天白云的景色依旧如画。 淡积云即...

  • UE4制作碎裂效果

    ue4可以制作物体自由掉落,碎裂的效果 我们首先放置一个静态物体,这里我们随便拖入一个立方体 选中该物体,在ue4...

网友评论

      本文标题:《地平线零》体积云UE复刻

      本文链接:https://www.haomeiwen.com/subject/xzprnktx.html