在上一节 《Opengl ES之矩阵变换(上)》 中,我们通过矩阵变换实现一个一些形变的效果。
如果细心的童鞋们可能会发现,我们的运行结果渲染的图片宽高明显是有些变形了,特别是在手机屏幕旋转为横屏之后,变形更加的明显, 那么如果希望无论是横屏还是竖屏都希望渲染的画面可以参照宽高等比拉伸显示该如何处理呢?同时这个需求也是播放器渲染视频画面时的一个基本需求, 通过矩阵的正交投影就能够很好低解决这个问题。
OpenGL中的坐标系及矩阵变换过程
下面这张图展示了OpenGL ES 中的坐标系及矩阵变换过程:
通过上面这张图可以看到一个物体最终在屏幕上显示出来,需要经过5个坐标系统之间的变换,而这些变换都是依靠矩阵变换完成的。
下面简单介绍以下这五个坐标系统:
- 局部空间(Local Space,或者称为物体空间(Object Space))
局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。假如我们有一张图,设定这张图的中心为坐标原点(0,0),左上角的坐标点为(-1,-1)那么这个物体的局部空间坐标系统。
- 世界空间(World Space)
如字面所示,在世界空间中肯定有很多物体模型,那么怎么描述这些物体模型所在的位置呢?这就需要一个统一的参找体系了,这个参照体系就是世界空间。
- 观察空间(View Space,或者称为视觉空间(Eye Space))
观察空间经常被人们称之OpenGL的摄像机,所以有时也称为摄像机空间或视觉空间。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。
- 裁剪空间(Clip Space)
在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内,而且任何在这个范围之外的点都应该被裁剪掉,被裁剪掉的坐标就会被忽略不可见,所以剩下的坐标就将变为屏幕上可见的片段,这也就是裁剪空间。
- 屏幕空间(Screen Space)
这个坐标系大家应该非常熟悉了,android中的各种view里用的坐标就是屏幕坐标。以手机的左上角为坐标原点,向右为X轴的正方向,向下为Y轴的正方向,这就是屏幕坐标。
MVP矩阵
为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵,这三个矩阵就是我们常说的mvp矩阵。
关于这三个矩阵的定义可以看下面这个图片说明:
正交投影
所谓正交投影就是上面三种矩阵当当中的投影矩阵。在Opengl有两种投影矩阵,分别是正交投影和透视投影,正交投影的效果就是无论在远处看还是在近处看,所看到的物体大小都是一样的, 而透视投影则不同,它的投影效果是近大远小,类似我们的眼睛观察一个物体,眼睛离这个物体越近看到的范围越小,感觉这个物体越大,眼睛离这个物体越远则看到的范围越大,感觉这个物体越小。因此透视投影一般在3D场景中常用。
今天我们的场景是2D环境中图片的等比缩放,因此主要关注正交投影,正交投影的效果如下图:
正交投影所用到的方法是Matrix.orthoM
,它的具体参数如图:
在Opengl总的变换矩阵公式为mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;
这个公式的顺序不能乱,也就是不遵守惩罚交换律,从右往做读取首字母就是所谓的mvp矩阵。而在实际的2D效果中一般会忽略掉试图矩阵viewMatrix
。
主要代码逻辑
以下是使用正交投影实现渲染图片等比缩放的实例代码,与上一节 《Opengl ES之矩阵变换(上)》 的区别主要是在java层,C++层的渲染逻辑还是MatrixTransformOpengl.cpp不变。
public class MvpMatrixActivity extends BaseGlActivity {
private MatrixTransformOpengl matrixTransformOpengl;
// 遵守先缩放再旋转最后平移的顺序
private final float[] mvpMatrix = new float[16];
private final float[] mProjectMatrix = new float[16];
private final float[] modelMatrix = new float[16];
// 因为在Opengl在顶点坐标的值是在-1到1之间,因此translateX的范围可以为-2到2。
private float translateX = 0;
private float scaleX = 1;
private float rotationZ = 0;
private int imageWidth;
private int imageHeight;
// 是否使用正交投影
private boolean isUseProjectMatrix = false;
@Override
public int getLayoutId() {
return R.layout.activity_gl_mvp_matrix;
}
@Override
public BaseOpengl createOpengl() {
matrixTransformOpengl = new MatrixTransformOpengl();
return matrixTransformOpengl;
}
@Override
public Bitmap requestBitmap() {
BitmapFactory.Options options = new BitmapFactory.Options();
// 不缩放
options.inScaled = false;
// Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_test, options);
// Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_big, options);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_boy, options);
// 设置一下矩阵
Matrix.setIdentityM(mvpMatrix, 0);
matrixTransformOpengl.setMvpMatrix(mvpMatrix);
imageWidth = bitmap.getWidth();
imageHeight = bitmap.getHeight();
return bitmap;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Matrix.setIdentityM(modelMatrix,0);
// Matrix.setIdentityM(viewMatrix,0);
Matrix.setIdentityM(mProjectMatrix,0);
// Matrix.setLookAtM(viewMatrix, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0);
findViewById(R.id.bt_translate).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != matrixTransformOpengl) {
translateX += 0.1;
if (translateX >= 2) {
translateX = 0f;
}
updateMatrix();
}
}
});
findViewById(R.id.bt_scale).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != matrixTransformOpengl) {
scaleX += 0.1;
updateMatrix();
}
}
});
findViewById(R.id.bt_rotate).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != matrixTransformOpengl) {
rotationZ += 10;
updateMatrix();
}
}
});
findViewById(R.id.bt_reset).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != matrixTransformOpengl) {
translateX = 0;
scaleX = 1;
rotationZ = 0;
isUseProjectMatrix = false;
updateMatrix();
}
}
});
findViewById(R.id.bt_mvp).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != matrixTransformOpengl) {
isUseProjectMatrix = true;
updateMatrix();
}
}
});
}
private void calculateProjectMatrix(){
Matrix.setIdentityM(mProjectMatrix, 0);
int width = myGLSurfaceView.surfaceWidth;
int height = myGLSurfaceView.surfaceHeight;
if(width == 0 || height == 0){
return;
}
// 投影矩阵变换
// 例如left为-3,right为1,表示整个屏幕是4等分,但是在opengl的顶点坐标中是-1到1也就是占了屏幕的两分,因此如下这个效果是x轴半屏
// Matrix.orthoM(mvpMatrix, 0, -3, 1, -1, 1, -1, 1);
float widthScale = (float) width / imageWidth;
float heightScale = (float) height / imageHeight;
// 那个缩放小就以那个为基准
if (heightScale > widthScale) {
// 图片高度缩放,也就是以宽度为基准
float newImageHeight = widthScale * imageHeight;
float r = (float) height / newImageHeight;
Matrix.orthoM(mProjectMatrix, 0, -1, 1, -r, r, -1, 1);
} else {
// 图片宽度缩放
float newImageWidth = heightScale * imageWidth;
float r = (float) width / newImageWidth;
Matrix.orthoM(mProjectMatrix, 0, -r, r, -1, 1, -1, 1);
}
}
private void updateMatrix() {
if(isUseProjectMatrix){
calculateProjectMatrix();
}else {
Matrix.setIdentityM(mProjectMatrix, 0);
}
Matrix.setIdentityM(modelMatrix, 0);
Matrix.setIdentityM(mvpMatrix, 0);
// 重点注释
// 在组合矩阵时,先进行缩放操作,然后是旋转,最后才是位移,但是写法需要反正写,也就是先写translateM,然后rotateM,最后scaleM
// 如果不这样写会发生什么呢?例如顺这写,先写scaleM,然后是rotateM,最后写translateM,测试时就会出现问题,旋转超过180度之后再移动,就会出现移动方向相反的情况
Matrix.translateM(modelMatrix, 0, translateX, 0, 0);
Matrix.rotateM(modelMatrix, 0, rotationZ, 0, 0, 1);
Matrix.scaleM(modelMatrix, 0, scaleX, 1f, 0f);
// a 表示旋转角度
// 后面三个参数,那个参数为1则表示绕那个轴旋转
Matrix.multiplyMM(mvpMatrix,0,mProjectMatrix,0,modelMatrix,0);
matrixTransformOpengl.setMvpMatrix(mvpMatrix);
myGLSurfaceView.requestRender();
}
}
点击使用正交投影
按钮可以实现渲染纹理图的等比缩放显示。
关注我,一起进步,人生不止coding!!!
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/14468.html