由高度图生成normalmap

本文介绍了如何将Heightmap转换为Normalmap,并提供了详细的数学原理和技术实现步骤。通过使用Heightmap上的像素及其周围像素的高度差,可以构造出切向量并进一步计算出法线向量。
Nvidia和ATI都有相应的工具把Heightmap转成NormalMap,有了NormalMap,我们就可以用NormalMapping技术进行Per Pixel Lighting计算了。那么HeightMap是怎么转化成NormalMap的呢?      其实并不难,在《3D游戏与计算机图形学方法》中,提供了一种由高度图生成法向图的方法。其思想是根据高度图中的象素与其周围象素的高度差,在切空间构造S向量和T向量,由SXT得到法线向量。
H(i,j)表示在height map(i,j)象素点的高度值,则在切线空间ST方向的切向量可以表示成:
S(i,j) = (1,0,H(i+1,j) - H(i-1,j) )
T(i,j) = (0,1,H(i,j+1) - H(i,j-1) )
Normal(i,j) = S(i,j) X T(i,j)
H(i+1,j) – H(i-1,j)为沿S方向的高度差,也就是S方向的坡度,H(i,j+1) - H(i,j-1)为沿T方向的高度差,也就是T方向的坡度。当相邻象素高度差为0时,则算出的Normal(i,j) = (0,0,1),表示法线垂直于平面,当有高度差时,法线就会分别朝S方向或T方向偏移。
 
   shader来实现也很简单,VSPS代码如下,上边左图为HeightMap,右图为由下面shader生成的NormalMap,这个方法生成的NormalMap并不够好,在RenderMonkey中有一个叫NormalmapFilter的Sample,会生成更高质理的NormalMap,有兴趣的朋友可以参考。VS_OUTPUT main(float4 Pos: POSITION){   VS_OUTPUT Out;   // Clean up inaccuracies   Pos.xy = sign(Pos.xy);   Out.Pos = float4(Pos.xy, 0, 1);   // Image-space   Out.texCoord.x = 0.5 * (1 + Pos.x);   Out.texCoord.y = 0.5 * (1 - Pos.y);   return Out;} float4 main(float2 texCoord: TEXCOORD) : COLOR {   float2 off = 1.0 / HeightMapSize;   float Scale = 1;   // Sample teh neighbor   float s0 = tex2D(Heightmap, texCoord + float2(-off.x,0)).r;   float s1 = tex2D(Heightmap, texCoord + float2( off.x,0)).r;   float s2 = tex2D(Heightmap, texCoord + float2( 0,-off.y)).r;   float s3 = tex2D(Heightmap, texCoord + float2(0,off.y)).r;   float3 U = float3(1,0,s1 - s0);   float3 V = float3(0,1,s3 - s2);   float3 normal = normalize(Scale * cross(U,V));   // Pack [-1, 1] into [0, 1]   return float4(normal * 0.5 + 0.5,1);}
### 如何根据灰度图像生成法线图 法线图(Normal Map)是一种用于增强三维模型表面细节的技术,通过改变光照计算来模拟复杂的几何结构。为了从灰度图像生成法线图,可以遵循一种基于高度场的方法[^1]。 #### 高度换为法线向量 灰度图像中的每个像素值表示对应位置的高度信息。假设输入的灰度图像 \( I(x, y) \) 表示二维空间上的高度场,则可以通过偏导数计算得到法线方向: \[ N_x = -\frac{\partial I}{\partial x}, \quad N_y = -\frac{\partial I}{\partial y} \] 其中,\( N_x \) 和 \( N_y \) 是法线在水平和垂直方向上的分量。通常还需要加入一个正交于平面的方向分量 \( N_z \),其大小由以下公式决定: \[ N_z = \sqrt{1 - (N_x^2 + N_y^2)} \] 如果梯度过大导致 \( N_x^2 + N_y^2 > 1 \),则需要对法线进行归一化处理以确保单位长度。 #### 数字实现过程 以下是 Python 中的一个简单实现方式,利用 NumPy 进行数值微分并构建法线贴图: ```python import numpy as np from PIL import Image def generate_normal_map(gray_image_path, strength=1.0): # 加载灰度图像 gray_img = Image.open(gray_image_path).convert('L') height_data = np.array(gray_img, dtype=np.float32) / 255.0 # 计算X轴和Y轴的差分近似梯度 dx, dy = np.gradient(height_data * strength) # 构建法线向量 normals = np.dstack((-dx, -dy, np.ones_like(dx))) norm = np.linalg.norm(normals, axis=2, keepdims=True) normals /= norm # 换至RGB范围 [0, 255] normal_rgb = ((normals + 1) / 2 * 255).astype(np.uint8) return Image.fromarray(normal_rgb) # 使用函数生成法线图 normal_map = generate_normal_map("input_gray.png", strength=1.0) normal_map.save("output_normal.png") ``` 上述代码片段展示了如何加载一张灰度图片并通过数值方法生成对应的法线图。最终输出的是 RGB 图像形式的法线数据。 #### 参考链接扩展阅读 对于更深入的理解和技术背景,可访问技术艺术家社区的相关文档页面[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值