PyOpenGL代码实战(一):创建窗口

一、前言

网络上有很多关于OpenGL的教程,但绝大多数都是C或C++的代码。本文章旨在教学如何在Python中编写OpenGL的代码。本文主要参考LearnOpenGL网站的教程,以实现一个Python版本的OpenGL代码框架。

二、前置知识

1、数学

学习PyOpenGL,你可能需要一些基础的数学知识,特别是线性代数几何学的相关知识。不用担心,你并不需要精通这些知识,只需要了解向量、矩阵、三角函数等基本概念及其运算即可。

2、计算机图形学

阅读本教程,你可能需要一定的计算机图形学基础。不过你没有学过也没有关系,在讲到相关内容时,我也会为大家简要讲解一下相应的基础理论。

3、 Python

本教程使用Python+PyOpenGL编写。你需要掌握如何编写Python代码。

三、PyOpenGL的配置

使用pip安装PyOpenGL。

pip install PyOpenGL PyOpenGL_accelerate

实际上,上述命令只是下载了 Python 与 OpenGL API的绑定,OpenGL 本身已经内置在硬件中,无需下载。

配置GLFW。关于GLFW的下载、安装以及配置,可以阅读这篇文章。注意一定要将GLFW添加到系统变量中,否则后续代码将无法运行。

GLFW 为 OpenGL 的底层 API 进行了一定的封装,提供了创建并管理窗口和 OpenGL 上下文的功能,同时还提供了处理手柄、键盘、鼠标输入的功能。

下载并配置完成后,在python中安装GLFW:

pip install glfw

在Python中导入PyOpenGL和GLFW:

from OpenGL.GL import *	# 导入PyOpenGL
import glfw				# 导入GLFW

四、创建窗口

首先,我们需要使用GLFW来创建窗口

# 初始化GLFW
if not glfw.init():
    raise RuntimeError("GLFW初始化失败!")
# 创建窗口
width, height, title = 1920, 1080, "Test"	# 窗口宽度、高度、标题
window = glfw.create_window(width, height, title, None, None)	# 暂时忽略后面两个参数
# 显示窗口
glfw.make_context_current(window)	# 通知 GLFW 将我们窗口的上下文设置为当前线程的主上下文
glfw.set_window_size_limits(window, width, height, width, height) # 使窗口大小不可修改
glViewport(0, 0, width, height) # 设置视口
glEnable(GL_CULL_FACE)  		# 开启背面剔除
glEnable(GL_DEPTH_TEST)  		# 开启深度测试

视口与 glViewport

视口是 OpenGL 的渲染窗口,前面glfw.create_window用于设置窗口的大小,而 glViewport 则定义了视口的大小。视口大小一般和窗口大小相同,但它们也可以不相等。如果视口大小小于窗口大小,多余的窗口位置将会显示为黑色。

glViewPort的前两个参数定义了视口左下角的屏幕坐标(屏幕坐标以窗口的左下角为原点),后两个参数视口的宽度和高度。

背面剔除与深度测试

背面剔除是指将用户看不到的面剔除掉,不再渲染这些资源以提高效率。深度测试是指通过深度缓冲来判断不同物体间的遮挡关系。这些内容会在之后的章节中详细介绍。

如果此时我们运行这段代码,你会发现窗口一闪而过,只出现了一瞬间就立刻消失。
在这里插入图片描述

这是因为在这段代码运行完成后,python程序终止,窗口也就消失了。为了让窗口一直显示,我们还需要创建渲染循环

在上述代码之后加上如下代码:

bgColor = (0.0, 0.0, 0.0, 1.0)
while not glfw.window_should_close(window):
    # 清空缓冲区
    glClearColor(*bgColor)	# 将窗口全部涂成背景色(注意前面的解包符号)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)	# 清除颜色缓冲和深度缓冲
    
    # 在此处可以绘制物体
    # ...
    
    # 交换缓冲区
    glfw.swap_buffers(window)
    # 提交事件
    glfw.poll_events()
    # 检测窗口是否关闭
    if glfw.get_key(window, glfw.KEY_ESCAPE) == glfw.PRESS:
        glfw.set_window_should_close(window, True)
# 结束渲染循环后销毁窗口并终止运行
glfw.destroy_window(window)
glfw.terminate()

我们介绍一下上述代码中出现的一些函数:

glfw.window_should_close():用于判断窗口是否应该被关闭,如果用户点击了窗口右上角的关闭按钮,该函数会返回True。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT):清除颜色缓冲和深度缓冲,具体功能会在后续章节中介绍。

glfw.swap_buffers(window):交换缓冲区。这条代码与双缓冲机制有关。

何为双缓冲机制?绘制物体需要时间,因此当我们在视口中绘制物体时,我们可以看到绘制过程,这将会造成画面的闪烁。为了解决这个问题,我们可以定义两个窗口,一个窗口显示给用户,另一个窗口则隐藏起来。渲染时,先在隐藏起来的窗口中绘制,等这一帧完全绘制完成后,交换两个窗口,将原来显示的窗口隐藏,原来隐藏的窗口则显示。这样就不会看到画面的闪烁效果了。

glfw.poll_events():提交事件。用于处理键盘、鼠标事件。在后续章节中详细介绍。

if glfw.get_key(window, glfw.KEY_ESCAPE) == glfw.PRESS:
	glfw.set_window_should_close(window, True)

glfw.get_key(window, key)用于判断键盘上某个按键当前的状态。其返回有两种可能:glfw.PRESSglfw.RELEASE,分别表示按键按下和释放。glfw.set_window_should_close(window, True or False)用于设置窗口是否应该被关闭。所以这段代码的功能是用户按下ESC按键后关闭窗口。

注意,调用glfw.set_window_should_close(window, True)后,窗口并不会立即关闭。只是之后再调用glfw.window_should_close()函数时会返回 True,此时跳出循环,运行glfw.destroy_window(window)时关闭窗口。

最后,我们得到了创建窗口的完整代码:

# 初始化GLFW
if not glfw.init():
    raise RuntimeError("GLFW初始化失败!")
# 创建窗口
width, height, title = 1920, 1080, "Test"	# 窗口宽度、高度、标题
window = glfw.create_window(width, height, title, None, None)	# 暂时忽略后面两个参数
# 显示窗口
glfw.make_context_current(window)	# 通知 GLFW 将我们窗口的上下文设置为当前线程的主上下文
glfw.set_window_size_limits(window, width, height, width, height) # 使窗口大小不可修改
glViewport(0, 0, width, height) # 设置视口
glEnable(GL_CULL_FACE)  		# 开启背面剔除
glEnable(GL_DEPTH_TEST)  		# 开启深度测试

bgColor = (0.0, 0.0, 0.0, 1.0)
while not glfw.window_should_close(window):
    # 清空缓冲区
    glClearColor(*bgColor)	# 将窗口全部涂成背景色
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)	# 清除颜色缓冲和深度缓冲
    
    # 在此处可以绘制物体
    # ...
    
    # 交换缓冲区
    glfw.swap_buffers(window)
    # 提交事件
    glfw.poll_events()
    # 检测窗口是否关闭
    if glfw.get_key(window, glfw.KEY_ESCAPE) == glfw.PRESS:
        glfw.set_window_should_close(window, True)
# 结束渲染循环后销毁窗口并终止运行
glfw.destroy_window(window)
glfw.terminate()

运行结果如下:
在这里插入图片描述

当我们点击右上角或者按下ESC时,窗口会关闭,程序会结束运行。

五、封装

我们已经成功完成了创建窗口的代码。但这些代码都写在一起,不利于后续的扩展。因此,我们可以定义窗口类,封装代码以提高可扩展性。

class Window:
    def __init__(self, width, height, title, bgColor=(0.0, 0.0, 0.0, 1.0)):
        # 初始化GLFW
        if not glfw.init():
            raise RuntimeError("GLFW初始化失败!")
        # 创建窗口
        self.width, self.height, self.title, self.bgColor = width, height, title, bgColor
        self.window = glfw.create_window(width, height, title, None, None)
        # 显示窗口
        self.show()

    def show(self):
        glfw.make_context_current(self.window)
        glfw.set_window_size_limits(self.window, self.width, self.height, self.width, self.height)
        glViewport(0, 0, self.width, self.height)
        glEnable(GL_CULL_FACE)
        glEnable(GL_DEPTH_TEST)

    def loop(self):
        while not glfw.window_should_close(self.window):
            glClearColor(*self.bgColor)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

            # 在此处可以绘制物体
            # ...

            glfw.swap_buffers(self.window)
            glfw.poll_events()
            if glfw.get_key(self.window, glfw.KEY_ESCAPE) == glfw.PRESS:
                glfw.set_window_should_close(self.window, True)

        glfw.destroy_window(self.window)
        glfw.terminate()


if __name__ == '__main__':
    w = Window(1920, 1080, "Test")
    w.loop()

这段代码和之前的创建窗口的代码并无不同,这里不再介绍。

六、结语

本文介绍了如何使用PyOpenGL创建窗口。在下一节中,我们将学习如何在窗口中绘制图形。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要导入PyOpenGLPyOpenGL_accelerate模块。然后,我们需要创建一个着色器程序对象,并编译着色器代码。 以下是一个简单的示例代码,可以用于创建着色器并绘制一个点: ```python from OpenGL.GL import * from OpenGL.GL.shaders import compileProgram, compileShader import glfw import numpy as np # 顶点着色器代码 vertex_shader = """ #version 330 layout (location = 0) in vec3 position; void main() { gl_Position = vec4(position, 1.0); } """ # 片段着色器代码 fragment_shader = """ #version 330 out vec4 frag_color; void main() { frag_color = vec4(1.0, 0.0, 0.0, 1.0); } """ # 初始化glfw if not glfw.init(): raise Exception("glfw init failed") # 创建窗口 window = glfw.create_window(640, 480, "My OpenGL Window", None, None) if not window: glfw.terminate() raise Exception("glfw window creation failed") # 将窗口设置为当前上下文 glfw.make_context_current(window) # 编译着色器代码 shader = compileProgram(compileShader(vertex_shader, GL_VERTEX_SHADER), compileShader(fragment_shader, GL_FRAGMENT_SHADER)) # 创建顶点数组 vertices = np.array([0.0, 0.0, 0.0], dtype=np.float32) # 创建缓冲区对象 vbo = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, vbo) glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW) # 获取顶点位置属性的位置 position = glGetAttribLocation(shader, "position") # 启用顶点属性数组 glEnableVertexAttribArray(position) # 设置顶点属性指针 glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 0, None) # 渲染循环 while not glfw.window_should_close(window): # 清空颜色缓冲区 glClear(GL_COLOR_BUFFER_BIT) # 使用着色器程序对象 glUseProgram(shader) # 绘制点 glDrawArrays(GL_POINTS, 0, 1) # 交换缓冲区和轮询事件 glfw.swap_buffers(window) glfw.poll_events() # 清理 glDeleteProgram(shader) glDeleteBuffers(1, [vbo]) glfw.terminate() ``` 这个例子中,我们使用了一个非常简单的顶点着色器和片段着色器代码来绘制一个只有一个顶点的点。我们使用了一个顶点缓冲区对象来存储顶点数据,并使用glVertexAttribPointer()函数设置顶点属性指针。最后,我们使用glDrawArrays()函数来绘制点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值