开场白
最近在学习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]
网友评论