上一篇博客讲解了OpenGLES怎么实现单纹理贴图
仅仅只绘制一张图片是不过瘾的
本篇博客讲解如何通过多纹理贴图实现图片和文本水印效果
在单纹理贴图基础上,多纹理贴图的区别主要有两点:
知道这两点之后,实现起来就容易了
这篇博客我会使用多纹理实现一张背景大图,一张水印小图和一串文字水印的效果。
最终效果在最后会展示。
废话不多说,正篇开始!
public class ImgTxtRender implements GLSurfaceView.Renderer {
private final String TAG = ImgTxtRender.class.getSimpleName();
private final Context mContext;
//背景图片顶点
private float vertexData[] = {
-1f, -1f, //左下
1f, -1f, //右下
-1f, 1f, //左上
1f, 1f, //右上
};
//图片水印顶点
private float vertexData1[] = {
-1f, 0.5f, //左下
0f, 0.5f, //右下
-1f, 1f, //左上
0f, 1f, //右上
};
//文本水印顶点
private float vertexData2[] = {
0f, -0.85f, //左下
1f, -0.85f, //右下
0f, -0.75f, //左上
1f, -0.75f //右上
};
//Android 纹理原点在左上角
private float textureData[] = {
0.0f, 1.0f, //左上
1.0f, 1.0f, //右上
0.0f, 0.0f, //左下
1.0f, 0.0f, //右下
};
//shader程序/渲染器
private int shaderProgram;
//纹理id数组
private int[] textureId = new int[3];
//顶点坐标
private int aPosition;
//纹理坐标
private int aTexCoord;
//采样器
private int sampler;
//顶点数据缓存
private FloatBuffer vertexBuffer;
private FloatBuffer vertexBuffer1;
private FloatBuffer vertexBuffer2;
//纹理数据缓存
private FloatBuffer textureBuffer;
//一个顶点有几个数据
private int VERTEX_POSITION_SIZE = 2;
//一个纹理点有几个数据
private int TEXTURE_POSITION_SIZE = 2;
public ImgTxtRender(Context context) {
mContext = context;
}
public void initData(Size size) {
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.v(TAG, "onSurfaceCreated()");
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
initGLES();
}
public void initGLES() {
Log.v(TAG, "initGLES!");
/************** 着色器程序/渲染器 **************/
//创建并连接 着色器程序
shaderProgram = ShaderUtils.createAndLinkProgram(mContext,
"img_vertex_shader.glsl",
"img_fragtment_shader.glsl");
if (shaderProgram == 0) {
Log.v(TAG, "create And Link ShaderProgram Fail!");
return;
}
//使用着色器源程序
glUseProgram(shaderProgram);
/************** 着色器变量 **************/
//从着色器程序中获取各个变量
aPosition = glGetAttribLocation(shaderProgram, "aPosition");
aTexCoord = glGetAttribLocation(shaderProgram, "aTexCoord");
sampler = glGetUniformLocation(shaderProgram, "sampler");
//将片段着色器的采样器(纹理属性:sampler)设置为0号单元
glUniform1i(sampler, 0);
vertexBuffer = ShaderUtils.getFloatBuffer(vertexData);
vertexBuffer1 = ShaderUtils.getFloatBuffer(vertexData1);
vertexBuffer2 = ShaderUtils.getFloatBuffer(vertexData2);
textureBuffer = ShaderUtils.getFloatBuffer(textureData);
//设置文字支持透明
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
/************* 纹理 **************/
//创建纹理对象1
glGenTextures(textureId.length, textureId, 0);
//激活纹理0:默认0号纹理单元,一般最多能绑16个,视GPU而定
glActiveTexture(GL_TEXTURE);
TextureUtils.LoadTexture(mContext,textureId[0], R.drawable.bg4);
TextureUtils.LoadTexture(mContext,textureId[1], R.drawable.watermark);
glBindTextureLoadTxt(textureId[2], "OpenGLES: Shawn.Xiao!");
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.v(TAG, "onSurfaceChanged(): " + width + " x " + height);
glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
//Log.v(TAG, "onDrawFrame()");
glClear(GL_COLOR_BUFFER_BIT);
drawBg();
drawWaterMarkImg();
drawWaterMarkTxt();
}
private void drawBg() {
/********* 顶点操作 **********/
glEnableVertexAttribArray(aPosition);
glEnableVertexAttribArray(aTexCoord);
glVertexAttribPointer(aPosition, VERTEX_POSITION_SIZE, GL_FLOAT, false, 2 * 4, vertexBuffer);
glVertexAttribPointer(aTexCoord, TEXTURE_POSITION_SIZE, GL_FLOAT, false, 2 * 4, textureBuffer);
/********* 纹理操作:激活、绑定 **********/
glActiveTexture(GL_TEXTURE);
glBindTexture(GL_TEXTURE_2D, textureId[0]);
/********* 绘制 **********/
//绘制vertexData.length/2即4个点
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexData.length / 2);
/********* 纹理操作:解除绑定 **********/
glBindTexture(GL_TEXTURE_2D, 0);
//关闭顶点数组的句柄
glDisableVertexAttribArray(aPosition);
glDisableVertexAttribArray(aTexCoord);
}
private void drawWaterMarkImg() {
/********* 顶点操作 **********/
glEnableVertexAttribArray(aPosition);
glEnableVertexAttribArray(aTexCoord);
glVertexAttribPointer(aPosition, VERTEX_POSITION_SIZE, GL_FLOAT, false, 2 * 4, vertexBuffer1);
glVertexAttribPointer(aTexCoord, TEXTURE_POSITION_SIZE, GL_FLOAT, false, 2 * 4, textureBuffer);
/********* 纹理操作:激活、绑定 **********/
//glActiveTexture(GL_TEXTURE1); //花了129元
glBindTexture(GL_TEXTURE_2D, textureId[1]);
/********* 绘制 **********/
//绘制vertexData.length/2即4个点
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexData1.length / 2);
/********* 纹理操作:解除绑定 **********/
glBindTexture(GL_TEXTURE_2D, 0);
//关闭顶点数组的句柄
glDisableVertexAttribArray(aPosition);
glDisableVertexAttribArray(aTexCoord);
}
private void drawWaterMarkTxt() {
/********* 顶点操作 **********/
glEnableVertexAttribArray(aPosition);
glEnableVertexAttribArray(aTexCoord);
glVertexAttribPointer(aPosition, VERTEX_POSITION_SIZE, GL_FLOAT, false, 2 * 4, vertexBuffer2);
glVertexAttribPointer(aTexCoord, TEXTURE_POSITION_SIZE, GL_FLOAT, false, 2 * 4, textureBuffer);
/********* 纹理操作:激活、绑定 **********/
glActiveTexture(GL_TEXTURE);
glBindTexture(GL_TEXTURE_2D, textureId[2]);
/********* 绘制 **********/
//绘制vertexData.length/2即4个点
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexData.length / 2);
/********* 纹理操作:解除绑定 **********/
glBindTexture(GL_TEXTURE_2D, 0);
//关闭顶点数组的句柄
glDisableVertexAttribArray(aPosition);
glDisableVertexAttribArray(aTexCoord);
}
private void glBindTextureLoadTxt(int textureId, String txt) {
//绑定纹理:将纹理放到当前单元的 GL_TEXTURE_BINDING_EXTERNAL_OES 目标对象中
glBindTexture(GL_TEXTURE_2D, textureId);
//配置纹理:过滤方式
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
/************* bitmap **************/
//定义变量
int textSize = 36;
String textColor = "#ffff00";
String bgColor = "#00000000";
int padding = 0;
//txt转换成bitmap
Bitmap bitmap = BitmapUtils.createTextImage(txt, textSize, textColor, bgColor, padding);
//绑定 bitmap 到textureIds[1]纹理
GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();//用完及时回收
//解绑纹理 指的是离开对 纹理的配置,进入下一个状态
glBindTexture(GL_TEXTURE_2D, 0);
}
}
ShaderUtils的函数与单纹理贴图的博文中是一样的
/*
* 创建和链接着色器程序
* 参数:顶点着色器、片段着色器程序ResId
* 返回:成功创建、链接了顶点和片段着色器的着色器程序Id
*/
public static int createAndLinkProgram(Context context, String vertexShaderFN, String fragShaderFN) {
//创建着色器程序
int shaderProgram = glCreateProgram();
if (shaderProgram == 0) {
Log.e(TAG, "Failed to create shaderProgram ");
return 0;
}
//获取顶点着色器对象
int vertexShader = loadShader(GL_VERTEX_SHADER, loadShaderSource(context, vertexShaderFN));
if (0 == vertexShader) {
Log.e(TAG, "Failed to load vertexShader");
return 0;
}
//获取片段着色器对象
int fragmentShader = loadShader(GL_FRAGMENT_SHADER, loadShaderSource(context, fragShaderFN));
if (0 == fragmentShader) {
Log.e(TAG, "Failed to load fragmentShader");
return 0;
}
//绑定顶点着色器到着色器程序
glAttachShader(shaderProgram, vertexShader);
//绑定片段着色器到着色器程序
glAttachShader(shaderProgram, fragmentShader);
//链接着色器程序
glLinkProgram(shaderProgram);
//检查着色器链接状态
int[] linked = new int[1];
glGetProgramiv(shaderProgram, GL_LINK_STATUS, linked, 0);
if (linked[0] == 0) {
glDeleteProgram(shaderProgram);
Log.e(TAG, "Failed to link shaderProgram");
return 0;
}
return shaderProgram;
}
public static FloatBuffer getFloatBuffer(float[] array) {
//将顶点数据拷贝映射到 native 内存中,以便opengl能够访问
FloatBuffer buffer = ByteBuffer
.allocateDirect(array.length * BYTES_PER_FLOAT)//直接分配 native 内存,不会被gc
.order(ByteOrder.nativeOrder())//和本地平台保持一致的字节序(大/小头)
.asFloatBuffer();//将底层字节映射到FloatBuffer实例,方便使用
buffer.put(array)//将顶点拷贝到 native 内存中
.position(0);//每次 put position 都会 + 1,需要在绘制前重置为0
return buffer;
}
//纹理Id由外部传入
public static void LoadTexture(Context context, int textureId, int bitmapResId) {
//绑定纹理:将纹理放到当前单元的 GL_TEXTURE_BINDING_EXTERNAL_OES 目标对象中
glBindTexture(GL_TEXTURE_2D, textureId);
//配置纹理:过滤方式
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
/************* bitmap **************/
//获取图片的 bitmap
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmapResId);
//绑定 bitmap 到textureIds[1]纹理
GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();//用完及时回收
//解绑纹理 指的是离开对 纹理的配置,进入下一个状态
glBindTexture(GL_TEXTURE_2D, 0);
}
文字转换成Bitmap
/**
* 设置文字水印
*
* @param text 文本内容
* @param textSize 文字大小
* @param textColor 文字颜色
* @param bgColor 文字背景颜色 #00000000
* @param padding 文字与边距距离
* @return 文字水印的 bitmap
*/
public static Bitmap createTextImage(String text, int textSize, String textColor, String bgColor, int padding) {
Paint paint = new Paint();
paint.setColor(Color.parseColor(textColor));
paint.setTextSize(textSize);
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
float width = paint.measureText(text, 0, text.length());
float top = paint.getFontMetrics().top;
float bottom = paint.getFontMetrics().bottom;
Bitmap bm = Bitmap.createBitmap((int) (width + padding * 2), (int) ((bottom - top) + padding * 2), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bm);
canvas.drawColor(Color.parseColor(bgColor));
canvas.drawText(text, padding, -top + padding, paint);
return bm;
}
着色器代码与上一篇博文中的单纹理贴图是一样的:
1.img_vertex_shader.glsl
#version 300 es
layout (location = 0) in vec4 aPosition; //把顶点坐标给这个变量, 确定要画画的形状
layout (location = 1) in vec4 aTexCoord; //接收纹理坐标,接收采样器采样图片的坐标
//传给片元着色器 像素点
out vec2 vTexCoord;
void main()
{
//内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
gl_Position = aPosition;
vTexCoord = aTexCoord.xy;
}
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec2 vTexCoord; //纹理坐标,图片当中的坐标点
uniform sampler2D sampler; //图片,采样器
out vec4 outColor;
void main(){
outColor = texture(sampler, vTexCoord);
}
Render、GLSurfaceView与Activity、Fragment等之间的实现逻辑请根据自己项目的实际情况去实现
这里只贴出Render在GLSurfaceView中设置的代码:
mGLSurfaceView = rootView.findViewById(R.id.Img_Txt_GLSurfaceView);
//设置GLES版本
mGLSurfaceView.setEGLContextClientVersion(3);
//创建Render对象,并将其设置到GLSurfaceView
mImgTxtRender = new ImgTxtRender(getActivity());
mGLSurfaceView.setRenderer(mImgTxtRender);
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
一张背景大图,一张水印小图,和一串水印文字: