博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
DirectX11 法线向量
阅读量:4086 次
发布时间:2019-05-25

本文共 3574 字,大约阅读时间需要 11 分钟。

法线向量

1. 平面向量与表面向量的区别
平面法线(face normal)是描述多边形所朝方向的单位向量(即,它与多边形上的所有点相互垂直),如下图a所示。表面法线(surface normal)是与物体表面上的点的正切平面(tangent plane)相互垂直的单位向量,如下图b所示。表面法线确定了表面上的点“面对”的方向。

这里写图片描述

当进行光照计算时,我们必须为三角形网格表面上的每个点求解表面法线,以确定光线与网格表面在该点位置上的入射角度。为了获得表面法线,我们必须为每个顶点指定表面法线(这些法线称为顶点法线)。然后在光栅化阶段,这些顶点法线会在三角形表面上进行线性插值,使三角形表面上的每个点都获得一个表面法线。

这里写图片描述
(p0和p1是线段的两个顶点,n0和n1是对应的顶点法线。点p是通过线性插值(加权平均值)得到的线段上的一点,n是点p的法线向量,它介于两个顶点法线之间。也就是,当存在一个位置使p =p0 + t (p1 –p0 ) 时,n =n0 + t(n1 –n0)。为了简单起见,我们只解释了线段的法线插值,但是这一概念可以被直接扩展为3D三角形的法线插值。)

注意:对每个像素的法线进行插值,并进行光照计算称为逐像素光照或phong光照。还有一种负担较轻,但不够精确的方法是对每个顶点进行光照运算,称为逐顶点光照,计算结果是从顶点着色器中输出的,在像素着色器中进行插值。将计算从像素着色器转移到顶点着色器是一种常见的性能优化措施,而且在很多情况下的视觉表现与逐像素光照差别不大。

2. 如何计算平面法线向量和表面法向向量?

为了求解一个三角形Δp0p1p2的平面法线,我们必须先计算该三角形边上的两个向量:

u = p1− p0

v = p2− p0

然后求得平面法线为:

这里写图片描述

下面的函数可以根据三角形的3个顶点来计算三角形正面的平面法线:

void ComputeNormal(const XMVector3& p0,    const XMVector3& p1,    const XMVector3& p2,    XMVector3& out){    XMVector3 u = p1 - p0;    XMVector3 v = p2 - p0;    XMVector3Cross(&out, &u, &v);    XMVector3Normalize (&out, &out);}

对于一个微分曲面来说,我们可以使用微积分计算曲面上的点的法线。但遗憾的是,三角形网格不是微分曲面。我们通常使用一种称为顶点法线平均值(vertex normal averaging)的技术求解三角形网格上的顶点法线。对于网格上的任意顶点v来说,v的顶点法线n等于以v为共享顶点的每个多边形的平面法线的平均值。例如在下图中,网格上的四个多边形共享顶点v;所以,v的顶点法线为:

这里写图片描述

这里写图片描述

(中间的顶点由相邻的4个多边形共享,我们通过计算这4个多边形平面法线的平均值就可以估算出该顶点的法线。)

在上面的例子中,我们不需要除以4,因为我们想要的是一个普通平均值,我们可以对结果进行规范化。注意,我们还可以构造更巧妙的平均值计算公式;例如,以每个多边形的面积作为权值,计算加权平均值(这样,面积较大的多边形会占有较大的权重,而面积较小的多边形会占有较小的权重)。

下面的伪代码说明了在给出一个三角形网格的顶点列表和索引列表时,如何计算该平均值:

// 输入:// 1.一个顶点数组(mVertices),每个顶点都有一个位置分量(pos)和// 一个法线分量(normal).// 2.一个索引数组(mIndices)。// 处理网格中的每个三角形:for(DWORD i = 0; i < mNumTriangles; ++i){    // 第i个三角形的索引    UNIT i0 = mIndices[i*3+0];    UNIT i1 = mIndices[i*3+1];    UNIT i2 = mIndices[i*3+2];    // 第i个三角形的顶点    Vertex v0 = mVertices[i0];    Vertex v1 = mVertices[i1];    Vertex v2 = mVertices[i2];    // 计算面法线    Vector3 e0 = v1.pos - v0.pos;    Vector3 e1 = v2.pos - v0.pos;    Vector3 faceNormal Cross( &e0, &e1);    // 这个三角形共享了以下三个顶点,    // 所以要将面法线加入到这些顶点法线的平均值中。    mVertices[i0].normal += faceNormal;    mVertices[i1].normal += faceNormal;    mVertices[i2].normal += faceNormal;}// 对每个顶点v,我们已经将共享v的所有三角形的面法线相加了,// 所以现在只需归一化即可。for(UNIT i = 0; i < mNumVertices; ++i)    mVertices[i].normal =  Normalize(&mVertices[i].normal));

