OpenGL沿路径绘制管道

7

我正在尝试使用OpenGL绘制一些有趣的形状。

目前,我正在制作一个管道。我可以使用以下代码绘制直管:

void tube(GLfloat radius, GLfloat segment_length) {
    glPolygonMode(GL_BACK, GL_NONE);
    glPolygonMode(GL_FRONT, GL_FILL);

    glPushMatrix(); {
        GLfloat z1 = 0.0;
        GLfloat z2 = segment_length;

        GLfloat y_offset = 0.0;
        GLfloat y_change = 0.00;

        int i = 0;
        int j = 0;
        for (j = 0; j < 20; j++) {
            glPushMatrix(); {
                glBegin(GL_TRIANGLE_STRIP); {
                    for (i = 360; i >= 0; i--) {
                        GLfloat theta = i * pi/180;
                        GLfloat x = radius * cos(theta);
                        GLfloat y = radius * sin(theta) + y_offset;

                        glVertex3f(x, y, z1);
                        glVertex3f(x, y, z2);
                    }
                } glEnd();
            } glPopMatrix();

            // attach the front of the next segment to the back of the previous
            z1 = z2;
            z2 += segment_length;

            // make some other adjustments
            y_offset += y_change;
        }
    } glPopMatrix();
}

然而,我还没有想出如何让管道沿着任何预定义的路径(比如一个螺旋线或者简单的直线)运动。如果你将y_change改为0.01之类的值,那么就会在每个管道段偏移0.01的y方向上进行绘制。这很好,但是我怎样才能使每个段指向那个方向呢?换句话说,现在每个段都是面向相同的方向绘制的,而且方向并不是管道的方向(因为y_change=0.01时,方向略微向上)。
我不确定该怎么办。我曾经尝试过通过获取前一段和当前段之间的矢量来玩弄向量,但除此之外,我不太清楚要做什么。

1
你看过(以红皮书的甜甜圈示例为例)吗?http://www.opengl.org/resources/code/samples/redbook/torus.c - Jerry Coffin
4个回答

20
这个想法被称为“路径控制挤出”,即您拥有一个基本的n维形状,并沿着n+1维曲线挤出它。我能看出你的表情:“嗯,他在说什么?”
因此,以下是粗略的概述。首先,您需要一个将值(通常称为t)映射到空间中连续、平滑曲线的函数。例如,螺旋线:
path(t): RR³, t ↦ ( a·sin(k·t), b·cos(k·t), c·t )

这个想法是,找到一个本地化的坐标系来定义与路径相关的顶点位置 - 这意味着其中一个坐标要与路径平行对齐,因此需要找到它的切线。这是通过找到其梯度来完成的:

tangent(t): RR³, t ↦ ( k·a·cos(k·t), -k·b·sin(k·t), c ) = d/dt path(t)

这是指沿着曲线指向本地基向量,其中本地坐标系统的原点位于曲线上的点 t

但是我们需要另外两个向量来构成一个完整的三维基向量。通常最好选择第二个基向量垂直于曲率,可以通过找到切线的旋度来实现:

normal(t): R → R³, t ↦ ( -k²·a·sin(k·t), -k²·b·cos(k·t), 0 ) = d/dt tangent(t) = d²/dt² path(t)

这被称为法向量。

第三个基向量可以通过法向量和切线的叉积得到,从而得到副法向量。我会让你把它当作练习去理解。

现在要沿着曲线拉伸一个形状,你需要将路径分成段,只需迭代选择范围内的t,就可以得到本地原点。你的拉伸形状的点相对于此原点路径(t)。假设你的形状由x-y平面上的P_n点组成,则:

for t in [k..l]:
    for p in P_n:
        yield_vertex( path(t).x + binormal(t).x * p.x, 
                      path(t).y + normal(t).y * p.y, 
                      path(t).z )

我会把这个留给你,让你想办法将其适配到OpenGL,毕竟思考可以帮助你学习。如果你明天还没解决,我很愿意提供答案,但是自己想出来通常更有趣。


2
+1:非常好的答案。如果您对分段线性函数感兴趣,可以将切线视为v(n)(从上一个点到这个点的向量)和v(n+1)(从这个点到下一个点的向量)的平均值。 - Jackson Pope
太棒了,感谢您的回答。我会花些时间来研究这个问题。我会让您知道我的进展情况(尽管学校和工作可能会占用我留给这个的娱乐时间)。 - gregghz
是的,我担心我的数学技能不足以解决这个问题(部分原因可能是我并不完全理解你的符号表示)。我似乎无法将其翻译成OpenGL,无论我做什么,管道段始终面向一个方向。 - gregghz

