Normal Transformation(法线变换)

本文详细探讨了三维空间中的法线变换问题,包括不同类型的变换对法线的影响及正确处理方法。文中还介绍了如何使用逆的转置矩阵来确保变换后的法线仍然垂直于表面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://blog.youkuaiyun.com/bugrunner/article/details/7285356

法向量只有方向没有大小,平移变换不会改变法向量,旋转变换M^-1^T逆的转置还是本身,均匀缩放变换也不会改变法向量,所以一般游戏中应用的变换可以直接作用于法向量变换,如果存在不均等的缩放变换,那么对法向量进行MV变换会得到不正确的结果,这个时候就要对法向量进行M^-1^T逆变换,推导见如下,或者在变换后重新计算法向量。

计算光照前需要对法向量进行单位化。

1).如果一开始就是单位向量,后面只是进行平移和旋转,那么法向量不需要重新计算,也不需要重新规范化,固定管线会对顶点法向量进行变换。

2).glEnable(GL_RESCALE_NORMAL) 对于开始有规范化的法向量,其中进行了均匀缩放的,那么需用GL_RESCALE_NORMAL重新规范化法向量,会比 GL_NORMALIZE更快。 如果是单位化了的法线向量(例如3ds max中导出的法线贴图),那么模型均匀缩放后,可以用GL_RESCALE_NORMAL重新规范化法向量。

3).glEnable(GL_NORMALIZE) 对于开始没有规范化的法向量,或者网格进行了非均匀缩放,那么需要重新程序员手动计算法向量的值(不要轻易非均匀缩放),且要进行GL_NORMALIZE规范化法向量。

自己编写的Shader中,需要注意法向量的变换处理。


关于三维空间法线变换的问题,之前就有看到过,但是一直也木有注意,普通的三维空间中的法线变换还是直接使用模型的变换矩阵来进行。但是,近来又看到了一些这方面相关的东西,因而总结一下。

已经知道不能直接用模型变换矩阵来变换Normal,比如使用某含有非一致性缩放(在x,y,z方向上进行不程度的拉伸)的变换矩阵来变换一球体,则可能得到如下列图示的结果:

  

左图为原始球体及其表面上的法向分布(2D投影后);中间为直接使用变换矩阵操作后的法向分布,但注意其明显与表面不垂直;右图为正确变换后的法向分布。
为什么直接变换不正确呢?网上有多种说明版本,但是对比了一下感觉还是PBRT上的解释比较好一些。这里假设某一点处的法向量为n,切向量为t,由两者在曲面上的垂直关系可知:

如果对于模型进行空间变换的矩阵为M,变换后在该点得到的新的切向量为,那么可得(这里假设切向量并不是由变换矩阵计算得到的,而是直接通过对模型表面进行几何分析计算得到的);变换后在该点得到新的法向量为,若是得到的正确法向量,则其必定仍然与切向量垂直。假设正确变换得到的法向量是在变换矩阵S下进行的,那么有,由法、切的垂直关系得:

在上式的最后阶段中可以看出,如果要满足成立条件则有,直接变换即可得,所以也就有:

这里就得到了通常我们所说的采用逆的转置矩阵来代替原始变换矩阵来对法向量进行操作。
上述推导中使用的一个依据是切向量与法向量之间的垂直关系,其实这是建立在另外一个基础上,那就是:切向量的计算一般是直接使用顶点与UV来进行,这样的话它就是直接与顶点相关,因而只要直接使用变换矩阵得到的顶点正确那么在此基础上计算而来的切向量也就正确,但是法向量却不是直接通过顶点计算、而是通过变换得到的(当然,如果Normal 也在网格顶点变换之后直接计算,而不是对原始的法向量用变换矩阵作空间变换的话就不会存在这一问题了)

另外,在实际操作中对于变换矩阵(一般为4x4的)并不一定可逆(比如一个由3D到2D的投影矩阵),因而上述S就没法计算,这种情况下更安全的一种方法是只使用原始4x4变换矩阵的左上角3x3矩阵,即不考虑平移部分,其实这也理所当然,平移操作本就不影响法向量。这一部分在Realtime Rendering中也有稍详细的说明。

