本節我們來看一下glBlendFunc API的使用,對應的代碼是OpenGL\learn\src\main\java\com\opengl\learn\GlBlendFuncRender.java文件。
所有實例均有提供源碼,下載地址:Opengl ES Source Code。
API中文說明:GLES2.0中文API-glBlendFunc。
glBlendFunc API是用來混合的,上面的API說明已經介紹了很多了,作爲入門學習,有幾個基本知識我們需要知道:1、混合時,要畫上去的爲源顏色,先畫上去的爲目標顏色,執行兩者的混合,比如調用glBlendFunc(GL_ONE, GL_ZERO),前面的GL_ONE表示取要畫上去的,而後面的GL_ZERO指原來已經畫上去的;2、必須在源顏色調用glDrawArrays、glDrawElements接口之前調用glEnable(GL_BLEND)開啓混合,在繪製完成的最後調用glDisable(GL_BLEND)關閉混合,否則可能會出現源顏色不顯示、透明背景黑色等等問題;3、使用混合繪製水印等透明效果時,需要特別注意,水印背景下面不能是白色,否則也會出現各種效果異常問題,本節最後我們有對此作實驗;4、既然是混合,就必須是兩層之間的混合,所以如果我們只調用一次glDrawArrays、glDrawElements接口繪製了一層,那根本不需要使用混合,這個我一開始也一直沒搞清楚。
好了,本節來看一下我們要實現的效果,如下圖:
大家看下,我們在最左下角繪製了一個水印。代碼實現中因爲繪製了兩層,所以爲了更好的解耦,我們把原來上一節GlActiveTextureRender中的內容封裝成Map類,本節新增了Watermark.java類。Watermark.java類的所有代碼如下:
package com.opengl.learn.blend;
import android.content.Context;
import android.util.Log;
import com.lime.common.ESShader;
import com.lime.common.TextureHelper;
import com.opengl.learn.R;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_BLEND;
import static android.opengl.GLES20.GL_ELEMENT_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_ONE;
import static android.opengl.GLES20.GL_STATIC_DRAW;
import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_UNSIGNED_SHORT;
import static android.opengl.GLES20.GL_ZERO;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindBuffer;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glBlendFunc;
import static android.opengl.GLES20.glBufferData;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glDisable;
import static android.opengl.GLES20.glDisableVertexAttribArray;
import static android.opengl.GLES20.glDrawElements;
import static android.opengl.GLES20.glEnable;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGenBuffers;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;
public class Watermark {
private static final String TAG = Watermark.class.getSimpleName();
private static final int BYTES_PER_FLOAT = 4;
private static final int BYTES_PER_SHORT = 2;
private static final int POSITION_COMPONENT_SIZE = 3;
private final float[] mVerticesData =
{
-1.0f, 0.5f, 0.0f, // v0
-1.0f, -0.5f, 0.0f, // v1
1.0f, -0.5f, 0.0f, // v2
1.0f, 0.5f, 0.0f, // v3
};
private final short[] mIndicesData =
{
0, 1, 2,
0, 2, 3,
};
private final float[] mTexturePosiontData =
{
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f
};
private Context mContext;
private int mBlendProgram;
private int mWidth, mHeight;
private FloatBuffer mVertices;
private FloatBuffer mTextureBuffer;
private ShortBuffer mIndices;
private int aBlendPosition, aBlendTexturePosition, uBlendTextureUnit;
private int[] mVBOIds = new int[4];
private int mBlendTexture;
public Watermark(Context context) {
mContext = context;
mVertices = ByteBuffer.allocateDirect(mVerticesData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertices.put(mVerticesData).position(0);
mTextureBuffer = ByteBuffer.allocateDirect(mTexturePosiontData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTextureBuffer.put(mTexturePosiontData).position(0);
mIndices = ByteBuffer.allocateDirect(mIndicesData.length * BYTES_PER_SHORT)
.order(ByteOrder.nativeOrder()).asShortBuffer();
mIndices.put(mIndicesData).position(0);
}
public void onSurfaceCreated() {
loadWatermark();
}
private void loadWatermark() {
String vShaderStr = ESShader.readShader(mContext, "blendfunc_vertexShader.glsl");
String fShaderStr = ESShader.readShader(mContext, "blendfunc_fragmentShader.glsl");
// Load the shaders and get a linked program object
mBlendProgram = ESShader.loadProgram(vShaderStr, fShaderStr);
aBlendPosition = glGetAttribLocation(mBlendProgram, "aBlendPosition");
aBlendTexturePosition = glGetAttribLocation(mBlendProgram, "aBlendTexturePosition");
uBlendTextureUnit = glGetUniformLocation(mBlendProgram, "uBlendTextureUnit");
glGenBuffers(3, mVBOIds, 0);
Log.e(TAG, "0: " + mVBOIds[0] + ", 1: " + mVBOIds[1] + ", 2: " + mVBOIds[2]);
// mVBOIds[0] - used to store vertex position
mVertices.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mVerticesData.length,
mVertices, GL_STATIC_DRAW);
mTextureBuffer.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mTexturePosiontData.length,
mTextureBuffer, GL_STATIC_DRAW);
// mVBOIds[2] - used to store element indices
mIndices.position(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, BYTES_PER_SHORT * mIndicesData.length, mIndices, GL_STATIC_DRAW);
mBlendTexture = TextureHelper.loadTexture(mContext, R.mipmap.watermark);
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
}
public void onDrawFrame() {
glViewport(0, 0, 288, 144);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ZERO);
drawWatermark();
glDisable(GL_BLEND);
}
private void drawWatermark() {
glUseProgram(mBlendProgram);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
glEnableVertexAttribArray(aBlendPosition);
glVertexAttribPointer(aBlendPosition, POSITION_COMPONENT_SIZE,
GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
glEnableVertexAttribArray(aBlendTexturePosition);
glVertexAttribPointer(aBlendTexturePosition, 2,
GL_FLOAT, false, 0, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mBlendTexture);
glUniform1i(mBlendTexture, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2]);
glDrawElements(GL_TRIANGLES, mIndicesData.length, GL_UNSIGNED_SHORT, 0);
glDisableVertexAttribArray(aBlendPosition);
glDisableVertexAttribArray(aBlendTexturePosition);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
其中大部分都是和Map基本相同的,因爲我們不需要color顏色了,所以把顏色相關的都刪除掉,我們需要的是水印的頂點座標、水印的紋理座標、水印的索引屬性,所以VBO的數組長度爲3,drawWatermark方法中的邏輯和Map中也基本相同,都是使能頂點屬性,然後賦值,綁定紋理uniform,最後繪製。
有一個不同的,就是在onDrawFrame方法中的第一行調用glViewport(0, 0, 288, 144)。我們最開始時候已經講過,glViewport API可以指定我們當前繪製的目標窗口位置和大小,所以我們可以使用它來控制水印繪製的位置。另外,因爲我們是兩個對象,所以兩個頂點着色器、兩個片段着色器,分別組合生成兩個Program對象,所以它們倆是不相干的,就是說完全可以取相同名字、相同類型的變量,也都可以正常使用,而且glActiveTexture(GL_TEXTURE0)激活紋理時,都是激活0號紋理,因爲它們不在同一個Program中,所以不會衝突,也不會出錯。
最後,我們把Map中頂點數組的值改爲如下來看看效果:
private final float[] mVerticesData =
{
-1.0f, 0.5f, 0.0f, // v0
-1.0f, -0.5f, 0.0f, // v1
1.0f, -0.5f, 0.0f, // v2
1.0f, 0.5f, 0.0f, // v3
};
對應的效果如下:
可以看到,左下角的水印沒了,看不到了。
我們把頂點數組還原回去,把glBlendFunc混合接口註釋掉:
@Override
public void onDrawFrame(GL10 gl) {
glClear(GL_COLOR_BUFFER_BIT);
mMap.onDrawFrame();
// glEnable(GL_BLEND);
// glBlendFunc(GL_ONE, GL_ONE);
mWatermark.onDrawFrame();
// glDisable(GL_BLEND);
}
效果如下:
左下角的水印背景是黑色的,反正就是各種不對,所以我們必須要掌握要點,才能保證水印能正確的畫出來。好了,本節的內容就講這麼多,大家可以下載代碼自己去試。