离散颜色高度图颜色插值/平滑及法线图生成方案咨询
高度图平滑与法线图生成实现方案
一、高度图平滑方案
普通高斯模糊会均匀加权所有邻域像素,容易抹平原始高度图的区块特征,导致输出地形层级丢失。推荐使用保留边缘的双边滤波插值+坡度关联噪声的方案,既可以生成自然的坡度过渡,也能保留原本的高度区块特征。
实现步骤:
- 第一步:将输入高度图的像素值转换为归一化高度值,比如8位灰度图可以把0-255的像素值映射到实际地形的
[min_h, max_h]高度范围,避免插值过程中出现精度损失。 - 第二步:用双边滤波做邻域插值,每个像素的权重同时考虑空间距离和高度差:高度差越大的邻域像素权重越低,避免把原本属于不同层级的高度区块强行融合。核心权重公式如下:
w = exp(-(dx²+dy²)/(2*σ_spatial²)) * exp(-(h_center - h_neighbor)²/(2*σ_height²))
其中σ_spatial控制平滑的空间范围,数值越大平滑范围越广;σ_height控制高度差过滤阈值,数值越小越能保留不同高度的硬边界,可根据自身高度图的区块落差调整参数。 - 第三步:插值完成后叠加细节噪声,不要直接加全局随机噪声,仅给坡度区域叠加小振幅的分层噪声,平地区域不加,避免破坏整体地形轮廓,同时模拟自然地表的粗糙感。
示例代码(Python,依赖numpy、Pillow):
import numpy as np from PIL import Image def smooth_heightmap(input_path, output_path, sigma_spatial=2, sigma_height=15, noise_strength=3): # 读取高度图为灰度数组 img = Image.open(input_path).convert('L') h_arr = np.array(img, dtype=np.float32) h, w = h_arr.shape smoothed = np.zeros_like(h_arr) # 3x3邻域双边滤波插值 for y in range(h): for x in range(w): total_w = 0 total_h = 0 for dy in (-1, 0, 1): for dx in (-1, 0, 1): ny, nx = y + dy, x + dx if 0 <= ny < h and 0 <= nx < w: # 计算空间距离权重 spatial_w = np.exp(-(dx**2 + dy**2) / (2 * sigma_spatial ** 2)) # 计算高度差权重 height_w = np.exp(-(h_arr[y, x] - h_arr[ny, nx]) ** 2 / (2 * sigma_height ** 2)) w = spatial_w * height_w total_w += w total_h += h_arr[ny, nx] * w smoothed[y, x] = total_h / total_w # 叠加坡度关联的细节噪声 gy, gx = np.gradient(smoothed) slope = np.sqrt(gx**2 + gy**2) slope = (slope - slope.min()) / (slope.max() - slope.min() + 1e-6) # 坡度归一化 noise = np.random.normal(0, noise_strength, size=smoothed.shape) * slope final = np.clip(smoothed + noise, 0, 255).astype(np.uint8) Image.fromarray(final).save(output_path) return final
二、平滑高度图生成法线图
法线图存储每个像素点对应地形表面的法向量,将XYZ三个分量从[-1,1]映射到[0,255]的RGB像素值即可生成,实现逻辑非常轻量。
实现步骤:
- 第一步:计算平滑后高度图的X、Y方向梯度:
gx = h[x+1,y] - h[x-1,y]、gy = h[x,y+1] - h[x,y-1] - 第二步:构造法向量
normal = (-gx, -gy, 2.0),并将向量归一化为单位长度 - 第三步:将归一化后的三个分量分别映射到0-255的范围,R通道存X分量、G通道存Y分量、B通道存Z分量,输出为RGB图即可。
示例代码:
def generate_normal_map(smoothed_h_arr, output_path, flip_y=False): h, w = smoothed_h_arr.shape # 计算x/y方向梯度 gx, gy = np.gradient(smoothed_h_arr) # 适配不同引擎的y轴朝向规则 gy = -gy if flip_y else gy # 构造法向量 normal = np.stack((-gx, gy, np.ones_like(gx)*2), axis=-1) # 归一化到单位向量 norm = np.linalg.norm(normal, axis=-1, keepdims=True) normal = normal / (norm + 1e-6) # 分量从[-1,1]映射到[0,255] normal = ((normal + 1) * 127.5).astype(np.uint8) Image.fromarray(normal).save(output_path)
注意事项:
- 不同图形引擎的法线Y轴朝向规则不同,OpenGL风格的法线需要翻转Y分量,调用函数时传入
flip_y=True即可适配。 - 如果需要更平滑的法线效果,可以把梯度计算的邻域范围从相邻1像素扩展到3-5像素,减少法线的锯齿感。
内容的提问来源于stack exchange,提问作者c4b4d4
相关产品推荐
相关产品推荐

