You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

地形生成纹理滤波难题:三种插值算法仍存棋盘格,求平滑方案

低分辨率地形纹理插值后棋盘格消除方案求助

我在地形生成流程中采用低分辨率地形纹理,先后尝试了双线性插值(Bilinear interpolation)、约束双三次插值(Constrained Bicubic Interpolation)、Catmull-Rom三种插值算法,但生成的地形始终存在明显棋盘格图案,希望找到能实现地形平滑效果的解决方法。


插值算法效果展示

  • 双线性插值:
    双线性插值效果
  • 约束双三次插值:
    约束双三次插值效果
  • Catmull-Rom插值:
    Catmull-Rom插值效果

算法来源说明

前两种插值算法取自技术文章,Catmull-Rom算法取自ShaderToy,相关参考信息已嵌入源代码注释中。


算法实现代码

双线性插值

auto sampleData = [this](FVector2D __uv) {
    auto W_function = [](auto X, auto Y) {
        return X * Y;
    };

float X = __uv.X * float(gX - 1);
float Y = __uv.Y * float(gY - 1);
if (X < 0) X = 0;
if (Y < 0) Y = 0;
if (X > gX - 1) X = gX - 1;
if (Y > gY - 1) Y = gY - 1;
float PixelColor = 0, PixelColor1 = 0, PixelColor2 = 0, PixelColor3 = 0;
PixelColor = FormatedTopographyImageData[int(X) * (gY)+int(Y)];

if (X <= gX - 2 && Y <= gY - 2)
    PixelColor1 = FormatedTopographyImageData[(int(X) + 1) * (gY)+int(Y) + 1];
if (Y <= gY - 2)
    PixelColor2 = FormatedTopographyImageData[int(X) * (gY)+int(Y) + 1];
if (X <= gX - 2)
    PixelColor3 = FormatedTopographyImageData[(int(X) + 1) * (gY)+int(Y)];

float temp;
float FinalColor1 =
    W_function((1.0f - modf(X, &temp)), (1.0f - modf(Y, &temp))) * PixelColor +
    W_function(modf(X, &temp), (1.0f - modf(Y, &temp))) * PixelColor3 + //+X
    W_function((1.0f - modf(X, &temp)), modf(Y, &temp)) * PixelColor2 + //+Y
    W_function(modf(X, &temp), modf(Y, &temp)) * PixelColor1; //+X +Y

return FinalColor1;
};

约束双三次插值

auto sampleDataNotLinear = [this](FVector2D __uv) {
    auto W_function = [](auto X, auto Y) {
        return X * X * Y * Y 
            *( 9 - 6 * X - 6 * Y + 4 * X * Y);
    };

    float X = __uv.X * float(gX - 1);
    float Y = __uv.Y * float(gY - 1);
    if (X < 0) X = 0;
    if (Y < 0) Y = 0;
    if (X > gX - 1) X = gX - 1;
    if (Y > gY - 1) Y = gY - 1;
    float PixelColor = 0, PixelColor1 = 0, PixelColor2 = 0, PixelColor3 = 0;
    PixelColor = FormatedTopographyImageData[int(X) * (gY)+int(Y)];

    if (X <= gX - 2 && Y <= gY - 2)
        PixelColor1 = FormatedTopographyImageData[(int(X) + 1) * (gY)+int(Y) + 1];
    if (Y <= gY - 2)
        PixelColor2 = FormatedTopographyImageData[int(X) * (gY)+int(Y) + 1];
    if (X <= gX - 2)
        PixelColor3 = FormatedTopographyImageData[(int(X) + 1) * (gY)+int(Y)];

    float temp;
    float FinalColor1 =
        W_function((1.0f - modf(X, &temp)), (1.0f - modf(Y, &temp))) * PixelColor +
        W_function(modf(X, &temp), (1.0f - modf(Y, &temp))) * PixelColor3 + //+X
        W_function((1.0f - modf(X, &temp)), modf(Y, &temp)) * PixelColor2 + //+Y
        W_function(modf(X, &temp), modf(Y, &temp)) * PixelColor1; //+X +Y

    return FinalColor1;
};

