一.概述
最近疏于写博客,接下来会陆续更新这段时间OpenGLES的一些开发过程。
前两篇OpenGLES的博客讲解了怎样使用OpenGLES实现相机普通预览和多宫格滤镜
在相机实现过程中,虽然使用到了纹理,但只是在生成一个纹理之后,使用纹理去创建SurfaceTexture,然后再用SurfaceTexture创建Surface,并没有使用纹理进行图片渲染,也就是纹理贴图。
之前的博文《OpenGL:纹理》中使用OpenGL实现了正方形箱子的纹理贴图,而且也详细介绍了纹理的生成、纹理坐标、设置过滤、环绕方式等。
本篇博客要讲解是OpenGLES的纹理贴图,两者原理其实是一样的,但是本篇博客更偏向于实际应用。
以下主要是代码演示,跟随代码注释了解实现过程。
二.Render类:
public class ImgRender implements GLSurfaceView.Renderer {private final String TAG = ImgRender.class.getSimpleName();private final Context mContext;private float vertexData[] = {-1f, -1f, //左下1f, -1f, //右下-1f, 1f, //左上1f, 1f, //右上};//Android 纹理原点在左上角private float textureData[] = {0.0f, 1.0f, //左上1.0f, 1.0f, //右上0.0f, 0.0f, //左下1.0f, 0.0f, //右下};//shader程序/渲染器private int shaderProgram;//纹理idprivate int[] textureId = new int[1];//顶点坐标private int aPosition;//纹理坐标private int aTexCoord;//采样器private int sampler;//顶点数据缓存private FloatBuffer vertexBuffer;//纹理数据缓存private FloatBuffer textureBuffer;//一个顶点有几个数据private int VERTEX_POSITION_SIZE = 2;//一个纹理点有几个数据private int TEXTURE_POSITION_SIZE = 2;public ImgRender(Context context) {mContext = context;}@Overridepublic 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);textureBuffer = ShaderUtils.getFloatBuffer(textureData);//创建纹理对象glGenTextures(textureId.length, textureId, 0);TextureUtils.LoadTexture(mContext, textureId[0], R.drawable.bg2);}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {Log.v(TAG, "onSurfaceChanged(): " + width + " x " + height);glViewport(0, 0, width, height);}@Overridepublic void onDrawFrame(GL10 gl) {//Log.v(TAG, "onDrawFrame()");glClear(GL_COLOR_BUFFER_BIT);/********* 顶点操作 **********/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);}
}
三.ShaderUtils相关函数:
3.1 createAndLinkProgram()
/** 创建和链接着色器程序* 参数:顶点着色器、片段着色器程序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;}
3.2 getFloatBuffer()
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,需要在绘制前重置为0return buffer;}
四.TextureUtils相关函数
4.1 LoadTexture()
//纹理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 **************///获取图片的 bitmapBitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmapResId);//绑定 bitmap 到textureIds[1]纹理GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);bitmap.recycle();//用完及时回收//解绑纹理 指的是离开对 纹理的配置,进入下一个状态glBindTexture(GL_TEXTURE_2D, 0);}
五.着色器代码
5.1 img_vertex_shader.glsl
#version 300 eslayout (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;
}
5.2 img_fragtment_shader.glsl
#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);
}
六.注意点
6.1 片段着色器中采样器的使用
有一点需要注意的是,在之前实现相机预览的片段着色器代码中,使用的采样器是:
uniform samplerExternalOES sCameraTexture;
现在渲染图片时使用的采样器是:
uniform sampler2D sampler;
在实现OpenGLES纹理贴图过程中,最初直接延用了实现相机预览时的采样器samplerExternalOES,但是图片始终无法渲染成功。
这是因为"采样器:samplerExternalOES"是OpenGLES中专门用来采样YUV数据的,所以在实现相机预览时要使用它,但是渲染图片时,就要用常规的"采样器Sampler2D"了。
6.2 Render、GLSurfaceView等实现过程
Render在GLSurfaceView中的设置,还有GLSurfeaceView在Activity或Fragment中的加载,这些常规操作的代码就不再做演示了。
根据自己实际开发过程嵌入实现就行。
七.最终效果
使用了一张宇宙星空图,请忽略开始的打开应用,点击按钮过程。
最后那张宇宙星空图的呈现,就是本次OpenGLES博文所要实现的纹理贴图的最终效果: