[CG从零开始] 4. pyopengl 绘制一个正方形

在做了 1~3 的基础工作后,我们的开发环境基本 OK 了,我们可以开始尝试利用 pyopengl 来进行绘制了 。本文主要有三个部分

  1. 利用 glfw 封装窗口类 , 并打开窗口;
  2. 封装 shader 类 , 进行编译、链接、使用;
  3. 封装 VAO、VBO、EBO
  4. 完成主函数进行绘制
完整的代码在仓库 (tag: v0.1) https://github.com/MangoWAY/CGLearner/tree/v0.1
1. 利用 glfw 封装窗口类为了显示我们绘制的内容,打开窗口是必不可少的操作 , 因此我们来简单封装一个窗口类,便于我们后续的学习、调用 。我们设置 opengl 的版本,向前兼容和配置(这俩在 macOS 必须配置),这些其实可以不用太关心,并不影响我们后续的学习进程,感兴趣可以看一下 glfw 的官方关于窗口的文档 。
# window_helper.pyimport glfw, logging, sysfrom OpenGL import GL as gllog = logging.getLogger(__name__)class Window:class Config:def __init__(self,gl_version = (3,3), size = (500,400), title = "cglearn", bgcolor = (0,0.4,0)) -> None:self.gl_version = gl_versionself.size = sizeself.title = titleself.bgcolor = bgcolordef __init__(self,config: Config) -> None:self.native_window = Noneself.config = configself.init(config)def set_background(self,r,g,b):gl.glClearColor(r, g, b, 0)def init(self, config: Config):if not glfw.init():log.error('failed to initialize GLFW')sys.exit(1)log.debug('requiring modern OpenGL without any legacy features')glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, config.gl_version[0])glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, config.gl_version[1])glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)log.debug('opening window')self.native_window = glfw.create_window(config.size[0], config.size[1], config.title, None, None)if not self.native_window:log.error('failed to open GLFW window.')sys.exit(2)glfw.make_context_current(self.native_window)log.debug('set background to dark blue')gl.glClearColor(0, config.bgcolor[0], config.bgcolor[1],config.bgcolor[2])2. 封装 shader 类用 OpenGL 完成一次简单的绘制有一些基本的操作 , 
  1. 需要编写 shader,然后创建 shader 程序,进行编译、链接、激活;
  2. 需要创建 VAO,VBO,EBO(可?。?来管理数据,传递给 shader 进行计算;
  3. 在循环中调用绘制指令来进行绘制;
这一小节我们来封装一个 shader 类 , 来完成 shader 的创建、编译、链接等操作 。
创建一个 shader 分几个步骤:
  • 创建 VERTEX 和 FRAGMENT shader;
  • 传送 shader 的代码 (string);
  • 编译 VERTEX 和 FRAGMENT shader;
  • 创建 program (shader 程序);
  • 将 VERTEX 和 FRAGMENT shader 附加到 program 上;
  • 链接 program;
在渲染前 , 还要激活 shader 程序
# shader.pyimport sysfrom OpenGL import GL as glfrom enum import Enumimport logginglog = logging.getLogger(__name__)class ShaderType(Enum):VERTEX = 0FRAGMENT = 1class Shader:def __init__(self) -> None:self.vertex_shader = ""self.fragment_shader = ""self.program_id = -1self.shader_ids = []def load_shader_source_from_string(self, shader_type: ShaderType, source: str):if shader_type == ShaderType.VERTEX:self.vertex_shader = sourceelif shader_type == ShaderType.FRAGMENT:self.fragment_shader = sourceelse:logging.error("wrong shader type !")# 从文件读取 shader , 按照普通的文本文件读取即可 。def load_shader_source_from_path(self, shader_type: ShaderType, path: str):with open(path,"r") as f:source = f.read()self.load_shader_source_from_string(shader_type, source)# 这个主要是用来打印编译时候出现的错误信息,不是关键,这里先略去def log_shader_info(self, shader_id):...# 这个主要是用来打印链接时候出现的错误信息,不是关键,这里先略去def log_program_info(self,program_id):...def create_program(self):# 创建 shader 程序self.program_id = gl.glCreateProgram()for shader_type in [gl.GL_VERTEX_SHADER, gl.GL_FRAGMENT_SHADER]:# 创建 VERTEX 和 FRAGMENT shadershader_id = gl.glCreateShader(shader_type)# 传送 shader 代码if shader_type == gl.GL_VERTEX_SHADER:gl.glShaderSource(shader_id, self.vertex_shader)else:gl.glShaderSource(shader_id, self.fragment_shader)log.debug(f'compiling the {shader_type} shader')# 编译 VERTEX 和 FRAGMENT shadergl.glCompileShader(shader_id)self.log_shader_info(shader_id)# 将 VERTEX 和 FRAGMENT shader 附加到 program 上gl.glAttachShader(self.program_id, shader_id)self.shader_ids.append(shader_id)log.debug('linking shader program')# 链接 shader 程序gl.glLinkProgram(self.program_id)self.log_program_info(self.program_id)log.debug('installing shader program into rendering state')# 激活 shader 程序def use_program(self):gl.glUseProgram(self.program_id)# 删除 shader 程序def clean_program(self):log.debug('cleaning up shader program')for shader_id in self.shader_ids:gl.glDetachShader(self.program_id, shader_id)gl.glDeleteShader(shader_id)gl.glUseProgram(0)gl.glDeleteProgram(self.program_id)

推荐阅读