Python+Opengl实现用B样条曲线在方块上实时交互写字

目录

Python+Opengl实现用B样条曲线在方块上实时交互写字

引言

博主是北京理工大学计算机2021级研一的学生,这项任务是计算机图形学这门课程的第一个大作业。由于之前并未接触过opengl,所以在完成这个作业过程中费了一些劲。因为网上找不到符合老师需求的代码,所以代码都是自己拼拼凑凑整的,可能仍有不规范之处。当然,这篇博客是在这门课全结束之后才发布的。

依赖

  • python3.9
  • opengl库

估计python3.7、3.8也都行,但没试过。具体导入库的代码如下:

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np

画方块

这里使用的是opengl自带的画面函数进行绘制的。
首先给出每个面的点坐标:

vertices2 = 1.4*np.array([
    [[0.2, 0.2, 0.2], [-0.2, 0.2, 0.2], [-0.2, -0.2, 0.2], [0.2, -0.2, 0.2]],  # 前
    [[0.2, 0.2, -0.2], [0.2, -0.2, -0.2], [-0.2, -0.2, -0.2], [-0.2, 0.2, -0.2]],  # 后
    [[0.2, 0.2, 0.2], [0.2, 0.2, -0.2], [-0.2, 0.2, -0.2], [-0.2, 0.2, 0.2]],  # 左
    [[0.2, -0.2, 0.2], [0.2, -0.2, -0.2], [-0.2, -0.2, -0.2], [-0.2, -0.2, 0.2]],  # 右
    [[0.2, 0.2, 0.2], [0.2, -0.2, 0.2], [0.2, -0.2, -0.2], [0.2, 0.2, -0.2]],  # 上
    [[-0.2, 0.2, 0.2], [-0.2, -0.2, 0.2], [-0.2, -0.2, -0.2], [-0.2, 0.2, -0.2]]  # 下
])

这里的1.4倍是为了调整大小关系,可根据实际情况调整这个系数。
再给出每个面的颜色:

colours = np.array([
    [0, 1, 1], [1, 0.5, 0.5],
    [1, 1, 0], [0.1, 0.1, 1],
    [0, 1, 0.2], [0.6, 0.6, 0.6]
])

这里的三个数就分别代表RGB的红绿蓝,也可自行调整。
最后再show函数里画出这个立方体。

    for i in range(vertices2.shape[0]):
        glBegin(GL_QUADS)
        points = vertices2[i, :]
        color = colours[i, :]
        for point in points:
            glColor3f(color[0], color[1], color[2])
            glVertex3f(point[0], point[1], point[2])
        glEnd()

屏幕坐标转换为世界坐标

这一步试过很多错,网上大部分代码都是基于C++的。
完成转换主要通过opengl里的反映射函数:gluUnProject.

gluUnProject(MOUSE_X, viewport[3] - MOUSE_Y, z, modelview_mat, projection_mat, viewport)

该函数通过调用鼠标点击的位置信息(MOUSE_X,MOUSE_Y)、图片深度信息(z)、三个转换矩阵(modelview_mat, projection_mat, viewport)进行计算。

B样条曲线绘制

这一步试过更多的错。
代码部分很简单,就是利用gluNurbsCurve函数来进行B样条曲线的绘制。其中,NURBS是非均匀有理B样条(Non-Uniform Rational B-Splines)的缩写。

gluNurbsCurve(nurb, KNOTS, POINTS, GL_MAP1_VERTEX_3)

POINTS就是B样条中的控制点,也就是鼠标点击后得到的点坐标。但由于之前KNOTS一直没有配置正确,导致函数无法画出曲线。
KNOTS可直接根据控制点的数量进行生成。

degree = 3
knotNum = len(POINTS) + degree
KNOTS = [float(i)/(knotNum-1) for i in range(knotNum)]

完整代码

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np

vertices2 = 1.4*np.array([
    [[0.2, 0.2, 0.2], [-0.2, 0.2, 0.2], [-0.2, -0.2, 0.2], [0.2, -0.2, 0.2]],  # 前
    [[0.2, 0.2, -0.2], [0.2, -0.2, -0.2], [-0.2, -0.2, -0.2], [-0.2, 0.2, -0.2]],  # 后
    [[0.2, 0.2, 0.2], [0.2, 0.2, -0.2], [-0.2, 0.2, -0.2], [-0.2, 0.2, 0.2]],  # 左
    [[0.2, -0.2, 0.2], [0.2, -0.2, -0.2], [-0.2, -0.2, -0.2], [-0.2, -0.2, 0.2]],  # 右
    [[0.2, 0.2, 0.2], [0.2, -0.2, 0.2], [0.2, -0.2, -0.2], [0.2, 0.2, -0.2]],  # 上
    [[-0.2, 0.2, 0.2], [-0.2, -0.2, 0.2], [-0.2, -0.2, -0.2], [-0.2, 0.2, -0.2]]  # 下
])

colours = np.array([
    [0, 1, 1], [1, 0.5, 0.5],
    [1, 1, 0], [0.1, 0.1, 1],
    [0, 1, 0.2], [0.6, 0.6, 0.6]
])
IS_PERSPECTIVE = True  # 透视投影
VIEW = np.array([-0.5, 0.5, -0.5, 0.5, 0.5, 20.0])  # 视景体的left/right/bottom/top/near/far六个面
RIGHT_IS_DOWNED = False
CameraPos = np.array([0.0, 0.0, 1])
CameraFront = np.array([0, 0, 0])
CameraUp = np.array([0, 1, 0])
SCALE_K = np.array([1.0, 1.0, 1.0])
yaw = 0
pitch = 0
MOUSE_X, MOUSE_Y = 0, 0
WIN_W = 480
WIN_H = 480
POINTS = np.array([[0, 0, 0]])


