去年有分析了一些Opengl ES的實例,但是後面在實際的工作中,發現根基不牢,工作中使用的一些複雜場景還是理解的不夠透徹,所以回過心來,必須把基礎把紮實。從這節開始,我們後面對一些非常基礎普通的Opengl ES API進行實際使用介紹,萬丈高樓平地起,必須把基礎搞紮實,我們才能一步步向上。
所有實例均有提供源碼,下載地址:Opengl ES Source Code。
網上找到一份非常好的資料,收集了很多API的英文翻譯,供大家參考:OpenGL ES 2.0 中文API。
好,本節我們來看一下glViewport API的作用。實現的實例如下圖:
很簡單,我們就是不斷的變化視口,然後每個視口畫一個三角形。本節的源碼是在OpenGL\learn\src\main\java\com\opengl\learn\GlViewportRender.java文件中,該類所有源碼如下:
package com.opengl.learn;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;
import com.lime.common.ESShader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_COMPILE_STATUS;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_LINK_STATUS;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glCompileShader;
import static android.opengl.GLES20.glCreateProgram;
import static android.opengl.GLES20.glCreateShader;
import static android.opengl.GLES20.glDeleteProgram;
import static android.opengl.GLES20.glDeleteShader;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetError;
import static android.opengl.GLES20.glGetProgramInfoLog;
import static android.opengl.GLES20.glGetProgramiv;
import static android.opengl.GLES20.glGetShaderInfoLog;
import static android.opengl.GLES20.glGetShaderiv;
import static android.opengl.GLES20.glLinkProgram;
import static android.opengl.GLES20.glShaderSource;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;
public class GlViewportRender implements GLSurfaceView.Renderer {
private static String TAG = GlViewportRender.class.getSimpleName();
private final float[] mVerticesData =
{0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f};
private final float[] mColorData =
{
1.0f, 0.0f, 0.0f, 1.0f, // c0
0.0f, 1.0f, 0.0f, 1.0f, // c1
0.0f, 0.0f, 1.0f, 1.0f // c2
};
private static final int BYTES_PER_FLOAT = 4;
private static final int POSITION_COMPONENT_SIZE = 3;
private static final int COLOR_COMPONENT_SIZE = 4;
private Context mContext;
private int mProgramObject;
private int mWidth;
private int mHeight;
private FloatBuffer mVertices, mColorVertices;
private int vPosition, vColor;
private static final int STRIDE =
(POSITION_COMPONENT_SIZE + COLOR_COMPONENT_SIZE) * BYTES_PER_FLOAT;
public GlViewportRender(Context context) {
mContext = context;
mVertices = ByteBuffer.allocateDirect(mVerticesData.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertices.put(mVerticesData).position(0);
mColorVertices = ByteBuffer.allocateDirect(mColorData.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mColorVertices.put(mColorData).position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
loadProgram();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public void onDrawFrame(GL10 gl) {
drawShape();
}
private void drawShape() {
// Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
for (int i = 0; i < 4; i++) {
if (i == 0) {
glViewport(0, mHeight / 2, mWidth / 2, mHeight / 2);
} else if (i == 1) {
glViewport(mWidth / 2, mHeight / 2, mWidth / 2, mHeight / 2);
} else if (i == 2) {
glViewport(0, 0, mWidth / 2, mHeight / 2);
} else if (i == 3) {
glViewport(mWidth / 2, 0, mWidth / 2, mHeight / 2);
}
// Use the program object
glUseProgram(mProgramObject);
mVertices.position(0);
// Load the vertex data
glVertexAttribPointer(vPosition, 3, GL_FLOAT, false, STRIDE, mVertices);
glEnableVertexAttribArray(vPosition);
mVertices.position(POSITION_COMPONENT_SIZE);
glVertexAttribPointer(vColor, 4, GL_FLOAT, false, STRIDE, mVertices);
glEnableVertexAttribArray(vColor);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
}
private void loadProgram() {
String vShaderStr = ESShader.readShader(mContext, "viewport_vertexShader.glsl");
String fShaderStr = ESShader.readShader(mContext, "viewport_fragmentShader.glsl");
int vertexShader;
int fragmentShader;
int programObject;
int[] linked = new int[1];
// Load the vertex/fragment shaders
vertexShader = loadShader(GL_VERTEX_SHADER, vShaderStr);
fragmentShader = loadShader(GL_FRAGMENT_SHADER, fShaderStr);
// Create the program object
programObject = glCreateProgram();
if (programObject == 0) {
return;
}
glAttachShader(programObject, vertexShader);
glAttachShader(programObject, fragmentShader);
// Link the program
glLinkProgram(programObject);
// Check the link status
glGetProgramiv(programObject, GL_LINK_STATUS, linked, 0);
if (linked[0] == 0) {
Log.e(TAG, "Error linking program:");
Log.e(TAG, glGetProgramInfoLog(programObject));
glDeleteProgram(programObject);
return;
}
// Store the program object
mProgramObject = programObject;
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
vPosition = glGetAttribLocation(mProgramObject, "vPosition");
vColor = glGetAttribLocation(mProgramObject, "vColor");
Log.i(TAG, "vPosition: " + vPosition + ", vColor: " + vColor);
}
private int loadShader(int type, String shaderSrc) {
int shader;
int[] compiled = new int[1];
// Create the shader object
shader = glCreateShader(type);
if (shader == 0) {
return 0;
}
// Load the shader source
glShaderSource(shader, shaderSrc);
// Compile the shader
glCompileShader(shader);
// Check the compile status
glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "compile shader error: " + glGetShaderInfoLog(shader));
glGetError();
glDeleteShader(shader);
return 0;
}
Log.i(TAG, "load " + type + " shader result: " + shader);
return shader;
}
}
我們主要是演練一下glViewport API的功能,其他邏輯就簡單略過了。glViewport接口的介紹請點擊:GLES2.0中文API-glViewport。可以看到該接口一共有四個參數,前兩個參數x、y定位了當前視口的左下角的點,以像素爲單位,後面兩個參數width、height定義了當前視口的大小,程序員思維應該很容易理解,一個接口就確定好了我們視口的位置和大小。那想像一下,我們平時繪製時,直接調用glViewport(0, 0, mWidth, mHeight),表示視口左下角和屏幕左下角重合,視口大小就是屏幕大小,所以視口的中心點也就是屏幕的中心點,繪製時就是按照Opengl座標,把整個屏幕投影在(-1,1)範圍內,X/Y方向都是。
明白了最基礎的使用,我們再考慮一下,那如果我要添加一個水印,要指定水印的位置,要怎麼辦呢?對了,我們就可以修改視口的參數來達到這樣的目的了,我們修改前兩個參數,就可以變換視口的左下角;修改後兩個參數就可以控制視口的大小。
那要實現本例的效果,想必大家感覺也很簡單了,四個三角形大小相同,且寬高歸一化後都是0.5f,我們要畫第一個左上角的三角形,它的左下角就是屏幕左側的中間點的座標(0,mHeight / 2);第二個右上角的三角形,它的左下角的座標就是屏幕的中心點(mWidth / 2, mHeight / 2);第三個左下角的三角形,它的左下角座標也就是屏幕的左下角(0,0);第四個三角形的左下角座標就是屏幕最底線的中間點(mWidth / 2, 0)。最後兩個參數寬高都一樣,這樣我們就畫出來四個不同位置的三角形了。
這裏順便說一下glClear(GL_COLOR_BUFFER_BIT)清屏的調用,不能在for循環中調用,否則就會出問題。比如我們把drawShape方法修改成如下:
private void drawShape() {
// Clear the color buffer
for (int i = 0; i < 4; i++) {
glClear(GL_COLOR_BUFFER_BIT);
if (i == 0) {
glViewport(0, mHeight / 2, mWidth / 2, mHeight / 2);
} else if (i == 1) {
glViewport(mWidth / 2, mHeight / 2, mWidth / 2, mHeight / 2);
} else if (i == 2) {
glViewport(0, 0, mWidth / 2, mHeight / 2);
} else if (i == 3) {
glViewport(mWidth / 2, 0, mWidth / 2, mHeight / 2);
}
// Use the program object
glUseProgram(mProgramObject);
mVertices.position(0);
// Load the vertex data
glVertexAttribPointer(vPosition, 3, GL_FLOAT, false, STRIDE, mVertices);
glEnableVertexAttribArray(vPosition);
mVertices.position(POSITION_COMPONENT_SIZE);
glVertexAttribPointer(vColor, 4, GL_FLOAT, false, STRIDE, mVertices);
glEnableVertexAttribArray(vColor);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
}
我們把glClear(GL_COLOR_BUFFER_BIT)邏輯放在for循環裏面了,那麼想像一下,前三個三角形畫完,都會執行glClear被清除,只有第四個三角形畫完後退出for循環,所以也就只會顯示最後一個三角形了。
還有一個需要注意的,我們該實例中頂點的位置和顏色屬性都是從mVertices中取值的,所以在給顏色賦值時,必須調用mVertices.position(POSITION_COMPONENT_SIZE)移動Buffer的起始位置,否則顏色取值就會出錯,出來的結果跟我們意想的就會不一樣,大家可以試一下。
好了,glViewport API我們就介紹到這裏吧,希望大家也能紮實的打好基礎,否則實際工作中碰到的那複雜場景我們真的是束手無策。