将3D面绘制为2D。

4

我有一个3D网格,希望能够将每个面绘制成2D形状。

我的想法是这样的: 对于每个面 1. 访问面法线 2. 从法向量获取旋转矩阵 3. 将每个顶点乘以旋转矩阵,以在“2D”平面上获得顶点 4. 从转换后的顶点获取2个坐标

我不知道这是否是最好的方法,所以欢迎任何建议。

目前我正在尝试从法向量获取旋转矩阵, 我该如何做到这一点?

更新:

这里是我需要的可视化说明:

3d to 2d

目前我有四边形,但没有问题 将它们转换为三角形。

我想旋转一个面的顶点,使其中一个维度被压扁。

我还需要存储面的原始3D旋转。 我想象那应该是面法线的反向旋转。

我觉得我有点迷失在空间中 :)

这是我使用Processing做的基本原型:

void setup(){
  size(400,400,P3D);
  background(255);
  stroke(0,0,120);
  smooth();
  fill(0,120,0);

  PVector x = new PVector(1,0,0);
  PVector y = new PVector(0,1,0);
  PVector z = new PVector(0,0,1);

  PVector n  = new PVector(0.378521084785,0.925412774086,0.0180059205741);//normal
  PVector p0 = new PVector(0.372828125954,-0.178844243288,1.35241031647);
  PVector p1 = new PVector(-1.25476706028,0.505195975304,0.412718296051);
  PVector p2 = new PVector(-0.372828245163,0.178844287992,-1.35241031647);
  PVector p3 = new PVector(1.2547672987,-0.505196034908,-0.412717700005);

  PVector[] face = {p0,p1,p2,p3};
  PVector[] face2d = new PVector[4];
  PVector   nr = PVector.add(n,new PVector());//clone normal

  float rx = degrees(acos(n.dot(x)));//angle between normal and x axis
  float ry = degrees(acos(n.dot(y)));//angle between normal and y axis
  float rz = degrees(acos(n.dot(z)));//angle between normal and z axis

  PMatrix3D r = new PMatrix3D();
  //is this ok, or should I drop the builtin function, and add 
  //the rotations manually
  r.rotateX(rx);
  r.rotateY(ry);
  r.rotateZ(rz);

  print("original: ");println(face);
  for(int i = 0 ; i < 4; i++){
    PVector rv = new PVector();
    PVector rn = new PVector();
    r.mult(face[i],rv);
    r.mult(nr,rn);
    face2d[i] = PVector.add(face[i],rv);
  }
  print("rotated: ");println(face2d);
  //draw
  float scale = 100.0;
  translate(width * .5,height * .5);//move to centre, Processing has 0,0 = Top,Lef
  beginShape(QUADS);
  for(int i = 0 ; i < 4; i++){
   vertex(face2d[i].x * scale,face2d[i].y * scale,face2d[i].z * scale);
  }
  endShape();
  line(0,0,0,nr.x*scale,nr.y*scale,nr.z*scale);

  //what do I do with this ?
  float c = cos(0), s = sin(0);
  float x2 = n.x*n.x,y2 = n.y*n.y,z2 = n.z*n.z; 
  PMatrix3D m = new PMatrix3D(x2+(1-x2)*c,  n.x*n.y*(1-c)-n.z*s,  n.x*n.z*(1-c)+n.y*s,  0,
                              n.x*n.y*(1-c)+n.z*s,y2+(1-y2)*c,n.y*n.z*(1-c)-n.x*s,0,
                              n.x*n.y*(1-c)-n.y*s,n.x*n.z*(1-c)+n.x*s,z2-(1-z2)*c,0,
                              0,0,0,1);
}

更新

如果我有点烦人,很抱歉,但似乎我还是不明白。

这里有一些使用Blender的API的Python代码:

import Blender
from Blender import *
import math
from math import sin,cos,radians,degrees

def getRotMatrix(n):
    c = cos(0)
    s = sin(0)
    x2 = n.x*n.x
    y2 = n.y*n.y
    z2 = n.z*n.z
    l1 = x2+(1-x2)*c, n.x*n.y*(1-c)+n.z*s, n.x*n.y*(1-c)-n.y*s
    l2 = n.x*n.y*(1-c)-n.z*s,y2+(1-y2)*c,n.x*n.z*(1-c)+n.x*s
    l3 = n.x*n.z*(1-c)+n.y*s,n.y*n.z*(1-c)-n.x*s,z2-(1-z2)*c
    m = Mathutils.Matrix(l1,l2,l3)
    return m

scn = Scene.GetCurrent()
ob = scn.objects.active.getData(mesh=True)#access mesh

out = ob.name+'\n'
#face0
f = ob.faces[0]
n = f.v[0].no
out += 'face: ' + str(f)+'\n'
out += 'normal: ' + str(n)+'\n'

m = getRotMatrix(n)
m.invert()

rvs = []
for v in range(0,len(f.v)):
    out += 'original vertex'+str(v)+': ' + str(f.v[v].co) + '\n'
    rvs.append(m*f.v[v].co)

out += '\n'
for v in range(0,len(rvs)):
    out += 'original vertex'+str(v)+': ' + str(rvs[v]) + '\n'

f = open('out.txt','w')
f.write(out)
f.close

我所做的就是获取当前对象,访问第一个面,获取法线和顶点,计算旋转矩阵,将其倒置,然后将其乘以每个顶点。最后,我输出简单的结果。

这是默认平面的输出结果,我手动将所有顶点旋转了30度:

Plane.008
face: [MFace (0 3 2 1) 0]
normal: [0.000000, -0.499985, 0.866024](vector)
original vertex0: [1.000000, 0.866025, 0.500000](vector)
original vertex1: [-1.000000, 0.866026, 0.500000](vector)
original vertex2: [-1.000000, -0.866025, -0.500000](vector)
original vertex3: [1.000000, -0.866025, -0.500000](vector)

rotated vertex0: [1.000000, 0.866025, 1.000011](vector)
rotated vertex1: [-1.000000, 0.866026, 1.000012](vector)
rotated vertex2: [-1.000000, -0.866025, -1.000012](vector)
rotated vertex3: [1.000000, -0.866025, -1.000012](vector)

这是著名的Suzanne网格模型的第一个面:

Suzanne.001
face: [MFace (46 0 2 44) 0]
normal: [0.987976, -0.010102, 0.154088](vector)
original vertex0: [0.468750, 0.242188, 0.757813](vector)
original vertex1: [0.437500, 0.164063, 0.765625](vector)
original vertex2: [0.500000, 0.093750, 0.687500](vector)
original vertex3: [0.562500, 0.242188, 0.671875](vector)

rotated vertex0: [0.468750, 0.242188, -0.795592](vector)
rotated vertex1: [0.437500, 0.164063, -0.803794](vector)
rotated vertex2: [0.500000, 0.093750, -0.721774](vector)
rotated vertex3: [0.562500, 0.242188, -0.705370](vector)

Plane.008网格的顶点已经改变,但Suzanne.001网格的顶点没有改变。它们不应该改变吗?我是否应该期望在一个轴上得到零?

注意:1. Blender的矩阵支持*运算符;2. 在Blender的坐标系中,Z轴指向上。看起来像是一个右手坐标系,在X轴上旋转了90度。

谢谢。

4个回答

2

我认为这看起来很合理。以下是如何从法向量获取旋转矩阵的方法。法向量即为向量,角度为0。您可能需要逆转旋转。

您的网格是否已三角化?我假设它已经三角化了。如果是这样,您可以不使用旋转矩阵来完成此操作。让面的点为A、B、C,取任意两个面的顶点,比如说AB。将x轴定义为沿着向量AB方向。A0,0处。B0,|AB|处。可以通过使用点积得到ACAB之间的夹角(通过三角函数),以及长度|AC|来确定C


哦,代码与插图没有链接。在代码中,我编写了一个带有绕x、y和z轴旋转的顶点的平面。插图是为了展示我想要实现的内容。 - George Profenza
关于维基百科页面,u是你的普通向量,具有x、y和z分量(如果它不是单位长度,请将其按照法线向量|u|的长度进行缩放)。theta是旋转角度,我们将其视为0。R是一个3x3矩阵,在给定的方程式中填入每个单元格,例如R[0][0] = u_x * u_x +(1-u_x * u_x)* cos(theta)。如果您的旋转矩阵实际上是齐次变换,则必须在右侧和底部添加一行和一列0,并在R [3] [3]中添加1。 - cape1232
@George,你将普通向量转换为旋转矩阵的方法是错误的。你应该学习维基百科上关于旋转的页面,并尝试一些例子来提高你的理解。旋转是棘手且不直观的。 - cape1232
@我 我刚刚注意到你在代码中正确地创建了旋转矩阵'm'。这是维基页面上的R。你有一个函数可以反转它来得到'm_inverse'吗? - cape1232
只是为了确保我理解正确,您的目标是否可以这样重新表述?您想将网格导入明信片包中,但明信片包不直接支持此操作,因此您想通过“展平”网格面并存储将其放回3D空间中原来位置的变换来实现。 - cape1232
显示剩余4条评论

1
你已经正确创建了m矩阵。这是对应于你的法向量的旋转。你可以使用该矩阵的逆矩阵来“取消旋转”你的点。face2d的法线将是x,即沿着x轴的点。因此,相应地提取你的2D坐标。(这假设你的四边形近似平面。)
我不知道你正在使用的库(Processing),所以我只是假设有m.invert()方法和一个用于将旋转矩阵应用于点的运算符。它们当然可能被称为其他名称。幸运的是,纯旋转矩阵的逆矩阵是其转置,并且如果需要,手动执行矩阵和向量的乘法也很容易。
void setup(){
  size(400,400,P3D);
  background(255);
  stroke(0,0,120);
  smooth();
  fill(0,120,0);

  PVector x = new PVector(1,0,0);
  PVector y = new PVector(0,1,0);
  PVector z = new PVector(0,0,1);

  PVector n  = new PVector(0.378521084785,0.925412774086,0.0180059205741);//normal
  PVector p0 = new PVector(0.372828125954,-0.178844243288,1.35241031647);
  PVector p1 = new PVector(-1.25476706028,0.505195975304,0.412718296051);
  PVector p2 = new PVector(-0.372828245163,0.178844287992,-1.35241031647);
  PVector p3 = new PVector(1.2547672987,-0.505196034908,-0.412717700005);

  PVector[] face = {p0,p1,p2,p3};
  PVector[] face2d = new PVector[4];

  //what do I do with this ?
  float c = cos(0), s = sin(0);
  float x2 = n.x*n.x,y2 = n.y*n.y,z2 = n.z*n.z; 
  PMatrix3D m_inverse = 
      new PMatrix3D(x2+(1-x2)*c, n.x*n.y*(1-c)+n.z*s, n.x*n.y*(1-c)-n.y*s, 0,
                    n.x*n.y*(1-c)-n.z*s,y2+(1-y2)*c,n.x*n.z*(1-c)+n.x*s,   0,
                     n.x*n.z*(1-c)+n.y*s,n.y*n.z*(1-c)-n.x*s,z2-(1-z2)*c,  0,
                    0,0,0,1);

  face2d[0] = m_inverse * p0; // Assuming there's an appropriate operator*().
  face2d[1] = m_inverse * p1; 
  face2d[2] = m_inverse * p2;
  face2d[3] = m_inverse * p3;

  // print & draw as you did before...

}

顺便提一下,由于旋转矩阵的逆矩阵是其转置,因此您可以通过转置用于填充PMatrix3D的方程式来直接计算m_inverse。 - cape1232
如果我打印m_inverse与顶点相乘的结果,我应该期望在其中一个轴上得到0.0吗? - George Profenza

0

对于面v0-v1-v3-v2向量v3-v0、v3-v2和一个面法线,已经形成了旋转矩阵,可以将2D面转换为3D面。

矩阵代表坐标系。每行(或列,取决于符号)对应于新坐标系内的轴坐标系。 3D旋转/平移矩阵可以表示为:

vx.x    vx.y    vx.z    0
vy.x    vy.y    vy.z    0
vz.x    vz.y    vz.z    0
vp.x    vp.y    vp.z    1

其中 vx 是坐标系的 x 轴,vy 是 y 轴,vz 是 z 轴,vp 是新系统的原点。

假设 v3-v0 是 y 轴(第二行),v3-v2 是 x 轴(第一行),法线是 z 轴(第三行)。从它们中构建矩阵。然后反转矩阵。你会得到一个能将 3D 面旋转为 2D 面的矩阵。

我有一个 3D 网格,我想绘制每个面成为 2D 形状。

我怀疑 UV展开 算法 更接近于你想要实现的,而不是尝试从 3D 面获取旋转矩阵。


聪明,但有一个限制。只有当v3-v0和v3-v2之间的角度恰好为90度时才有效。您的轴必须是正交的。为了保证正交性(tm),您可以从一侧开始,比如v3-v0,将其与面法线进行叉积运算以获得第三个轴。这些是通过构造正交的。 - cape1232
@cape1232:据我所知,即使角度不完全是90度(只要不是0或180度),它也可以工作 - 我曾经使用矩阵来获取重心坐标。但是,为了实现这一点,您需要反转矩阵,而不仅仅是转置它。此外,如果角度不是90度,逆变换将“扭曲”四边形,因此在2D v0-v3-v2中,角度将为90度。另外,如果反演考虑了3D面的原点(比如v3),则在2D投影中,该顶点将位于x == 0,y == 0处。 - SigTerm

0

这很容易实现:(注:通过“面”我指的是“三角形”)

  1. 创建一个视图矩阵,表示相机看向一个面。
    1. 使用双线性插值确定面的中心。
    2. 确定面的法线。
    3. 将相机定位在相反的法线方向上的一些单位。
    4. 让相机看向面的中心。
    5. 将相机的上向量指向面的任何顶点的中间方向。
    6. 将宽高比设置为1。
    7. 使用这些数据计算视图矩阵。
  2. 创建一个正交投影矩阵。
    1. 将视锥体的宽度和高度设置得足够大,以包含整个面(例如,面的最长边的长度)。
    2. 计算投影矩阵。
  3. 对于面的每个顶点v,将其乘以两个矩阵:v * view * projection。

该结果是将您的3D面部投影到2D空间中,就好像您在完全正交的情况下观察它们,没有任何透视扭曲。最终坐标将以规范化屏幕坐标表示,其中(-1,-1)为左下角,(0,0)为中心,(1,1)为右上角。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接