CG从零开始] 5. 搞清 MVP 矩阵理论 + 实践


1. 加载 fbx 模型#

在第 3 篇中介绍了如何安装 pyassimp,这回我们来用一下,我们先定义一个简单的 Mesh 和 SubMesh 类保存加载的模型的数据,然后再定义一个模型加载类,用来加载数据,代码如下所示,比较简单。

# mesh.py
class SubMesh:
    def __init__(self, indices) -> None:
        self.indices = indices

class Mesh:
    def __init__(self) -> None:
        self.vertices = []
        self.normals = []
        self.subMeshes = []

# model_importer.py
# pyassimp 4.1.4 has some problem will lead to randomly crash, use 4.1.3 to fix
# should set link path to find the dylib
import pyassimp
import numpy as np
from .mesh import Mesh, SubMesh

class ModelImporter:
    def __init__(self) -> None:
        pass

    def load_mesh(self, path: str):
        scene = pyassimp.load(path)
        mmeshes = []
        for mesh in scene.meshes:
            mmesh = Mesh()
            mmesh.vertices = np.reshape(np.copy(mesh.vertices), (1,-1)).squeeze(0)
            print(mmesh.vertices)
            mmesh.normals = np.reshape(np.copy(mesh.normals),(1,-1)).squeeze(0)
            mmesh.subMeshes = []
            mmesh.subMeshes.append(SubMesh(np.reshape(np.copy(mesh.faces), (1,-1)).squeeze(0)))
            mmeshes.append(mmesh)
        return mmeshes

2. 定义 Transform#

Transform 用来描述物体的位置、旋转、缩放信息,可以说是比较基础的,所以必不可少,详细的解释在代码的注释里。

import numpy as np
from scipy.spatial.transform import Rotation as R

class Transform:

    def __init__(self) -> None:
        # 为了简单,目前我用欧拉角来存储旋转信息
        self._eulerAngle = [0,0,0]
        self._pos = [0,0,0]
        self._scale = [1,1,1]

    # -- 都是常规的 get set,这里略去
    # ......

    # 这就是我们所需要的 model 矩阵,注意这里没有考虑的物体的层级
    # 关系,默认物体都是在最顶层,所以 local 和 world 坐标是一样
    # 后续的文章会把层级关系考虑进来
    def localMatrix(self):
        # 按照 TRS 的构建方式
        # 位移矩阵 * 旋转矩阵 * 缩放矩阵
        mat = np.identity(4)
        # 对角线是缩放
        for i in range(3):
            mat[i,i] = self._scale[i]
        rot = np.identity(4)
        rot[:3,:3] = R.from_euler("xyz", self._eulerAngle, degrees = True).as_matrix()
        mat = rot @ mat
        for i in range(3):
            mat[i,3] = self._pos[i]
        return mat

    # 将世界坐标变换到当前物体的坐标系下,注意这里也是没有考虑层级关系的
    # 这个可以用来获得从世界坐标系到相机坐标系的转换。
    def get_to_Local(self):
        mat = self.localMatrix()
        ori = np.identity(4)
        ori[:3,:3] = mat[:3,:3]
        ori = np.transpose(ori)
        pos = np.identity(4)
        pos[0:3,3] = -mat[0:3,3]
        return ori @ pos
        

3.定义相机#

最后我们定义相机,目前相机的 Transform 信息可以用来定义 View 矩阵,其他例如 fov 等主要用来定义投影矩阵。

from math import cos, sin
import math
import numpy as np

class Camera:
    def __init__(self) -> None:
        self._fov = 60
        self._near = 0.3
        self._far = 1000
        self._aspect = 5 / 4

    # -- 都是常规的 get set,这里略去
    # ......
    
    # 完全参照投影矩阵的公式定义
    def getProjectionMatrix(self):
        r = math.radians(self._fov / 2)
        cotangent = cos(r) / sin(r)
        deltaZ = self._near - self._far
        projection = np.zeros((4,4))
        projection[0,0] = cotangent / self._aspect
        projection[1,1] = cotangent
        projection[2,2] = (self._near + self._far) / deltaZ
        projection[2,3] = 2 * self._near * self._far / deltaZ
        projection[3,2] = -1
        return projection

4. 构建 MVP 矩阵#

完成了上述的步骤后,我们就可以构建 MVP 矩阵了。

...
# 定义物体的 transform
trans = transform.Transform()
trans.localPosition = [0,0,0]
trans.localScale = [0.005,0.005,0.005]
trans.localEulerAngle = [0,10,0]
# 获取 model 矩阵
model = trans.localMatrix()

# 定义相机的 transform
viewTrans = transform.Transform()
viewTrans.localPosition = [0,2,2]
viewTrans.localEulerAngle = [-40,0,0]
# 获取 view 矩阵
view = viewTrans.get_to_Local()

# 定义相机并获得 projection 矩阵
cam = Camera()
proj = cam.getProjectionMatrix()
# 构建 MVP 矩阵
mvp = np.transpose(proj @ view @ model)
# 作为 uniform 传入 shader 中,然后 shader 中将顶点位置乘上mvp矩阵。
mshader.set_mat4("u_mvp", mvp)
...

然后加载模型,构建一下顶点数组和索引数组,我给每个顶点额外添加了随机的颜色

importer = ModelImporter()

meshes = importer.load_mesh("box.fbx")
vert = []
for i in range(len(meshes[0].vertices)):
    if i % 3 == 0:
        vert.extend([meshes[0].vertices[i],meshes[0].vertices[i + 1],meshes[0].vertices[i + 2]])
        vert.extend([meshes[0].normals[i],meshes[0].normals[i + 1],meshes[0].normals[i + 2]])
        vert.extend([random.random(),random.random(),random.random()])
inde = meshes[0].subMeshes[0].indices
# 开一下深度测试
gl.glEnable(gl.GL_DEPTH_TEST)

本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;

2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;

3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;

4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;

5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/294499.html

(0)
上一篇 2022年12月3日
下一篇 2022年12月3日

相关推荐

发表回复

登录后才能评论