Catmull-Rom插值

auto fract = [](float n) {
    float temp;
    return modf(n, &temp);
};
// note: (not) entirely stolen from https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
//
// Samples a texture with Catmull-Rom filtering, using 9 texture fetches instead of 16.
// See http://vec3.ca/bicubic-filtering-in-fewer-taps/ for more details
auto SampleTextureCatmullRom = [this, &sampleData](FVector2D _uv)
{
    // We're going to sample a a 4x4 grid of texels surrounding the target UV coordinate. We'll do this by rounding
    // down the sample location to get the exact center of our "starting" texel. The starting texel will be at
    // location [1, 1] in the grid, where [0, 0] is the top left corner.
    FVector2D samplePos = FVector2D(_uv.X * float(gX), _uv.Y * float(gY));
    FVector2D texPos1 = FVector2D(floor(samplePos.X - 0.5f) + 0.5f, floor(samplePos.Y - 0.5f) + 0.5f);

    // Compute the fractional offset from our starting texel to our original sample location, which we'll
    // feed into the Catmull-Rom spline function to get our filter weights.
    FVector2D f = FVector2D(samplePos.X - texPos1.X, samplePos.Y - texPos1.Y);

    // Compute the Catmull-Rom weights using the fractional offset that we calculated earlier.
    // These equations are pre-expanded based on our knowledge of where the texels will be located,
    // which lets us avoid having to evaluate a piece-wise function.

    FVector2D w0 = f * (FVector2D(-0.5f) + f * (FVector2D(1.0f) - 0.5f * f));
    FVector2D w1 = FVector2D(1.0f) + f * f * (FVector2D(-2.5f) + 1.5f * f);
    FVector2D w2 = f * (FVector2D(0.5f) + f * (FVector2D(2.0f) - 1.5f * f));
    FVector2D w3 = f * f * (FVector2D(-0.5f) + 0.5f * f);

    // Work out weighting factors and sampling offsets that will let us use bilinear filtering to
    // simultaneously evaluate the middle 2 samples from the 4x4 grid.
    FVector2D w12 = w1 + w2;
    FVector2D offset12 = w2 / w12;

    // Compute the final UV coordinates we'll use for sampling the texture
    FVector2D texPos0 = texPos1 - FVector2D(1.0f);
    FVector2D texPos3 = texPos1 + FVector2D(2.0f);
    FVector2D texPos12 = texPos1 + offset12;

    texPos0.X /= float(gX);
    texPos0.Y /= float(gY);
    texPos3.X /= float(gX);
    texPos3.Y /= float(gY);
    texPos12.X /= float(gX);
    texPos12.Y /= float(gY);

    float result = 0.0f;
    result += sampleData(FVector2D(texPos0.X, texPos0.Y)) * w0.X * w0.Y;
    result += sampleData(FVector2D(texPos12.X, texPos0.Y)) * w12.X * w0.Y;
    result += sampleData(FVector2D(texPos3.X, texPos0.Y)) * w3.X * w0.Y;

    result += sampleData(FVector2D(texPos0.X, texPos12.Y)) * w0.X * w12.Y;
    result += sampleData(FVector2D(texPos12.X, texPos12.Y)) * w12.X * w12.Y;
    result += sampleData(FVector2D(texPos3.X, texPos12.Y)) * w3.X * w12.Y;

    result += sampleData(FVector2D(texPos0.X, texPos3.Y)) * w0.X * w3.Y;
    result += sampleData(FVector2D(texPos12.X, texPos3.Y)) * w12.X * w3.Y;
    result += sampleData(FVector2D(texPos3.X, texPos3.Y)) * w3.X * w3.Y;

    return FVector(result, result, result);
};

内容的提问来源于stack exchange,提问作者Kedriik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 10:02:01