def init():
    glClearColor(0.0, 0.0, 0.0, 1.0)  # 设置画布背景色。注意:这里必须是4个参数
    glEnable(GL_DEPTH_TEST)  # 开启深度测试,实现遮挡关系
    glDepthFunc(GL_LEQUAL) # 设置深度测试函数(GL_LEQUAL只是选项之一)
    global nurb
    nurb = gluNewNurbsRenderer()
    global samplingTolerance
    gluNurbsProperty(nurb, GLU_SAMPLING_TOLERANCE, samplingTolerance)

nurb=None
samplingTolerance=1.0

def show():
    global IS_PERSPECTIVE, VIEW
    global CameraPos, CameraFront, CameraUp
    global SCALE_K
    global WIN_W, WIN_H
    global vertices2
    global POINTS
    global KNOTS
    global nurb

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    # 设置投影(透视投影)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()

    if IS_PERSPECTIVE:
        glFrustum(VIEW[0], VIEW[1], VIEW[2], VIEW[3], VIEW[4], VIEW[5])

    # 设置模型视图
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    # 几何变换
    glScale(SCALE_K[0], SCALE_K[1], SCALE_K[2])

    # 视点
    gluLookAt(
        CameraPos[0], CameraPos[1], CameraPos[2],
        CameraFront[0], CameraFront[1], CameraFront[2],
        CameraUp[0], CameraUp[1], CameraUp[2]
    )

    glViewport(0, 0, WIN_W, WIN_H)

    n = POINTS.shape[0]

    for i in range(n):
        glPointSize(3.0)
        glColor3f(1.0, 0.0, 0.0)
        glBegin(GL_POINTS)
        glVertex3f(POINTS[i, 0], POINTS[i, 1], POINTS[i, 2])
        glEnd()

    #连续线段连接
    glLineWidth(0.2)
    glColor3f(1.0, 1.0, 1.0)
    glBegin(GL_LINE_STRIP)
    for i in range(n):
        glVertex3f(POINTS[i, 0], POINTS[i, 1], POINTS[i, 2])
    glEnd()

    if n > 2:
        degree = 3
        knotNum = len(POINTS) + degree
        KNOTS =  [float(i)/(knotNum-1) for i in range(knotNum)]
        gluBeginCurve(nurb)
        glColor3f(0, 0, 0)
        glLineWidth(7.0)
        gluNurbsCurve(nurb, KNOTS, POINTS, GL_MAP1_VERTEX_3)
        gluEndCurve(nurb)

    for i in range(vertices2.shape[0]):
        glBegin(GL_QUADS)
        points = vertices2[i, :]
        color = colours[i, :]
        for point in points:
            glColor3f(color[0], color[1], color[2])
            glVertex3f(point[0], point[1], point[2])
        glEnd()

    glutSwapBuffers()


def Mouse_click(button, state, x, y):
    global RIGHT_IS_DOWNED
    global MOUSE_X, MOUSE_Y
    global SCALE_K
    global WIN_W
    global WIN_H
    global POINTS

    MOUSE_X = x
    MOUSE_Y = y

    if button == GLUT_LEFT_BUTTON and state == 0:

        modelview_mat = OpenGL.GL.glGetDoublev(OpenGL.GL.GL_MODELVIEW_MATRIX)
        projection_mat = OpenGL.GL.glGetDoublev(OpenGL.GL.GL_PROJECTION_MATRIX)
        viewport = OpenGL.GL.glGetIntegerv(OpenGL.GL.GL_VIEWPORT)

        z = OpenGL.GL.glReadPixels(MOUSE_X, viewport[3] - MOUSE_Y, 1, 1, OpenGL.GL.GL_DEPTH_COMPONENT, OpenGL.GL.GL_FLOAT)

        ret = gluUnProject(MOUSE_X, viewport[3] - MOUSE_Y, z, modelview_mat, projection_mat, viewport)
        ret_paint = [[ret[0], ret[1], ret[2]]]

        if abs(ret[0]) < 0.3 and abs(ret[1]) < 0.3 and abs(ret[2]) < 0.3: #方块之外的点不描出
            POINTS = np.append(POINTS, ret_paint, axis=0)

    if button == GLUT_RIGHT_BUTTON:
        RIGHT_IS_DOWNED = state == GLUT_DOWN


def Mouse_motion(x, y):
    global RIGHT_IS_DOWNED
    global MOUSE_X, MOUSE_Y
    global yaw, pitch
    global CameraPos

    if RIGHT_IS_DOWNED:
        dx = x - MOUSE_X
        dy = y - MOUSE_Y
        MOUSE_X = x
        MOUSE_Y = y

        sensitivity = 0.4
        dx = dx * sensitivity
        dy = dy * sensitivity

        yaw = yaw + dx
        pitch = pitch + dy

        if pitch > 89:
            pitch = 89
        if pitch < -89:
            pitch = -89

        CameraPos[0] = np.cos(np.radians(yaw)) * np.cos(np.radians(pitch))
        CameraPos[1] = np.sin(np.radians(pitch))
        CameraPos[2] = np.sin(np.radians(yaw)) * np.cos(np.radians(pitch))

        glutPostRedisplay()


if __name__ == '__main__':
    glutInit()
    displayMode = GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH
    glutInitDisplayMode(displayMode)
    glutInitWindowSize(WIN_W, WIN_H)
    glutInitWindowPosition(300, 200)
    glutCreateWindow("CUBE")

    init()
    glutDisplayFunc(show)
    glutMouseFunc(Mouse_click)
    glutMotionFunc(Mouse_motion)
    glutMainLoop()
上一篇:10


下一篇:SQLAlchemy异常捕捉