D3D12渲染技术之向量法线

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jxw167/article/details/82756621

面法线是描述多边形面向的方向的单位矢量(即,它与多边形上的所有点正交); 表面法线是与表面上的点的切平面正交的单位矢量; 观察表面法线确定表面上的点“朝向”的方向。上面说的两点分别见下图所示:

(a)面法线与面上的所有点正交。
(b)表面法线是与表面上的点的切平面正交的矢量。

对于光照计算,我们需要在三角形网格表面上求每个点的曲面法线,以便我们可以确定光线照射到网格曲面上的点的角度。 为了获得曲面法线,我们仅在顶点处指定曲面法线(所谓的顶点法线)。 然后,为了在三角形网格的表面上的每个点处获得表面法线近似,这些顶点法线将在光栅化期间在三角形上插值,见下图所示:

顶点法线n0和n1在线段顶点p0和p1处定义,通过在顶点法线之间进行线性插值(加权平均),找到线段内部的点p的法向量n; 也就是说,n = n0 + t(n1-n0)其中t是p = p0 + t(p1-p0)尽管我们为了简单起见在线段上进行了常规插值,但这个想法直观地概括为在3D三角形上插值。

注意事项:
对每个像素的法线和光照计算进行插值称为像素照明或phong照明, 一种不太精确的方法是每个顶点进行照明计算,然后,从顶点着色器输出每顶点光照计算的结果,并在三角形的像素上进行插值,将像素着色器移动到顶点着色器的计算是品质方面的常见性能优化,有时视觉差异非常微妙,使得这种优化非常有吸引力。

计算法线向量

要找到三角形Δp0,P1,p2,的面法线,我们首先计算位于三角形边缘的两个向量:
u = p1 – p0
v = p2 – p0

面法线计算:

下面是一个函数,用于计算三角形三个顶点的三角形正面的面法线。

MVECTOR ComputeNormal(FXMVECTOR p0,
FXMVECTOR p1,
FXMVECTOR p2)
{
XMVECTOR u = p1 - p0;
XMVECTOR v = p2 - p0;

return XMVector3Normalize(
XMVector3Cross(u,v));
}

中间顶点由相邻的四个多边形共享,因此我们通过平均四个多边形面法线来近似中间顶点法线。

对于可微分的表面,我们可以使用微积分来找到曲面上的法线,不幸的是,三角形网格不可区分, 通常应用于三角形网格的技术称为顶点平均法线。 网格中的顶点法线n或任意顶点v是通过平均共享顶点v的网格中每个多边形的面法线来找到的。例如,在上图中,网格中的四个多边形因此共享顶点v, v的顶点法线由下式给出:

在上面的例子中,我们不需要除以4,就像求平均值那样,将结果标准化,还要注意,可以构建更复杂的平均方案; 例如,可以使用加权平均值,其中权重由多边形的面积确定(例如,具有较大区域的多边形比具有较小区域的多边形具有更多权重)。

以下伪代码显示了如何在给定三角形网格的顶点和索引列表的情况下实现此平均:

// 1. An array of vertices (mVertices). Each vertex has a
// position component (pos) and a normal component (normal).
// 2. An array of indices (mIndices).

// For each triangle in the mesh:
for(UINT i = 0; i < mNumTriangles; ++i)
{
// indices of the ith triangle
UINT i0 = mIndices[i3+0];
UINT i1 = mIndices[i
3+1];
UINT i2 = mIndices[i*3+2];

// vertices of ith triangle
Vertex v0 = mVertices[i0];
Vertex v1 = mVertices[i1];
Vertex v2 = mVertices[i2];

// compute face normal
Vector3 e0 = v1.pos - v0.pos;
Vector3 e1 = v2.pos - v0.pos;
Vector3 faceNormal = Cross(e0, e1);

// This triangle shares the following three vertices,
// so add this face normal into the average of these
// vertex normals.
mVertices[i0].normal += faceNormal;
mVertices[i1].normal += faceNormal;
mVertices[i2].normal += faceNormal;
}

// For each vertex v, we have summed the face normals of all
// the triangles that share v, so now we just need to normalize.
for(UINT i = 0; i < mNumVertices; ++i)
mVertices[i].normal = Normalize(&mVertices[i].normal));
`

转换法线向量

见下图a所示,其中我们有与法向量n正交的切向量u = v1-v0。 如果我们应用非均匀缩放变换A,我们从图b中看到,变换的切向量uA = v1A-v0A不与变换的法向量nA保持正交。

所以我们的问题是:给定转换矩阵A转换点和向量(非正态),我们想找到一个转换矩阵B,它转换法向量,使得转换的切线向量与转换的法向量正交(即 uA·nB = 0)。 要做到这一点,让我们首先从知道的条件开始:我们知道法向量n与切向量u正交:

因此,B =(A-1)T(A的逆转置)在转换法向量时起作用,使得它们垂直于其变换切向量uA。

注意,如果矩阵是正交的(AT = A-1),则B =(A-1)T =(AT)T = A; 也就是说,我们不需要计算逆转置,因为A在这种情况下完成了工作。 总之,当通过非均匀或剪切变换转换法向量时,请使用逆转置。
我们在MathHelper.h中实现了一个辅助函数来计算逆转置:
static XMMATRIX InverseTranspose(CXMMATRIX M)
{
XMMATRIX A = M;
A.r[3] = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);

XMVECTOR det = XMMatrixDeterminant(A);
return XMMatrixTranspose(XMMatrixInverse(&det, A));
}

我们清除矩阵中的任何平移,因为我们使用逆转置来转换向量,并且转换仅适用于点。 我们知道为向量设置w = 0(使用齐次坐标)可以防止向量被转换修改, 因此,我们不应该将矩阵中的平移归零, 问题是如果我们想要连接逆转置和另一个不包含非均匀缩放的矩阵,比如视图矩阵(A-1)T
V,(A-1)T的第四列中的转置平移会导致错误。 因此,我们将其归零以避免此错误, 正确的方法是通过以下方式转换法线:((AV)-1)T, 下面是缩放和平移矩阵的示例,以及第四列不是[0,0,0,1] T的逆转置看起来是这样的:

注意,即使使用逆转置变换,法向量也可能失去单位长度;因此,它们可能需要在转换后重新规范化。

阅读更多

扫码向博主提问

海洋_

博客专家

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • 3D引擎架构
  • 服务器架构
  • GPU渲染
  • 客户端架构
  • 引擎优化
去开通我的Chat快问
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页