如何从 VTK IntersectWithLine 获取纹理坐标?
How to get texture coordinates from VTK IntersectWithLine?
我已经通过 vtkOBJReader
加载了纹理映射的 OBJ 并将其加载到 vtkModifiedBSPTree
:
auto readerOther(vtkSmartPointer<vtkOBJReader>::New());
auto rawOtherPath(modelPathOther.toLatin1());
readerOther->SetFileName(rawOtherPath.data());
readerOther->Update();
auto meshDataOther(readerOther->GetOutput());
auto bspTreeOther(vtkSmartPointer<vtkModifiedBSPTree>::New());
bspTreeOther->SetDataSet(meshDataOther);
bspTreeOther->BuildLocator();
然后我计算我的线段开始和结束并将其输入
if (bspTreeOther->IntersectWithLine(p1, p2, tolerance, distanceAlongLine, intersectionCoords, pcoords, subId, cellId, cell))
当然还有所有相关的预定义变量。
我需要的是纹理交点处的UV坐标。
我对 VTK 还很陌生,所以我还没有理解它是如何组合在一起的逻辑;当我挖掘源代码时,抽象层仍然让我迷失。
我在 SO 和 VTK 用户档案中寻找这个答案,发现那些对 VTK 有深刻理解的人给那些自己几乎在那里的人提供了模糊的提示,因此到目前为止对我帮助不大。
(2018 年 11 月 9 日追加)
澄清一下,我正在使用由单个 3D 扫描仪拍摄创建的非退化三角网格,因此我的代码永远不会看到四边形和其他更高的多边形。一个通用的解决方案应该考虑到这些事情,但这可以通过首先通过良好应用 handwavium 对网格进行三角测量来实现。
[重写 2019/5/7 以反映更新的理解。]
在发现参数坐标是一个函数的输入,在三角形单元的情况下,可以从该函数获得重心坐标,然后了解什么是重心坐标,我能够计算出以下内容。
const auto readerOther(vtkSmartPointer<vtkOBJReader>::New());
const auto rawOtherPath(modelPathOther.toLatin1());
readerOther->SetFileName(rawOtherPath.data());
readerOther->Update();
const auto meshDataOther(readerOther->GetOutput());
const auto bspTreeOther(vtkSmartPointer<vtkModifiedBSPTree>::New());
bspTreeOther->SetDataSet(meshDataOther);
bspTreeOther->BuildLocator();
double point1[3]{0.0, 0.0, 0.0}; // start of line segment used to intersect the model.
double point2[3]{0.0, 0.0, 10.0}; // end of line segment
double distanceAlongLine;
double intersectionCoords[3]; // The coordinate of the intersection.
double parametricCoords[3]; // Parametric Coordinates of the intersection - see https://lorensen.github.io/VTKExamples/site/VTKBook/08Chapter8/#82-interpolation-functions
int subId; // ?
vtkIdType cellId;
double intersectedTextureCoords[2];
if (bspTreeOther->IntersectWithLine(point1, point2, TOLERANCE, distanceAlongLine, intersectionCoords, parametricCoords, subId, cellId))
{
const auto textureCoordsOther(meshDataOther->GetPointData()->GetTCoords());
const auto pointIds{meshDataOther->GetCell(cellId)->GetPointIds()};
const auto vertexIndex0{pointIds->GetId(0)};
const auto vertexIndex1{pointIds->GetId(1)};
const auto vertexIndex2{pointIds->GetId(2)};
double texCoord0[2];
double texCoord1[2];
double texCoord2[2];
textureCoordsOther->GetTuple(vertexIndex0, texCoord0);
textureCoordsOther->GetTuple(vertexIndex1, texCoord1);
textureCoordsOther->GetTuple(vertexIndex2, texCoord2);
const auto parametricR{parametricCoords[0]};
const auto parametricS{parametricCoords[1]};
const auto barycentricW0{1 - parametricR - parametricS};
const auto barycentricW1{parametricR};
const auto barycentricW2{parametricS};
intersectedTextureCoords[0] =
barycentricW0 * texCoord0[0] +
barycentricW1 * texCoord1[0] +
barycentricW2 * texCoord2[0];
intersectedTextureCoords[1] =
barycentricW0 * texCoord0[1] +
barycentricW1 * texCoord1[1] +
barycentricW2 * texCoord2[1];
}
请注意,此代码是对我正在使用的实际代码的解释;我正在使用 Qt 及其 QVector2D 和 QVector3D 类 以及一些解释器粘合函数来往返于双精度数组。
有关各种细胞类型的参数坐标系的详细信息,请参见https://lorensen.github.io/VTKExamples/site/VTKBook/08Chapter8。
代码
注意如果一个顶点属于多个多边形并且具有不同的纹理坐标,VTK 将创建该顶点的副本。
我不使用 vtkCleanPolyData,因为 VTK 会合并这样的 "duplicates",据我所知,我们将丢失所需的信息。
我用vtkCellLocator instead of vtkModifiedBSPTree,
因为在我的情况下它更快。
主文件main.cpp
。
您可以在 start
和 end
数组中找到幻数 — 这些是您的 p1
和 p2
。
我设置这些值只是为了举例
#include <vtkSmartPointer.h>
#include <vtkPointData.h>
#include <vtkCellLocator.h>
#include <vtkGenericCell.h>
#include <vtkOBJReader.h>
#include <vtkTriangleFilter.h>
#include <vtkMath.h>
#include <iostream>
int main(int argc, char * argv[])
{
if (argc < 2)
{
std::cerr << "Usage: " << argv[0] << " OBJ_file_name" << std::endl;
return EXIT_FAILURE;
}
auto reader{vtkSmartPointer<vtkOBJReader>::New()};
reader->SetFileName(argv[1]);
reader->Update();
// Triangulate the mesh if needed
auto triangleFilter{vtkSmartPointer<vtkTriangleFilter>::New()};
triangleFilter->SetInputConnection(reader->GetOutputPort());
triangleFilter->Update();
auto mesh{triangleFilter->GetOutput()};
// Use `auto mesh(reader->GetOutput());` instead if no triangulation needed
// Build a locator to find intersections
auto locator{vtkSmartPointer<vtkCellLocator>::New()};
locator->SetDataSet(mesh);
locator->BuildLocator();
// Initialize variables needed for intersection calculation
double start[3]{-1, 0, 0.5};
double end[3]{ 1, 0, 0.5};
double tolerance{1E-6};
double relativeDistanceAlongLine;
double intersectionCoordinates[3];
double parametricCoordinates[3];
int subId;
vtkIdType cellId;
auto cell{vtkSmartPointer<vtkGenericCell>::New()};
// Find intersection
int intersected = locator->IntersectWithLine(
start,
end,
tolerance,
relativeDistanceAlongLine,
intersectionCoordinates,
parametricCoordinates,
subId,
cellId,
cell.Get()
);
// Get points of intersection cell
auto pointsIds{vtkSmartPointer<vtkIdList>::New()};
mesh->GetCellPoints(cellId, pointsIds);
// Store coordinates and texture coordinates of vertices of the cell
double meshTrianglePoints[3][3];
double textureTrianglePoints[3][2];
auto textureCoordinates{mesh->GetPointData()->GetTCoords()};
for (unsigned pointNumber = 0; pointNumber < cell->GetNumberOfPoints(); ++pointNumber)
{
mesh->GetPoint(pointsIds->GetId(pointNumber), meshTrianglePoints[pointNumber]);
textureCoordinates->GetTuple(pointsIds->GetId(pointNumber), textureTrianglePoints[pointNumber]);
}
// Normalize the coordinates
double movedMeshTrianglePoints[3][3];
for (unsigned i = 0; i < 3; ++i)
{
movedMeshTrianglePoints[0][i] = 0;
movedMeshTrianglePoints[1][i] =
meshTrianglePoints[1][i] -
meshTrianglePoints[0][i];
movedMeshTrianglePoints[2][i] =
meshTrianglePoints[2][i] -
meshTrianglePoints[0][i];
}
// Normalize the texture coordinates
double movedTextureTrianglePoints[3][2];
for (unsigned i = 0; i < 2; ++i)
{
movedTextureTrianglePoints[0][i] = 0;
movedTextureTrianglePoints[1][i] =
textureTrianglePoints[1][i] -
textureTrianglePoints[0][i];
movedTextureTrianglePoints[2][i] =
textureTrianglePoints[2][i] -
textureTrianglePoints[0][i];
}
// Calculate SVD of a matrix consisting of normalized vertices
double U[3][3];
double w[3];
double VT[3][3];
vtkMath::SingularValueDecomposition3x3(movedMeshTrianglePoints, U, w, VT);
// Calculate pseudo inverse of a matrix consisting of normalized vertices
double pseudoInverse[3][3]{0};
for (unsigned i = 0; i < 3; ++i)
{
for (unsigned j = 0; j < 3; ++j)
{
for (unsigned k = 0; k < 3; ++k)
{
if (w[k] != 0)
{
pseudoInverse[i][j] += VT[k][i] * U[j][k] / w[k];
}
}
}
}
// Calculate interpolation matrix
double interpolationMatrix[3][2]{0};
for (unsigned i = 0; i < 3; ++i)
{
for (unsigned j = 0; j < 2; ++j)
{
for (unsigned k = 0; k < 3; ++k)
{
interpolationMatrix[i][j] += pseudoInverse[i][k] * movedTextureTrianglePoints[k][j];
}
}
}
// Calculate interpolated texture coordinates of the intersection point
double interpolatedTexturePoint[2]{textureTrianglePoints[0][0], textureTrianglePoints[0][1]};
for (unsigned i = 0; i < 2; ++i)
{
for (unsigned j = 0; j < 3; ++j)
{
interpolatedTexturePoint[i] += (intersectionCoordinates[j] - meshTrianglePoints[0][j]) * interpolationMatrix[j][i];
}
}
// Print the result
std::cout << "Interpolated texture coordinates";
for (unsigned i = 0; i < 2; ++i)
{
std::cout << " " << interpolatedTexturePoint[i];
}
std::cout << std::endl;
return EXIT_SUCCESS;
}
CMake 项目文件CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
PROJECT(IntersectInterpolate)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(VTK REQUIRED)
include(${VTK_USE_FILE})
add_executable(IntersectInterpolate MACOSX_BUNDLE main.cpp)
if(VTK_LIBRARIES)
target_link_libraries(IntersectInterpolate ${VTK_LIBRARIES})
else()
target_link_libraries(IntersectInterpolate vtkHybrid vtkWidgets)
endif()
数学
我们需要什么
假设您有一个由三角形组成的网格,并且您的顶点具有纹理坐标。
给定三角形的顶点A
、B
和C
,对应的纹理坐标A'
、B'
和C'
,你想要找到从三角形的另一个内部点和边界点到纹理的映射(以插值)。
让我们做一些理性的假设:
- 点
A
、B
、C
应对应其纹理坐标A'
、B'
、C'
;
- 边界上的每个点
X
,比如说AB
,应该对应于A'B'
线上的点,如下所示:|AX| / |AB| = |A'X'| / |A'B'|
——原来的一半三角形应该在纹理贴图的中间;
- 三角形的质心
(A + B + C) / 3
应该对应于纹理三角形的质心 (A' + B' + C') / 3
.
要求解的方程
看起来我们想要仿射映射:原始三角形的顶点坐标应该乘以一些系数并添加到一些常数。
让我们构建方程组
Ax * Mxx + Ay * Myx + Az * Mzx + M0x = A'x
Ax * Mxy + Ay * Myy + Az * Mzy + M0y = A'y
Ax * Mxz + Ay * Myz + Az * Mzz + M0z = 0
B
和 C
也一样。
您可以看到我们有 9
个方程和 12
个未知数。
虽然,包含 Miz
的方程(对于 {x, y, z}
中的 i
)有解 0
并且在进一步的计算中没有任何作用,所以我们可以将它们设置为等于0
。
因此,我们的系统具有 6
个方程和 8
个未知数
Ax * Mxx + Ay * Myx + Az * Mzx + M0x = A'x
Ax * Mxy + Ay * Myy + Az * Mzy + M0y = A'y
让我们在矩阵视图中编写整个系统
-- -- -- -- -- --
| 1 Ax Ay Az | | M0x M0y | | A'x A'y |
| 1 Bx By Bz | x | Mxx Mxy | = | B'x B'y |
| 1 Cx Cy Cz | | Myx Myy | | C'x C'y |
-- -- | Mzx Mzy | -- --
-- --
我从 B
和 C
中减去 A
顶点的坐标
和来自 B'
和 C'
的纹理坐标 A'
,
现在我们有了第一个顶点位于的三角形
在坐标系的开始以及相应的纹理坐标中。
这意味着现在三角形不会相对于另一个平移(移动)
我们不需要 M0
插值矩阵的一部分
-- -- -- -- -- --
| Bx By Bz | | Mxx Mxy | | B'x B'y |
| Cx Cy Cz | x | Myx Myy | = | C'x C'y |
-- -- | Mzx Mzy | -- --
-- --
解决方案
我们称第一个矩阵为P
,第二个为M
,最后一个为T
P M = T
矩阵P
不是方阵。
如果我们向其中添加零行,矩阵将变为奇异矩阵。
因此,我们必须计算它的伪逆才能求解方程。
VTK中没有计算伪逆矩阵的函数。
我们去维基百科上的 Moore–Penrose inverse 文章,看到它可以使用 SVD 计算。
VTKMath::SingularValueDecomposition3x3 函数允许我们这样做。
该函数为我们提供了 U
、S
和 VT
矩阵。
我将矩阵 P
的伪逆写为 P"
,
将 U
换位为 UT
,将 VT
换位为 V
。
对角矩阵的伪逆 S
是具有 1 / Sii
个元素的矩阵
其中 Sii
不是零,0
表示零元素
P = U S VT
P" = V S" UT
M = P" T
用法
要应用插值矩阵,
我们需要不要忘记我们需要翻译输入和输出向量。
A'
是三角形中第一个顶点的纹理坐标的二维向量,
A
是顶点坐标的3D向量,
M
是找到的插值矩阵,
p
是我们想要获取纹理坐标的 3D 交点,
t'
是生成的带有内插纹理坐标的 2D 矢量
t' = A' + (p - A) M
我已经通过 vtkOBJReader
加载了纹理映射的 OBJ 并将其加载到 vtkModifiedBSPTree
:
auto readerOther(vtkSmartPointer<vtkOBJReader>::New());
auto rawOtherPath(modelPathOther.toLatin1());
readerOther->SetFileName(rawOtherPath.data());
readerOther->Update();
auto meshDataOther(readerOther->GetOutput());
auto bspTreeOther(vtkSmartPointer<vtkModifiedBSPTree>::New());
bspTreeOther->SetDataSet(meshDataOther);
bspTreeOther->BuildLocator();
然后我计算我的线段开始和结束并将其输入
if (bspTreeOther->IntersectWithLine(p1, p2, tolerance, distanceAlongLine, intersectionCoords, pcoords, subId, cellId, cell))
当然还有所有相关的预定义变量。
我需要的是纹理交点处的UV坐标。
我对 VTK 还很陌生,所以我还没有理解它是如何组合在一起的逻辑;当我挖掘源代码时,抽象层仍然让我迷失。
我在 SO 和 VTK 用户档案中寻找这个答案,发现那些对 VTK 有深刻理解的人给那些自己几乎在那里的人提供了模糊的提示,因此到目前为止对我帮助不大。
(2018 年 11 月 9 日追加) 澄清一下,我正在使用由单个 3D 扫描仪拍摄创建的非退化三角网格,因此我的代码永远不会看到四边形和其他更高的多边形。一个通用的解决方案应该考虑到这些事情,但这可以通过首先通过良好应用 handwavium 对网格进行三角测量来实现。
[重写 2019/5/7 以反映更新的理解。]
在发现参数坐标是一个函数的输入,在三角形单元的情况下,可以从该函数获得重心坐标,然后了解什么是重心坐标,我能够计算出以下内容。
const auto readerOther(vtkSmartPointer<vtkOBJReader>::New());
const auto rawOtherPath(modelPathOther.toLatin1());
readerOther->SetFileName(rawOtherPath.data());
readerOther->Update();
const auto meshDataOther(readerOther->GetOutput());
const auto bspTreeOther(vtkSmartPointer<vtkModifiedBSPTree>::New());
bspTreeOther->SetDataSet(meshDataOther);
bspTreeOther->BuildLocator();
double point1[3]{0.0, 0.0, 0.0}; // start of line segment used to intersect the model.
double point2[3]{0.0, 0.0, 10.0}; // end of line segment
double distanceAlongLine;
double intersectionCoords[3]; // The coordinate of the intersection.
double parametricCoords[3]; // Parametric Coordinates of the intersection - see https://lorensen.github.io/VTKExamples/site/VTKBook/08Chapter8/#82-interpolation-functions
int subId; // ?
vtkIdType cellId;
double intersectedTextureCoords[2];
if (bspTreeOther->IntersectWithLine(point1, point2, TOLERANCE, distanceAlongLine, intersectionCoords, parametricCoords, subId, cellId))
{
const auto textureCoordsOther(meshDataOther->GetPointData()->GetTCoords());
const auto pointIds{meshDataOther->GetCell(cellId)->GetPointIds()};
const auto vertexIndex0{pointIds->GetId(0)};
const auto vertexIndex1{pointIds->GetId(1)};
const auto vertexIndex2{pointIds->GetId(2)};
double texCoord0[2];
double texCoord1[2];
double texCoord2[2];
textureCoordsOther->GetTuple(vertexIndex0, texCoord0);
textureCoordsOther->GetTuple(vertexIndex1, texCoord1);
textureCoordsOther->GetTuple(vertexIndex2, texCoord2);
const auto parametricR{parametricCoords[0]};
const auto parametricS{parametricCoords[1]};
const auto barycentricW0{1 - parametricR - parametricS};
const auto barycentricW1{parametricR};
const auto barycentricW2{parametricS};
intersectedTextureCoords[0] =
barycentricW0 * texCoord0[0] +
barycentricW1 * texCoord1[0] +
barycentricW2 * texCoord2[0];
intersectedTextureCoords[1] =
barycentricW0 * texCoord0[1] +
barycentricW1 * texCoord1[1] +
barycentricW2 * texCoord2[1];
}
请注意,此代码是对我正在使用的实际代码的解释;我正在使用 Qt 及其 QVector2D 和 QVector3D 类 以及一些解释器粘合函数来往返于双精度数组。
有关各种细胞类型的参数坐标系的详细信息,请参见https://lorensen.github.io/VTKExamples/site/VTKBook/08Chapter8。
代码
注意如果一个顶点属于多个多边形并且具有不同的纹理坐标,VTK 将创建该顶点的副本。 我不使用 vtkCleanPolyData,因为 VTK 会合并这样的 "duplicates",据我所知,我们将丢失所需的信息。
我用vtkCellLocator instead of vtkModifiedBSPTree, 因为在我的情况下它更快。
主文件main.cpp
。
您可以在 start
和 end
数组中找到幻数 — 这些是您的 p1
和 p2
。
我设置这些值只是为了举例
#include <vtkSmartPointer.h>
#include <vtkPointData.h>
#include <vtkCellLocator.h>
#include <vtkGenericCell.h>
#include <vtkOBJReader.h>
#include <vtkTriangleFilter.h>
#include <vtkMath.h>
#include <iostream>
int main(int argc, char * argv[])
{
if (argc < 2)
{
std::cerr << "Usage: " << argv[0] << " OBJ_file_name" << std::endl;
return EXIT_FAILURE;
}
auto reader{vtkSmartPointer<vtkOBJReader>::New()};
reader->SetFileName(argv[1]);
reader->Update();
// Triangulate the mesh if needed
auto triangleFilter{vtkSmartPointer<vtkTriangleFilter>::New()};
triangleFilter->SetInputConnection(reader->GetOutputPort());
triangleFilter->Update();
auto mesh{triangleFilter->GetOutput()};
// Use `auto mesh(reader->GetOutput());` instead if no triangulation needed
// Build a locator to find intersections
auto locator{vtkSmartPointer<vtkCellLocator>::New()};
locator->SetDataSet(mesh);
locator->BuildLocator();
// Initialize variables needed for intersection calculation
double start[3]{-1, 0, 0.5};
double end[3]{ 1, 0, 0.5};
double tolerance{1E-6};
double relativeDistanceAlongLine;
double intersectionCoordinates[3];
double parametricCoordinates[3];
int subId;
vtkIdType cellId;
auto cell{vtkSmartPointer<vtkGenericCell>::New()};
// Find intersection
int intersected = locator->IntersectWithLine(
start,
end,
tolerance,
relativeDistanceAlongLine,
intersectionCoordinates,
parametricCoordinates,
subId,
cellId,
cell.Get()
);
// Get points of intersection cell
auto pointsIds{vtkSmartPointer<vtkIdList>::New()};
mesh->GetCellPoints(cellId, pointsIds);
// Store coordinates and texture coordinates of vertices of the cell
double meshTrianglePoints[3][3];
double textureTrianglePoints[3][2];
auto textureCoordinates{mesh->GetPointData()->GetTCoords()};
for (unsigned pointNumber = 0; pointNumber < cell->GetNumberOfPoints(); ++pointNumber)
{
mesh->GetPoint(pointsIds->GetId(pointNumber), meshTrianglePoints[pointNumber]);
textureCoordinates->GetTuple(pointsIds->GetId(pointNumber), textureTrianglePoints[pointNumber]);
}
// Normalize the coordinates
double movedMeshTrianglePoints[3][3];
for (unsigned i = 0; i < 3; ++i)
{
movedMeshTrianglePoints[0][i] = 0;
movedMeshTrianglePoints[1][i] =
meshTrianglePoints[1][i] -
meshTrianglePoints[0][i];
movedMeshTrianglePoints[2][i] =
meshTrianglePoints[2][i] -
meshTrianglePoints[0][i];
}
// Normalize the texture coordinates
double movedTextureTrianglePoints[3][2];
for (unsigned i = 0; i < 2; ++i)
{
movedTextureTrianglePoints[0][i] = 0;
movedTextureTrianglePoints[1][i] =
textureTrianglePoints[1][i] -
textureTrianglePoints[0][i];
movedTextureTrianglePoints[2][i] =
textureTrianglePoints[2][i] -
textureTrianglePoints[0][i];
}
// Calculate SVD of a matrix consisting of normalized vertices
double U[3][3];
double w[3];
double VT[3][3];
vtkMath::SingularValueDecomposition3x3(movedMeshTrianglePoints, U, w, VT);
// Calculate pseudo inverse of a matrix consisting of normalized vertices
double pseudoInverse[3][3]{0};
for (unsigned i = 0; i < 3; ++i)
{
for (unsigned j = 0; j < 3; ++j)
{
for (unsigned k = 0; k < 3; ++k)
{
if (w[k] != 0)
{
pseudoInverse[i][j] += VT[k][i] * U[j][k] / w[k];
}
}
}
}
// Calculate interpolation matrix
double interpolationMatrix[3][2]{0};
for (unsigned i = 0; i < 3; ++i)
{
for (unsigned j = 0; j < 2; ++j)
{
for (unsigned k = 0; k < 3; ++k)
{
interpolationMatrix[i][j] += pseudoInverse[i][k] * movedTextureTrianglePoints[k][j];
}
}
}
// Calculate interpolated texture coordinates of the intersection point
double interpolatedTexturePoint[2]{textureTrianglePoints[0][0], textureTrianglePoints[0][1]};
for (unsigned i = 0; i < 2; ++i)
{
for (unsigned j = 0; j < 3; ++j)
{
interpolatedTexturePoint[i] += (intersectionCoordinates[j] - meshTrianglePoints[0][j]) * interpolationMatrix[j][i];
}
}
// Print the result
std::cout << "Interpolated texture coordinates";
for (unsigned i = 0; i < 2; ++i)
{
std::cout << " " << interpolatedTexturePoint[i];
}
std::cout << std::endl;
return EXIT_SUCCESS;
}
CMake 项目文件CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
PROJECT(IntersectInterpolate)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(VTK REQUIRED)
include(${VTK_USE_FILE})
add_executable(IntersectInterpolate MACOSX_BUNDLE main.cpp)
if(VTK_LIBRARIES)
target_link_libraries(IntersectInterpolate ${VTK_LIBRARIES})
else()
target_link_libraries(IntersectInterpolate vtkHybrid vtkWidgets)
endif()
数学
我们需要什么
假设您有一个由三角形组成的网格,并且您的顶点具有纹理坐标。
给定三角形的顶点A
、B
和C
,对应的纹理坐标A'
、B'
和C'
,你想要找到从三角形的另一个内部点和边界点到纹理的映射(以插值)。
让我们做一些理性的假设:
- 点
A
、B
、C
应对应其纹理坐标A'
、B'
、C'
; - 边界上的每个点
X
,比如说AB
,应该对应于A'B'
线上的点,如下所示:|AX| / |AB| = |A'X'| / |A'B'|
——原来的一半三角形应该在纹理贴图的中间;- 三角形的质心
(A + B + C) / 3
应该对应于纹理三角形的质心(A' + B' + C') / 3
.
- 三角形的质心
要求解的方程
看起来我们想要仿射映射:原始三角形的顶点坐标应该乘以一些系数并添加到一些常数。 让我们构建方程组
Ax * Mxx + Ay * Myx + Az * Mzx + M0x = A'x
Ax * Mxy + Ay * Myy + Az * Mzy + M0y = A'y
Ax * Mxz + Ay * Myz + Az * Mzz + M0z = 0
B
和 C
也一样。
您可以看到我们有 9
个方程和 12
个未知数。
虽然,包含 Miz
的方程(对于 {x, y, z}
中的 i
)有解 0
并且在进一步的计算中没有任何作用,所以我们可以将它们设置为等于0
。
因此,我们的系统具有 6
个方程和 8
个未知数
Ax * Mxx + Ay * Myx + Az * Mzx + M0x = A'x
Ax * Mxy + Ay * Myy + Az * Mzy + M0y = A'y
让我们在矩阵视图中编写整个系统
-- -- -- -- -- --
| 1 Ax Ay Az | | M0x M0y | | A'x A'y |
| 1 Bx By Bz | x | Mxx Mxy | = | B'x B'y |
| 1 Cx Cy Cz | | Myx Myy | | C'x C'y |
-- -- | Mzx Mzy | -- --
-- --
我从 B
和 C
中减去 A
顶点的坐标
和来自 B'
和 C'
的纹理坐标 A'
,
现在我们有了第一个顶点位于的三角形
在坐标系的开始以及相应的纹理坐标中。
这意味着现在三角形不会相对于另一个平移(移动)
我们不需要 M0
插值矩阵的一部分
-- -- -- -- -- --
| Bx By Bz | | Mxx Mxy | | B'x B'y |
| Cx Cy Cz | x | Myx Myy | = | C'x C'y |
-- -- | Mzx Mzy | -- --
-- --
解决方案
我们称第一个矩阵为P
,第二个为M
,最后一个为T
P M = T
矩阵P
不是方阵。
如果我们向其中添加零行,矩阵将变为奇异矩阵。
因此,我们必须计算它的伪逆才能求解方程。
VTK中没有计算伪逆矩阵的函数。
我们去维基百科上的 Moore–Penrose inverse 文章,看到它可以使用 SVD 计算。
VTKMath::SingularValueDecomposition3x3 函数允许我们这样做。
该函数为我们提供了 U
、S
和 VT
矩阵。
我将矩阵 P
的伪逆写为 P"
,
将 U
换位为 UT
,将 VT
换位为 V
。
对角矩阵的伪逆 S
是具有 1 / Sii
个元素的矩阵
其中 Sii
不是零,0
表示零元素
P = U S VT
P" = V S" UT
M = P" T
用法
要应用插值矩阵,
我们需要不要忘记我们需要翻译输入和输出向量。
A'
是三角形中第一个顶点的纹理坐标的二维向量,
A
是顶点坐标的3D向量,
M
是找到的插值矩阵,
p
是我们想要获取纹理坐标的 3D 交点,
t'
是生成的带有内插纹理坐标的 2D 矢量
t' = A' + (p - A) M