本篇介绍一下摄像机的概念与实现,并且进一步将着色器也封装一下,(着色器与摄像机都存放在 util
中),方便后续调用。
OpenGL 入门教程(4) 封装util(摄像机/着色器)
引言
上一节已经完成了顶点变换与坐标系的初步讲解,并按流程渲染了 MVP 变换的长方体。
然而具体的视角变换和投影还没详细讲解,是因为这一节会同时讲解并封装这些流程,方便之后的调用。
同时我们也会提供对着色器的封装,方便之后的调用。
摄像机
OPENGL中,摄像机其实不是一个真实存在的概念。实际讨论的是观察矩阵。
摄像机有两个关键属性:
- 摄像机位置(Camera Position):摄像机在世界空间中的位置,通常用一个向量表示。
- 摄像机方向(Camera Direction):摄像机指向的方向,通常通过计算摄像机位置向量与目标点之间的差值得到。
空间中一个平面可以由一个点和平面法向量唯一确定。 摄像机对应的投影平面也是如此。
有这两个属性,就可以确定投影平面了,也即确定观察矩阵。
通常把摄像机,从屏幕指向屏幕外的方向定为z轴正方向,对x,y轴有了新的定义:
- 右轴:摄像机空间的x轴正方向,通常通过上向量和方向向量的叉乘得到。
- 上轴:摄像机空间的y轴正方向,通常通过方向向量和右轴的叉乘得到。
Look At
我们定义右轴上轴方向向量,实际是为了直接求出看指定目标的观察矩阵。
\[LookAt = \begin{bmatrix} \color{red}{R_x} & \color{red}{R_y} & \color{red}{R_z} & 0 \\ \color{green}{U_x} & \color{green}{U_y} & \color{green}{U_z} & 0 \\ \color{blue}{D_x} & \color{blue}{D_y} & \color{blue}{D_z} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} 1 & 0 & 0 & -\color{purple}{P_x} \\ 0 & 1 & 0 & -\color{purple}{P_y} \\ 0 & 0 & 1 & -\color{purple}{P_z} \\ 0 & 0 & 0 & 1 \end{bmatrix}\]其中R是右向量,U是上向量,D是方向向量,P是摄像机位置向量。
矩阵总结
把之前的变换矩阵都列出来如下:
1 |
|
自由摄像机
指定一个目标的相机可能很简便,但也很僵硬。
我们实际会封装一个自由移动旋转是摄像机。
为了代码表示的整体性,在代码中以注释的方式做解释。
1 |
|
同时修改之前的init_window函数,在创建窗口时返回摄像机:
1 |
|
着色器
之前讲了着色器的用法,现在可以直接封装一个着色器,在初始化就完成编译。
同样的,为了代码表示的整体性,在代码中以注释的方式做解释。
1 |
|
实际中顶点,着色器会写在单独的文件里,不过这里为了教程的直观性,就硬编码到代码里了。
保存这些代码到一个文件,命名为 pyopengl_util.py
, 上一个教程中的代码改写成以下代码:
注意现在是按照帧率 fps 更新画面。 ``` python from OpenGL.GL import * from OpenGL.GLUT import * from OpenGL.GLU import * import numpy as np
from pyopengl_util import *
vertices = (ctypes.c_float * (36*6))( -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, -0.5, -0.5, -0.5, 0.0, 0.0, -1.0,
1 |
|
顶点着色器
vertexShaderSource = “”” #version 330 core layout (location = 0) in vec3 aPos; uniform mat4 model; uniform mat4 view; uniform mat4 projection; out vec3 aColor;
void main() { gl_Position = projection * view * model * vec4(aPos, 1.0); aColor = vec3(aPos.x, aPos.y, aPos.z)/(aPos.x + aPos.y + aPos.z); } “””
片段着色器
fragmentShaderSource = “”” #version 330 core in vec3 aColor; out vec4 FragColor;
void main() { FragColor = vec4(aColor, 1.0); } “””
if name == “main”: camera = init_window(1024, 768, b”rectangle”)
1 |
|
1 |
|
至此,我们完成的第一次的封装,之后可以用 util 更方便的渲染。