OpenGL-矩阵相乘之为什么要先设置观察者Camera

2020-07-11  本文已影响0人  过气的程序员DZ

开场白


最近在学习OpenGL的知识,写了一些Demo。每次都是先将观察者Camera矩阵入栈,如果改变矩阵入栈顺序就得不到想要的效果。所以这篇文章来聊聊这方面的问题。

文中使用本人另一篇文章OpenGL-MVP模式的六种图元绘制中的demo为例

精简后的代码


本文只研究矩阵的入栈时机,所以多余代码就省略了:

......
//模型视图矩阵
GLMatrixStack modelViewMatrix;
//投影矩阵
GLMatrixStack projectionMatrix;
//观察者
GLFrame cameraFrame;
//物体
GLFrame objectFrame;
//视图椎体
GLFrustum viewFrustum;
...
//几何变化管道
GLGeometryTransform transformPipeline;
......

void ChangeSize(int w,int h)
{
    ......
    //视图椎体变量设置观察者位置
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
    //从视图椎体中加载投影矩阵
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    //模型视图矩阵设置为单元矩阵
    modelViewMatrix.LoadIdentity();
}

void SetupRC()
{
    ......
    //设置变化管线的矩阵堆栈
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    //设置camera位置
    cameraFrame.MoveForward(-15.0f);
    ......
}
......

void RenderScene(void) {
    ......
    //入栈
    modelViewMatrix.PushMatrix();
    
    //将camera frame转换成矩阵,并入栈
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.MultMatrix(mCamera);
    
    //将object frame转换成矩阵,并入栈
    M3DMatrix44f mObjectFrame;
    objectFrame.GetMatrix(mObjectFrame);
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    //设置着色器
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    ......
}

1.ChangeSize回调方法

projectionMatrix栈

投影矩阵栈projectionMatrix是GLMatrixStack实例,通过类名就可以了解到它就是一个栈。我们来看看GLMatrixStack的构造方法:


GLMatrixStack构造方法

这个可以看到栈中会加入一个单元矩阵,作为栈顶元素。

projectionMatrix会调用LoadMatrix方法,参数是viewFrustum.GetProjectionMatrix()

projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());

viewFrustum是GLFrustm实例,Frustum是椎体的意思,可以理解为以观察者为顶点,到屏幕形成椎体几何图形。
在viewFrustum调用SetPerspective方法中:

viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);

已经初始化好了Objection矩阵:



所以viewFrustum.GetProjectionMatrix作为参数传入到projectionMatrix.LoadMatrix()方法中,就可以将projection的矩阵传入到projectionMatrix栈中。
LoadMartix()方法就是一个入栈操作:


modelViewMatrix栈

modelViewMatrix也是GLMatrixStack实例,

    modelViewMatrix.LoadIdentity();

其实这句代码可以不写,这句代码的意思就是将栈顶元素设置为单元矩阵,上面我们了解到,在构造方法中已经做过这一步了,所以写不写都可以。


2. SetupRC回调方法

    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);

将modelViewMatrix和projectionMatrix放入到管线中。
设置cameraFrame的位置

    cameraFrame.MoveForward(-15.0f);

3. SetupRC回调方法

    modelViewMatrix.PushMatrix();

modelViewMatrix调用PushMatrix()入栈方法,其实就是将栈顶元素再次入栈,可以看看实现:


    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.MultMatrix(mCamera);

    M3DMatrix44f mObjectFrame;
    objectFrame.GetMatrix(mObjectFrame);
    modelViewMatrix.MultMatrix(mObjectFrame);

先将camera转换成矩阵,加入modelViewMatrix栈中,再讲objcet转换成矩阵加入到modelViewMatrix栈中,我们来看看MutlMatrix()方法的实现:


是将栈顶元素取出用赋值给mTemp,然后吧mTemp与参数mMatrix的城际后存入到栈顶。
通过上面的代码,就可以理解为[单元矩阵] * [camera矩阵] * [object矩阵],其实这个过程就是model矩阵乘以object矩阵的到modelView矩阵的过程,camera就是观察者矩阵,其实就是我们平时说的View矩阵。object矩阵就是物体矩阵,也就是model矩阵。得到公式1:modeView_M = view_M * model_M

再接下来我们看看设置着色器中的参数第二个参数:

shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);

GetModelViewProjectionMatrix()方法的实现:


这个方法就是获取modelViewProjection矩阵,而这个矩阵是通过projection矩阵与modeView矩阵的乘积,得到公式2:modelViewProjection_M = projection_M * modelView_M

通过上面公式可以得到矩阵的乘积顺序:
mvp_M = projection_M * view_M * model_M
注意:矩阵相乘是没有交换律的,所以这个顺序是不可以变的。
这也就是为什么我们开发的时候要先设置观察者camera(view矩阵),这样可以先求出modelView矩阵,进而求出mvp矩阵。

4.OpenGL中的矩阵是列优先


这张图在我之前的文章里用到过,我们再来仔细看看:
物体在本地坐标系中,通过model矩阵变换到世界坐标系,在通过观察者矩阵变换到观察者坐标系,在通过投影矩阵,剪裁等操作,最后展示到屏幕坐标系中。这个过程可以用下面公式简单标示一下:(_v代表点,_m代表矩阵)
clip_v = local_v * model_m * view_m * projection_m

有没有发现这个公式和我们上面推到的公式是相反的。

其实是因为OpenGL中矩阵使用的是‘列优先顺序’,如图:



而在我们学习中都是以‘行优先的顺序’,所以整个过程是正好相反的。


矩阵列优先和行优先

我们平时说矩阵都是行优先,比如说一个矩阵A是[2x3]矩阵:

//行优先
1 2 3
4 5 6 

//列优先
1 4
2 5
3 6

我们再来一个矩阵B[3x1]:

//行优先
7 8 9

//列优先
7
8
9

在行优先的情况下,A * B是可以相乘的,[2x3] * [3x1]= [2x1],
而如果是列优先,B * A才能进行相乘,[1x3] * [3x2] = [1x2]

上一篇 下一篇

猜你喜欢

热点阅读