我的 3D 柏林噪声中 Z 轴上出现伪影的原因是什么?

What is the cause of artifacts on the Z axis in my 3D perlin noise?

我目前正在使用 C++ 实现 3D Perlin 噪声,当我将其可视化为通过 3D 体积的 2D 切片的视频时,会出现奇怪的块状伪像,如此代码定义的只有一个八度:

#include <png++/png.hpp>
#include <memory>
#include <string>

#include "Octave.h"

constexpr unsigned IMAGE_SIZE = 512;

std::string numberToString(unsigned n, unsigned digits);

int main() {
    std::mt19937_64 rnd(0);
    auto octave = std::make_unique<Octave>(&rnd, 32, 1); //make_unique because Octave objects are too big to fit on the stack

    for(unsigned z=0;z<625;++z){
        std::cout << z << "/625" << std::endl;
        png::image<png::rgb_pixel> image(IMAGE_SIZE, IMAGE_SIZE);
        for(unsigned x=0;x<IMAGE_SIZE;++x){
            for(unsigned y=0;y<IMAGE_SIZE;++y){
                unsigned brightness = (octave->noise(x, z*(64.0/300.0), y)*.5+.5)*255;
                image[y][x] = png::rgb_pixel(brightness, brightness, brightness);
            }
        }
        image.write("output/perlin-" + numberToString(z, 4) + ".png");
    }


    return 0;
}

std::string numberToString(unsigned n, unsigned digits){
    std::string string = std::to_string(n);
    while(string.length() < digits){
        string = "0" + string;
    }
    return string;
}

上述程序中的每个图像都是视频中的一个帧。显示此问题输出的视频(通过 ffmpeg 转换为视频)可在此处获得:https://youtu.be/f7NxYo8U7TQ。 Youtube 在该视频中添加了一些程序输出中不存在的压缩伪像。

从上面的视频中可以清楚地看出,当切片沿 Z 轴移动时,会出现方形伪影:。根据制作的平面切片(例如 XZ 或 ZY),伪影在性质上略有变化。似乎在一个渲染的八度音阶的频率翻转之后它们变得更糟。

这是什么原因?

Octave.h:

#pragma once

#include <array>
#include <random>

class Octave{
public:
    Octave(std::mt19937_64 *rnd, double frequency, double amplitude);
    double noise(double x, double y, double z);

private:
    static constexpr int PERMUTATION_TABLE_PART_SIZE = 1000000;
    static constexpr int PERMUTATION_TABLE_PART_COUNT = 3;
    static constexpr int PERMUTATION_TABLE_SIZE = PERMUTATION_TABLE_PART_SIZE*PERMUTATION_TABLE_PART_COUNT;
    std::array<int, PERMUTATION_TABLE_SIZE> m_permutationTable;
    double m_frequency, m_amplitude;

    double influence(int x, int y, int z, double distanceX, double distanceY, double distanceZ);

    inline static double square(double d) { return d*d; }
    inline static double vectorLength(double x, double y, double z);
    inline static double interpolate(double a, double b, double x);

};

Octave.cpp:

#include "Octave.h"

#include <utility>
#include <cmath>
#include <iostream>

Octave::Octave(std::mt19937_64 *rnd, double frequency, double amplitude) : m_frequency(frequency), m_amplitude(amplitude) {
    //fill in basic array
    for(int i=0;i<PERMUTATION_TABLE_PART_SIZE;++i){
        for(int j=0;j<PERMUTATION_TABLE_PART_COUNT;++j){
            m_permutationTable[i+PERMUTATION_TABLE_PART_SIZE*j] = i;
        }
    }

    //shuffle array
    for(int i=0;i<PERMUTATION_TABLE_SIZE;++i){
        int swapWith = ((*rnd)() % (PERMUTATION_TABLE_SIZE-i))+i;
        std::swap(m_permutationTable[i], m_permutationTable[swapWith]);
    }
}

double Octave::noise(double x, double y, double z) {
    x /= m_frequency;
    y /= m_frequency;
    z /= m_frequency;

    int intX = std::floor(x);
    int intY = std::floor(y);
    int intZ = std::floor(z);

    double floatX = x - intX;
    double floatY = y - intY;
    double floatZ = z - intZ;

    double influence1 = influence(intX, intY, intZ, floatX, floatY, floatZ);
    double influence2 = influence(intX+1, intY, intZ, floatX-1, floatY, floatZ);
    double influence3 = influence(intX+1, intY+1, intZ, floatX-1, floatY-1, floatZ);
    double influence4 = influence(intX, intY+1, intZ, floatX, floatY-1, floatZ);
    double influence5 = influence(intX, intY, intZ+1, floatX, floatY, floatZ-1);
    double influence6 = influence(intX+1, intY, intZ+1, floatX-1, floatY, floatZ);
    double influence7 = influence(intX+1, intY+1, intZ+1, floatX-1, floatY-1, floatZ-1);
    double influence8 = influence(intX, intY+1, intZ+1, floatX, floatY-1, floatZ-1);

    double frontUpperInterpolatedValue = interpolate(influence4, influence3, floatX);
    double backUpperInterpolatedValue = interpolate(influence8, influence7, floatX);
    double frontLowerInterpolatedValue = interpolate(influence1, influence2, floatX);
    double backLowerInterpolatedValue = interpolate(influence5, influence6, floatX);

    double upperInterpolatedValue = interpolate(frontUpperInterpolatedValue, backUpperInterpolatedValue, floatZ);
    double lowerInterpolatedValue = interpolate(frontLowerInterpolatedValue, backLowerInterpolatedValue, floatZ);

    return interpolate(lowerInterpolatedValue, upperInterpolatedValue, floatY)*m_amplitude;
}

double Octave::influence(int x, int y, int z, double distanceX, double distanceY, double distanceZ) {
    //create un-normalized gradient vector
    //the ordering of x, y, and z is arbitrary but different to produce different x y and z
    double gradientX = (m_permutationTable[m_permutationTable[m_permutationTable[x]+y]+z]/static_cast<double>(PERMUTATION_TABLE_PART_SIZE))*2-1;
    double gradientY = (m_permutationTable[m_permutationTable[m_permutationTable[y]+x]+z]/static_cast<double>(PERMUTATION_TABLE_PART_SIZE))*2-1;
    double gradientZ = (m_permutationTable[m_permutationTable[m_permutationTable[y]+x]+z]/static_cast<double>(PERMUTATION_TABLE_PART_SIZE))*2-1;

    //normalize gradient vector
    double gradientVectorInverseLength = 1/vectorLength(gradientX, gradientY, gradientZ);
    gradientX *= gradientVectorInverseLength;
    gradientY *= gradientVectorInverseLength;
    gradientZ *= gradientVectorInverseLength;

    //compute dot product
    double dot = gradientX*distanceX+gradientY*distanceY+gradientZ*distanceZ;

    return dot;
}

double Octave::vectorLength(double x, double y, double z) {
    return std::sqrt(square(x)+square(y)+square(z));
}

double Octave::interpolate(double a, double b, double x) {
    return (b-a)*(6*x*x*x*x*x-15*x*x*x*x+10*x*x*x)+a;
}

我确定了这个问题。我在计算 influence6 时忘记做 floatZ-1