OpenGLES正交投影

在图片渲染的时候,之前使用的顶点坐标是占满整个屏幕的归一化坐标

1
2
3
4
5
6
7
8

//顶点坐标
static float vertexData[] = { // in counterclockwise order:
-1f, -1f, 0.0f, // bottom left
1f, -1f, 0.0f, // bottom right
-1f, 1f, 0.0f, // top left
1f, 1f, 0.0f, // top right
};

这样就导致了如下图所示的的问题

横竖屏切换存在的问题

所以我们应该根据屏幕宽高和图片宽高对应的比例算出正确的位置:

横竖屏切换问题解决

上面我们得到的( ?)是不在归一化坐标范围内的,为了能使OpenGL正确的渲染,我们就需要把(?)以及其他边统一转换到归一化坐标内,这个操作就是正交投影

使用正交投影,不管物体多远多近,物体看起来总是形状、大小比例相同的。

在OpenGLES里面使用投影矩阵:

vertex_shader_m.glsl

1
2
3
4
5
6
7
8
9
10
attribute vec4 av_Position;//顶点位置
attribute vec2 af_Position;//纹理位置
varying vec2 v_texPo;//纹理位置 与fragment_shader交互

uniform mat4 u_Matrix; //投影矩阵

void main() {
v_texPo = af_Position;
gl_Position = av_Position * u_Matrix;
}

在使用的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//投影矩阵
private int uMatrix;
private float[] matrix = new float[4 * 4];


//1. 获取投影矩阵
uMatrix = GLES20.glGetUniformLocation(program, "u_Matrix");


//2. 计算值
public void onSurfaceChanged(int width, int height) {
if (width > height) {
float x = width / ((float) height / bitmap.getHeight() * bitmap.getWidth());
Matrix.orthoM(matrix, 0, -x, x, -1, 1, -1, 1);
} else {
float y = height / ((float) width / bitmap.getWidth() * bitmap.getHeight());
Matrix.orthoM(matrix, 0, -1, 1, -y, y, -1, 1);
}
}


//3. 赋值

GLES20.glUniformMatrix4fv(uMatrix, 1, false, matrix, 0);

主要代码如下:BitmapTexture.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161


import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.Matrix;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;


//纹理 根据坐标系映射
public class BitmapTexture {


//顶点坐标
static float vertexData[] = { // in counterclockwise order:
-1f, -1f, 0.0f, // bottom left
1f, -1f, 0.0f, // bottom right
-1f, 1f, 0.0f, // top left
1f, 1f, 0.0f, // top right
};

//纹理坐标 对应顶点坐标 与之映射
static float textureData[] = { // in counterclockwise order:
0f, 1f, 0.0f, // bottom left
1f, 1f, 0.0f, // bottom right
0f, 0f, 0.0f, // top left
1f, 0f, 0.0f, // top right
};

//每一次取点的时候取几个点
static final int COORDS_PER_VERTEX = 3;

private final int vertexCount = vertexData.length / COORDS_PER_VERTEX;
//每一次取的总的点 大小
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex


private Context context;

//位置
private FloatBuffer vertexBuffer;
//纹理
private FloatBuffer textureBuffer;
private int program;
private int avPosition;
//纹理位置
private int afPosition;
//正交投影
private int uMatrix;
private float[] matrix = new float[4 * 4];

//需要渲染的纹理id
private int imageTextureId;


private Bitmap bitmap;

public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}

public BitmapTexture(Context context) {
this.context = context;

vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);
vertexBuffer.position(0);

textureBuffer = ByteBuffer.allocateDirect(textureData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureData);
textureBuffer.position(0);
}


public void onSurfaceCreated() {
String vertexSource = ShaderUtil.readRawTxt(context, R.raw.vertex_shader_m);
String fragmentSource = ShaderUtil.readRawTxt(context, R.raw.fragment_shader);
program = ShaderUtil.createProgram(vertexSource, fragmentSource);

if (program > 0) {
//获取顶点坐标字段
avPosition = GLES20.glGetAttribLocation(program, "av_Position");
//获取纹理坐标字段
afPosition = GLES20.glGetAttribLocation(program, "af_Position");

uMatrix = GLES20.glGetUniformLocation(program, "u_Matrix");

imageTextureId = createImageTexture();
}
}

public void onSurfaceChanged(int width, int height) {
if (width > height) {
float x = width / ((float) height / bitmap.getHeight() * bitmap.getWidth());
Matrix.orthoM(matrix, 0, -x, x, -1, 1, -1, 1);
} else {
float y = height / ((float) width / bitmap.getWidth() * bitmap.getHeight());
Matrix.orthoM(matrix, 0, -1, 1, -y, y, -1, 1);
}
}


public void draw() {

//使用程序
GLES20.glUseProgram(program);

GLES20.glUniformMatrix4fv(uMatrix, 1, false, matrix, 0);

//绑定渲染纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTextureId);

GLES20.glEnableVertexAttribArray(avPosition);
GLES20.glEnableVertexAttribArray(afPosition);
//设置顶点位置值
GLES20.glVertexAttribPointer(avPosition, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
//设置纹理位置值
GLES20.glVertexAttribPointer(afPosition, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureBuffer);

//绘制 GLES20.GL_TRIANGLE_STRIP:复用坐标
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
GLES20.glDisableVertexAttribArray(avPosition);
GLES20.glDisableVertexAttribArray(afPosition);

//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}


private int createImageTexture() {
int[] textureIds = new int[1];
//创建纹理
GLES20.glGenTextures(1, textureIds, 0);
if (textureIds[0] == 0) {
return 0;
}
//绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]);
//环绕(超出纹理坐标范围) (s==x t==y GL_REPEAT 重复)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
//过滤(纹理像素映射到坐标点) (缩小、放大:GL_LINEAR线性)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

//测试图片
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textureIds[0];
}
}

注意: 在使用FBO GLES20.glTexImage2D分配内存大小的时候,需要根据横竖屏来设置值。不然计算出来的值和渲染的宽高不一样,渲染就会出现变形。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

public void onSurfaceChanged(int width, int height) {
if (width > height) {
float x = width / ((float) height / bitmap.getHeight() * bitmap.getWidth());
Matrix.orthoM(matrix, 0, -x, x, -1, 1, -1, 1);
} else {
float y = height / ((float) width / bitmap.getWidth() * bitmap.getHeight());
Matrix.orthoM(matrix, 0, -1, 1, -y, y, -1, 1);
}


if (fboId != 0) {
GLES20.glDeleteFramebuffers(1, new int[]{fboId}, 0);
GLES20.glDeleteTextures(1, new int[]{imageTextureId}, 0);
}

createFBO(width, height);
}



private void createFBO(int w, int h) {
if (bitmap == null) {
throw new IllegalArgumentException("bitmap is null");
}

//1. 创建FBO
int[] fbos = new int[1];
GLES20.glGenFramebuffers(1, fbos, 0);
fboId = fbos[0];
//2. 绑定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);

//3. 创建FBO纹理
fboTextureId = createTexture();

//4. 把纹理绑定到FBO
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, fboTextureId, 0);

//5. 设置FBO分配内存大小
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, w, h,
0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);

//6. 检测是否绑定从成功
if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER)
!= GLES20.GL_FRAMEBUFFER_COMPLETE) {
Log.e("zzz", "glFramebufferTexture2D error");
}
//7. 解绑纹理和FBO
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
-------------The End-------------