• OpenGLES:单纹理贴图


    效果展示

    使用了一张宇宙星空图,请忽略"打开应用,点击按钮"过程

    最后那张宇宙星空图的呈现,就是本次OpenGLES博文所要实现的纹理贴图最终效果:

    一.概述

    最近疏于写博客,接下来会陆续更新这段时间OpenGLES的一些开发过程。

    前两篇OpenGLES的博客讲解了怎样使用OpenGLES实现相机普通预览多宫格滤镜

    在相机实现过程中,虽然使用到了纹理,但只是在生成一个纹理之后,使用纹理去创建SurfaceTexture,然后再用SurfaceTexture创建Surface,并没有使用纹理进行图片渲染,也就是纹理贴图。

    之前的博文《OpenGL:纹理》中使用OpenGL实现了正方形箱子的纹理贴图,而且也详细介绍了纹理的生成、纹理坐标、设置过滤、环绕方式等。

    本篇博客要讲解是OpenGLES的纹理贴图,两者原理其实是一样的,但是本篇博客更偏向于实际应用。

    以下主要是代码演示,跟随代码注释了解实现过程。

    二.Render类:

    1. public class ImgRender implements GLSurfaceView.Renderer {
    2. private final String TAG = ImgRender.class.getSimpleName();
    3. private final Context mContext;
    4. private float vertexData[] = {
    5. -1f, -1f, //左下
    6. 1f, -1f, //右下
    7. -1f, 1f, //左上
    8. 1f, 1f, //右上
    9. };
    10. //Android 纹理原点在左上角
    11. private float textureData[] = {
    12. 0.0f, 1.0f, //左上
    13. 1.0f, 1.0f, //右上
    14. 0.0f, 0.0f, //左下
    15. 1.0f, 0.0f, //右下
    16. };
    17. //shader程序/渲染器
    18. private int shaderProgram;
    19. //纹理id
    20. private int[] textureId = new int[1];
    21. //顶点坐标
    22. private int aPosition;
    23. //纹理坐标
    24. private int aTexCoord;
    25. //采样器
    26. private int sampler;
    27. //顶点数据缓存
    28. private FloatBuffer vertexBuffer;
    29. //纹理数据缓存
    30. private FloatBuffer textureBuffer;
    31. //一个顶点有几个数据
    32. private int VERTEX_POSITION_SIZE = 2;
    33. //一个纹理点有几个数据
    34. private int TEXTURE_POSITION_SIZE = 2;
    35. public ImgRender(Context context) {
    36. mContext = context;
    37. }
    38. @Override
    39. public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    40. Log.v(TAG, "onSurfaceCreated()");
    41. glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    42. initGLES();
    43. }
    44. public void initGLES() {
    45. Log.v(TAG, "initGLES!");
    46. /************** 着色器程序/渲染器 **************/
    47. //创建并连接 着色器程序
    48. shaderProgram = ShaderUtils.createAndLinkProgram(mContext,
    49. "img_vertex_shader.glsl",
    50. "img_fragtment_shader.glsl");
    51. if (shaderProgram == 0) {
    52. Log.v(TAG, "create And Link ShaderProgram Fail!");
    53. return;
    54. }
    55. //使用着色器源程序
    56. glUseProgram(shaderProgram);
    57. /************** 着色器变量 **************/
    58. //从着色器程序中获取各个变量
    59. aPosition = glGetAttribLocation(shaderProgram, "aPosition");
    60. aTexCoord = glGetAttribLocation(shaderProgram, "aTexCoord");
    61. sampler = glGetUniformLocation(shaderProgram, "sampler");
    62. //将片段着色器的采样器(纹理属性:sampler)设置为0号单元
    63. glUniform1i(sampler, 0);
    64. vertexBuffer = ShaderUtils.getFloatBuffer(vertexData);
    65. textureBuffer = ShaderUtils.getFloatBuffer(textureData);
    66. //创建纹理对象
    67. glGenTextures(textureId.length, textureId, 0);
    68. TextureUtils.LoadTexture(mContext, textureId[0], R.drawable.bg2);
    69. }
    70. @Override
    71. public void onSurfaceChanged(GL10 gl, int width, int height) {
    72. Log.v(TAG, "onSurfaceChanged(): " + width + " x " + height);
    73. glViewport(0, 0, width, height);
    74. }
    75. @Override
    76. public void onDrawFrame(GL10 gl) {
    77. //Log.v(TAG, "onDrawFrame()");
    78. glClear(GL_COLOR_BUFFER_BIT);
    79. /********* 顶点操作 **********/
    80. glEnableVertexAttribArray(aPosition);
    81. glEnableVertexAttribArray(aTexCoord);
    82. glVertexAttribPointer(aPosition, VERTEX_POSITION_SIZE, GL_FLOAT, false, 2 * 4, vertexBuffer);
    83. glVertexAttribPointer(aTexCoord, TEXTURE_POSITION_SIZE, GL_FLOAT, false, 2 * 4, textureBuffer);
    84. /********* 纹理操作:激活、绑定 **********/
    85. glActiveTexture(GL_TEXTURE);
    86. glBindTexture(GL_TEXTURE_2D, textureId[0]);
    87. /********* 绘制 **********/
    88. //绘制vertexData.length/2即4个点
    89. glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexData.length / 2);
    90. /********* 纹理操作:解除绑定 **********/
    91. glBindTexture(GL_TEXTURE_2D, 0);
    92. //关闭顶点数组的句柄
    93. glDisableVertexAttribArray(aPosition);
    94. glDisableVertexAttribArray(aTexCoord);
    95. }
    96. }

    三.ShaderUtils相关函数:

    3.1 createAndLinkProgram()

    1. /*
    2. * 创建和链接着色器程序
    3. * 参数:顶点着色器、片段着色器程序ResId
    4. * 返回:成功创建、链接了顶点和片段着色器的着色器程序Id
    5. */
    6. public static int createAndLinkProgram(Context context, String vertexShaderFN, String fragShaderFN) {
    7. //创建着色器程序
    8. int shaderProgram = glCreateProgram();
    9. if (shaderProgram == 0) {
    10. Log.e(TAG, "Failed to create shaderProgram ");
    11. return 0;
    12. }
    13. //获取顶点着色器对象
    14. int vertexShader = loadShader(GL_VERTEX_SHADER, loadShaderSource(context, vertexShaderFN));
    15. if (0 == vertexShader) {
    16. Log.e(TAG, "Failed to load vertexShader");
    17. return 0;
    18. }
    19. //获取片段着色器对象
    20. int fragmentShader = loadShader(GL_FRAGMENT_SHADER, loadShaderSource(context, fragShaderFN));
    21. if (0 == fragmentShader) {
    22. Log.e(TAG, "Failed to load fragmentShader");
    23. return 0;
    24. }
    25. //绑定顶点着色器到着色器程序
    26. glAttachShader(shaderProgram, vertexShader);
    27. //绑定片段着色器到着色器程序
    28. glAttachShader(shaderProgram, fragmentShader);
    29. //链接着色器程序
    30. glLinkProgram(shaderProgram);
    31. //检查着色器链接状态
    32. int[] linked = new int[1];
    33. glGetProgramiv(shaderProgram, GL_LINK_STATUS, linked, 0);
    34. if (linked[0] == 0) {
    35. glDeleteProgram(shaderProgram);
    36. Log.e(TAG, "Failed to link shaderProgram");
    37. return 0;
    38. }
    39. return shaderProgram;
    40. }

    3.2 getFloatBuffer()

    1. public static FloatBuffer getFloatBuffer(float[] array) {
    2. //将顶点数据拷贝映射到 native 内存中,以便opengl能够访问
    3. FloatBuffer buffer = ByteBuffer
    4. .allocateDirect(array.length * BYTES_PER_FLOAT)//直接分配 native 内存,不会被gc
    5. .order(ByteOrder.nativeOrder())//和本地平台保持一致的字节序(大/小头)
    6. .asFloatBuffer();//将底层字节映射到FloatBuffer实例,方便使用
    7. buffer.put(array)//将顶点拷贝到 native 内存中
    8. .position(0);//每次 put position 都会 + 1,需要在绘制前重置为0
    9. return buffer;
    10. }

    四.TextureUtils相关函数

    4.1 LoadTexture()

    1. //纹理Id由外部传入
    2. public static void LoadTexture(Context context, int textureId, int bitmapResId) {
    3. //绑定纹理:将纹理放到当前单元的 GL_TEXTURE_BINDING_EXTERNAL_OES 目标对象中
    4. glBindTexture(GL_TEXTURE_2D, textureId);
    5. //配置纹理:过滤方式
    6. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    7. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    8. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    9. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    10. /************* bitmap **************/
    11. //获取图片的 bitmap
    12. Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmapResId);
    13. //绑定 bitmap 到textureIds[1]纹理
    14. GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
    15. bitmap.recycle();//用完及时回收
    16. //解绑纹理 指的是离开对 纹理的配置,进入下一个状态
    17. glBindTexture(GL_TEXTURE_2D, 0);
    18. }

    五.着色器代码

    5.1 img_vertex_shader.glsl

    1. #version 300 es
    2. layout (location = 0) in vec4 aPosition; //把顶点坐标给这个变量, 确定要画画的形状
    3. layout (location = 1) in vec4 aTexCoord; //接收纹理坐标,接收采样器采样图片的坐标
    4. //传给片元着色器 像素点
    5. out vec2 vTexCoord;
    6. void main()
    7. {
    8. //内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
    9. gl_Position = aPosition;
    10. vTexCoord = aTexCoord.xy;
    11. }

    5.2 img_fragtment_shader.glsl

    1. #version 300 es
    2. #extension GL_OES_EGL_image_external_essl3 : require
    3. precision mediump float;
    4. in vec2 vTexCoord; //纹理坐标,图片当中的坐标点
    5. uniform sampler2D sampler; //图片,采样器
    6. out vec4 outColor;
    7. void main(){
    8. outColor = texture(sampler, vTexCoord);
    9. }

    六.Render、GLSurfaceView等实现过程

    GLRender及其在GLSurfaceView中的设置:

    1. mGLSurfaceView = rootView.findViewById(R.id.gl_SurfaceView);
    2. //设置GLES版本
    3. mGLSurfaceView.setEGLContextClientVersion(3);
    4. //创建Render对象,并将其设置到GLSurfaceView
    5. mImgRender = new ImgRender(getActivity());
    6. mGLSurfaceView.setRenderer(mImgRender);
    7. mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

    还有GLSurfeaceView在Activity或Fragment中的加载,这些常规操作的代码就不再详细演示了。根据自己实际开发过程实现就行。

    七.注意点

    1.片段着色器中采样器的使用

    有一点需要注意的是,在之前实现相机预览的片段着色器代码中,使用的采样器是:

    uniform samplerExternalOES sCameraTexture;

    现在渲染图片时使用的采样器是:

    uniform sampler2D sampler; 

    在实现OpenGLES纹理贴图过程中,最初直接延用了实现相机预览时的采样器samplerExternalOES,但是图片始终无法渲染成功。

    这是因为"采样器:samplerExternalOES"是OpenGLES中专门用来采样YUV数据的,所以在实现相机预览时要使用它,但是渲染图片时,就要用常规的"采样器Sampler2D"了。

    八.结束语

    单纹理贴图的实现过程到此就讲解结束了

    最终的实现效果如博文开始的效果展示

  • 相关阅读:
    Himall商城文件帮助类IOHelper(1)
    Opencv Python图像处理笔记一:图像、窗口基本操作
    Ubuntu安装深度学习环境相关(yolov8-python部署)
    65_Pandas显示设置(小数位数、有效数字、最大行/列数等)
    边缘计算、云计算、雾计算在物联网中的作用
    洛谷题单 Part2.2 排序算法(十种常见的排序算法)
    php反序列化个人笔记
    10个最受欢迎的HDR环境贴图下载站
    2.9 GBDT模型(上篇)
    小程序中会员如何绑定身份证信息
  • 原文地址:https://blog.csdn.net/geyichongchujianghu/article/details/132928008