使用了一张宇宙星空图,请忽略"打开应用,点击按钮"过程。
最后那张宇宙星空图的呈现,就是本次OpenGLES博文所要实现的纹理贴图的最终效果:

最近疏于写博客,接下来会陆续更新这段时间OpenGLES的一些开发过程。
前两篇OpenGLES的博客讲解了怎样使用OpenGLES实现相机普通预览和多宫格滤镜
在相机实现过程中,虽然使用到了纹理,但只是在生成一个纹理之后,使用纹理去创建SurfaceTexture,然后再用SurfaceTexture创建Surface,并没有使用纹理进行图片渲染,也就是纹理贴图。
之前的博文《OpenGL:纹理》中使用OpenGL实现了正方形箱子的纹理贴图,而且也详细介绍了纹理的生成、纹理坐标、设置过滤、环绕方式等。
本篇博客要讲解是OpenGLES的纹理贴图,两者原理其实是一样的,但是本篇博客更偏向于实际应用。
以下主要是代码演示,跟随代码注释了解实现过程。
- 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;
- //纹理id
- private 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;
- }
-
- @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);
- textureBuffer = ShaderUtils.getFloatBuffer(textureData);
-
- //创建纹理对象
- glGenTextures(textureId.length, textureId, 0);
-
- TextureUtils.LoadTexture(mContext, textureId[0], R.drawable.bg2);
- }
-
- @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);
-
- /********* 顶点操作 **********/
- 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);
- }
- }
- /*
- * 创建和链接着色器程序
- * 参数:顶点着色器、片段着色器程序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);
- }
- #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);
- }
GLRender及其在GLSurfaceView中的设置:
- mGLSurfaceView = rootView.findViewById(R.id.gl_SurfaceView);
- //设置GLES版本
- mGLSurfaceView.setEGLContextClientVersion(3);
- //创建Render对象,并将其设置到GLSurfaceView
- mImgRender = new ImgRender(getActivity());
- mGLSurfaceView.setRenderer(mImgRender);
- mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
还有GLSurfeaceView在Activity或Fragment中的加载,这些常规操作的代码就不再详细演示了。根据自己实际开发过程实现就行。
有一点需要注意的是,在之前实现相机预览的片段着色器代码中,使用的采样器是:
uniform samplerExternalOES sCameraTexture;
现在渲染图片时使用的采样器是:
uniform sampler2D sampler;
在实现OpenGLES纹理贴图过程中,最初直接延用了实现相机预览时的采样器samplerExternalOES,但是图片始终无法渲染成功。
这是因为"采样器:samplerExternalOES"是OpenGLES中专门用来采样YUV数据的,所以在实现相机预览时要使用它,但是渲染图片时,就要用常规的"采样器Sampler2D"了。
单纹理贴图的实现过程到此就讲解结束了
最终的实现效果如博文开始的效果展示