C++生成类Perlin/Value噪声时插值异常,输出图像底部右侧损坏求助
问题描述
我正尝试生成类似Perlin噪声或Value噪声的效果。
我使用了stb官方仓库提供的stb_image_write库写入图像文件(我先存为HDR格式到本地,再通过GIMP转换为PNG格式便于上传展示)。
本代码需要C++20环境支持,因为我调用了std::lerp()函数。
当前我先生成了一个线性梯度作为测试用例,预期输出也应该是对应的线性梯度。
我知道完整生成目标噪声还有更多步骤,但目前就卡在了插值异常这个问题上。
问题代码
#include <cmath> template <size_t size> void noise(float seed[size][size], float output[size][size], size_t octave); int main() { // 生成测试梯度 float seedNoise[64][64] = {}; for ( size_t x = 0; x < 64; x++ ) { for ( size_t y = 0; y < 64; y++ ) { seedNoise[x][y] = ((((float)x) / 64.0f + ((float)y / 64.0f)) / 2.0f); } } float _map[64][64] = { 0 }; noise<64>(seedNoise, _map, 4); } template <size_t size> void noise(float seed[size][size], float output[size][size], size_t octave) { size_t step = size / octave; // size_t step = (size - 1) / octave; for ( size_t x = 0; x <= size - step; x += step ) { for ( size_t y = 0; y <= size - step; y += step ) { // 从种子中提取块的四个角点值 float a = seed[x][y]; float b = seed[x + (step - 1)][y]; float c = seed[x][y + (step - 1)]; float d = seed[x + (step - 1)][y + (step - 1)]; for ( size_t u = 0; u < step; u++ ) { float uStep = ((float)u) / ((float)step); for ( size_t v = 0; v < step; v++ ) { float vStep = ((float)v) / ((float)step); float x1 = std::lerp(a, b, uStep); // 顶边插值 float x2 = std::lerp(c, d, uStep); // 底边插值 float y1 = std::lerp(a, c, vStep); // 左边插值 float y2 = std::lerp(b, d, vStep); // 右边插值 float x3 = std::lerp(x1, x2, vStep); // 上下插值结果再插值 float y3 = std::lerp(y1, y2, uStep); // 左右插值结果再插值 float odat = (x3 + y3) / 2; // 两次插值结果取平均 output[x + u][y + v] = odat; } } } } }
效果说明
- 源梯度图:预期输出结果应和该图效果相近
- 实际输出图:生成图像的右侧和底部区域完全异常
- 调整后新输出图:修复后的正常输出效果
问题原因及修复方案
1. 插值因子计算错误
当前uStep = u / step、vStep = v / step的计算逻辑,会导致块的最右、最下点插值结果仅能达到角点值的(step-1)/step,和预期的角点值不符,边缘区域就会出现明显断层。可将插值因子修改为uStep = (float)u / (step - 1.f),让u=step-1时插值因子刚好为1,插值结果等于角点值。
2. 双线性插值逻辑错误
双线性插值只需先对水平两条边做x方向插值得到x1、x2,再对x1、x2做y方向插值即可得到正确结果。额外计算垂直两条边的插值再取平均的操作属于冗余计算,会导致最终结果偏离正确值,直接删除这部分逻辑即可。
3. 采样步长逻辑问题
如果octave参数代表采样点的数量(如octave=4对应4*4个采样点),那么步长应该修改为step = (size - 1) / (octave - 1),确保最后一个采样点刚好落在图像的右下角边界,避免边缘区域未正确覆盖。
修复后核心代码片段
template <size_t size> void noise(float seed[size][size], float output[size][size], size_t octave) { size_t step = (size - 1) / (octave - 1); for ( size_t x = 0; x < size; x += step ) { for ( size_t y = 0; y < size; y += step ) { float a = seed[x][y]; float b = seed[x + step][y]; float c = seed[x][y + step]; float d = seed[x + step][y + step]; for ( size_t u = 0; u < step; u++ ) { float uStep = (float)u / step; for ( size_t v = 0; v < step; v++ ) { float vStep = (float)v / step; float x1 = std::lerp(a, b, uStep); float x2 = std::lerp(c, d, uStep); output[x + u][y + v] = std::lerp(x1, x2, vStep); } } } } }
内容的提问来源于stack exchange,提问作者BrokenCode
相关产品推荐
相关产品推荐