1

GLE挤出库有点过时,存在一些错误,需要重新制作。 - sancelot

1

0

从代数向量的角度来看,物理解释是您挤出问题的好选择:

https://cobalt.rocky.edu/~ulrich.hoensch/FS_2016/MAT275/Lecture%20Notes/Lecture%2013_5%20The%20Binormal%20Vector%20and%20Torsion.pdf

https://physics.stackexchange.com/questions/368634/direction-of-velocity-vector-in-3d-space

https://math.libretexts.org/Bookshelves/Calculus/Map%3A_University_Calculus_(Hass_et_al)/12%3A_Vector-Valued_Functions_and_Motion_in_Space/12.5%3A_Tangential_and_Normal_Components_of_Acceleration

https://web.mit.edu/hyperbook/Patrikalakis-Maekawa-Cho/node24.html

实际上可以看到:

1)path(t)是r(alpha(t)),其中alpha(t)= kt,k = 2PI / w r(t)-具有3d(欧几里得空间)坐标的位置矢量:x(t),y(t),z(t) alpha-中心处的角度 w-标量角速度= 2PI / k =(r x v)/ || r ^ 2 ||,其中x是向量积

  1. tangent(t)是v(t)切向速度= dr / dt(梯度)

  2. normal(t)是a(t)法向加速度= dv / dt

  3. binormal(b)是向量积的结果b(t)= a(t)x v(t)

binormal(b),normal(a)和tangent(v)向量定义了沿着空间曲线(r)的正交局部坐标系-请参见node24.html文章中的图2.6

因此,如果我已经记住了b(= a x v)、n(= a = dv/dt)和v(= dr/dt)的本地计算值,我可以使用一种转换从BNV系统转换为XYZ系统(从一个坐标系到另一个坐标系的转换;两个坐标系都是正交的),以获得x、y、z坐标;

总之,任何来自XYZ的点(x,y,z)都可以写成其从BNV系统中投影点的线性组合。

在物理学中,参见运动方程:https://en.wikipedia.org/wiki/Equations_of_motion

您可以在这份数学文档中看到它,在幻灯片3和5中: https://www.robots.ox.ac.uk/~sjrob/Teaching/Vectors/

备注

-不要忘记一些可导函数可以分解为诸如sin和cos函数的线性组合(参见傅里叶变换:https://en.wikipedia.org/wiki/Fourier_series)。

-此外,不要忘记这些组合函数(需要区分时非常有用):http://mathsfirst.massey.ac.nz/Calculus/ChainRule/decompose.html#vma

结论:

-如果找到一个三维参数化函数(=算法)来描述一个三维物体(= 3D模型 = 3D对象),那么可以在三维空间中绘制它;

-参数化不是绘制三维物体(3D对象)的唯一方法...例如可以使用二维函数的旋转来获得它!

或者,除了参数化之外,还可以通过以下方式获得3D实体:Tube(沿路径挤出)图像作为两个棱柱的交集结果,参见http://www.songho.ca/opengl/gl_cylinder.html,以及从初始面/轮廓中的点P投影到两个棱柱的交点平面中获得的P'(投影点P的计算),参见https://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/raycast/sld017.htm

(这种挤出方法将一个点集作为输入,用于轮廓=初始面和路径,可以改进以获得平滑的路径或轮廓:https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline

https://jiharu.github.io/intm3d/week03.html,

https://www.ee.ucl.ac.uk/~mflanaga/java/Interpolation.html

或者,您可以尝试使用另一种更平滑的Kockanek-Bartel(KB)插值方法,也称为张力-连续性-偏差样条(TCB)(请参见https://download.java.net/media/java3d/javadoc/1.5.0/com/sun/j3d/utils/behaviors/interpolators/package-summary.html)。

-但请不要忘记,使用这些方法只能得到一个点云(而不是整个3D模型),用于近似3D曲线。

如果知道点云、边缘和面的法向量,则可以完全定义一个模型(请参见基于点云的Poisson重建https://doc.cgal.org/latest/Poisson_surface_reconstruction_3/index.html)。

请注意代数向量3D(或3D向量代数)的强大作用!!!


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