3. 对法线向量进行非等比缩放变换需要注意的问题

在下图(a)中,正切向量u = v1− v0垂直于法线向量n。当我们对这两个向量应用一个不成比例的缩放变换A时,我们可以从下图(b)中看到,变换之后的切线向量uA = v1A – v0A不再垂直于变换之后的法线向量nA。
这里写图片描述
(a)变换之前的表面法线。(b)当x轴上的单位长度增大两倍后,法线不再垂直于表面。(c)通过计算缩放变换的逆转置矩阵,我们可以得到正确的变换结果。

所以我们的问题是:当给出一个用于变换点和(非法线)向量的变换矩阵A时,如何求出一个专门用来变换法线向量的变换矩阵B,使变换之后的切线向量和法线向量依然保持垂直关系(即uA•uB = 0)。要解决一问题,让我们先从一些已知条件开始:我们知道法线向量n直于切线向量u。

u•v = 0 切线向量垂直于法线向量

unT = 0 用矩阵乘法来表示点积
u (AA-1) nT= 0 插入单位矩阵I = AA-1
(uA)( A-1nT ) = 0 矩阵乘法的结合性
(uA)((A-1nT)T)T = 0 转置特性(AT)T = A
(uA)(n(A-1)T)T= 0 转置特性(AB)T = BTAT
uA•n(A-1)T = 0 用点积来表示矩阵乘法
uA•nB = 0 变换之后的切线向量垂直于变换之后的法线向量

这样,使用B = (A-1)T(A的逆转置矩阵)来变换法线向量,就可以使它与变换之后的切线向量依然保持垂直关系。注意,当变换矩阵为正交矩阵(AT = A-1)时,B = (A-1)T = (AT)T = A;也就是,我们不必计算逆转置矩阵,直接用A来代替B即可。总之,当以一个非等比缩放矩阵对法线向量进行变换时,我们必须使用该矩阵的逆转置矩阵。

在MathHelper.h中有一个辅助方法用于计算逆转置矩阵:

static XMMATRIX InverseTranspose(CXMMATRIX M){    // 逆转置矩阵仅用于法线。所以将矩阵中的平移行    // 设置为0,这样就不会影响逆转置矩阵的计算——因为我们    // 不需要对平移进行逆转置运算。    XMMATRIX A = M;    A.r[3] = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);    XMVECTOR det = XMMatrixDeterminant(A);    return XMMatrixTranspose(XMMatrixInverse(&det, A));}

因为逆转置只用于变换矢量,而平移是作用在点上的,因此需要从矩阵中将第4行归零化排除平移因素。但是,3.2.1节告诉我们,为了防止矢量被平移操作影响,它的w应设置为0(使用齐次坐标)。因此,我们可能得出一个看起来像是对的结论:无须将矩阵中的平移行归零化。但问题是,如果我们将逆转置矩阵和另一个不包含非等比例缩放的矩阵相乘,例如视矩阵(A-1 )T·V,转置后位于(A-1)T第4列的平移项导致结果错误。所以,我们将平移项清零就是为了预防这个错误。正确的方法是使用((A·V)-1)T对法线进行变换。

下面是一个错误例子,因为第4行变换之前没有归零化,所以第4列经过逆转置后并不是[0,0,0,1]T:

这里写图片描述

你可能感兴趣的文章
(python版)《剑指Offer》JZ49:把字符串转换成整数
查看>>
(python版)《剑指Offer》JZ52:正则表达式匹配
查看>>
(python版)《剑指Offer》JZ53:表示数值的字符串
查看>>
(python版)《剑指Offer》JZ5:用两个栈实现队列
查看>>
Win10(64位)系统清除BIOS密码的方法
查看>>
(python版)《剑指Offer》JZ20:包含min函数的栈
查看>>
(python版)《剑指Offer》JZ21:栈的压入、弹出序列
查看>>
(python版)《剑指Offer》JZ27:斐波那契数列
查看>>
(python版)《剑指Offer》JZ28:青蛙跳台阶
查看>>
(python版)《剑指Offer》JZ10:矩形覆盖
查看>>
(python版)《剑指Offer》JZ65:矩阵中的路径
查看>>
(python版)《剑指Offer》JZ66:机器人的运动范围
查看>>
(python版)《剑指Offer》JZ11:二进制中1的个数
查看>>
Kaggle入门教程
查看>>
(python版)《剑指Offer》JZ12:数值的整数次方
查看>>
(python版)《剑指Offer》JZ19:顺时针打印矩阵
查看>>
(python版)《剑指Offer》JZ29:最小的 k 个数
查看>>
图像处理之特征提取(一)之HOG特征 特征数的计算
查看>>
python+django+xadmin+mysql学习笔记
查看>>
Springboot框架配置远程Tomcat服务器以及本地Tomcat服务器进行远程debug调试
查看>>