当然,使用逆的转置来进行Normal变换只是正确方法的一种,不过也有其它的方法,比如从一个变换矩阵中只抽取出Rotation的部分来施加到Normal上,这样就免去了Translation和Scale的影响。矩阵的R, T, S分解也有很多经典的方法,可以参考一个这个帖子:http://www.gamedev.net/topic/441695-transform-matrix-decomposition/(虽然这种方法的效率与复杂度需要另外讨论,但毕竟也是一个途径)

不过一般情况下,我们对于场景中三维模型的变换基本上都是进行三向一致的缩放操作,这样的话M就是一个正交的矩阵,如此一来就有S等于M,因而直接使用M对法向量作变换也不会有什么问题出现(或者有差别也被忽略了^_*)。不过任意的缩放变换是建模工具的一个基本需求,因而在做工具时需要重视这一问题。


//粗配准 void PointProcessing::coarseRegistration( const std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr>& clusters, // 多簇源点云 const pcl::PointCloud<pcl::PointXYZ>::Ptr& target, // 目标点云 std::vector<Eigen::Matrix4f>& transformation_matrices, // 输出变换矩阵集合 float sac_ia_max_distance, // SAC-IA最大对应距离 int feature_radius // FPFH特征半径 ) { transformation_matrices.clear(); // 遍历每个簇 for (const auto& cluster : clusters) { // --- 1. 源簇关键点提取 --- pcl::PointCloud<pcl::PointXYZ>::Ptr src_keypoints(new pcl::PointCloud<pcl::PointXYZ>); pcl::UniformSampling<pcl::PointXYZ> uniform_sampling; uniform_sampling.setInputCloud(cluster); uniform_sampling.setRadiusSearch(2.0); // 关键点采样半径(可参数化) uniform_sampling.filter(*src_keypoints); // --- 2. 目标关键点提取(复用同一采样器)--- pcl::PointCloud<pcl::PointXYZ>::Ptr tgt_keypoints(new pcl::PointCloud<pcl::PointXYZ>); uniform_sampling.setInputCloud(target); uniform_sampling.filter(*tgt_keypoints); // --- 3. 使用自定义函数计算法线 --- pcl::PointCloud<pcl::Normal>::Ptr src_normals(new pcl::PointCloud<pcl::Normal>); computernormals(src_keypoints, src_normals, 5.0); // 法线半径5.0 pcl::PointCloud<pcl::Normal>::Ptr tgt_normals(new pcl::PointCloud<pcl::Normal>); computernormals(tgt_keypoints, tgt_normals, 5.0); // --- 4. 使用自定义函数计算FPFH特征 --- pcl::PointCloud<pcl::FPFHSignature33>::Ptr src_features(new pcl::FPFHSignature33); computerFPFH(src_keypoints, src_normals, src_features, feature_radius); pcl::PointCloud<pcl::FPFHSignature33>::Ptr tgt_features(new pcl::FPFHSignature33); computerFPFH(tgt_keypoints, tgt_normals, tgt_features, feature_radius); // --- 5. SAC-IA配准 --- pcl::SampleConsensusInitialAlignment<pcl::PointXYZ, pcl::PointXYZ, pcl::FPFHSignature33> sac_ia; sac_ia.setInputSource(src_keypoints); sac_ia.setSourceFeatures(src_features); sac_ia.setInputTarget(tgt_keypoints); sac_ia.setTargetFeatures(tgt_features); sac_ia.setMaxCorrespondenceDistance(sac_ia_max_distance); pcl::PointCloud<pcl::PointXYZ>::Ptr aligned(new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr aligned(new pcl::PointCloud<pcl::PointXYZ>); sac_ia.align(*aligned); // 无返回值,直接执行 // 检查变换矩阵有效性 Eigen::Matrix4f matrix = sac_ia.getFinalTransformation(); if (!matrix.isZero() && !matrix.hasNaN()) { transformation_matrices.push_back(matrix); } else { transformation_matrices.push_back(Eigen::Matrix4f::Identity()); } } }其中clusters是一个包含多个聚类的容器,修改这个函数,循环遍历clusters,计算法线与特征,随后与target进行粗配准
最新发布
03-08
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值