美文网首页
初识:OpenGL es在Android中实战的使用【渲染一张图

初识:OpenGL es在Android中实战的使用【渲染一张图

作者: Antonylr | 来源:发表于2020-04-27 13:21 被阅读0次

    一.简单了解OpenGL es的基础知识

    Opengl 渲染图片纹理基本步骤:[使用前要了解一下基础知识哦! 更多Opengl知识:https://recomm.cnblogs.com/blogpost/4472599]
    1.编写着色器(顶点着色器Vertex Shader 和 片元着色器Fragment Shader)
    2.设置顶点,纹理坐标
    3.加载着色器
    4.创建纹理
    5.渲染图片

    Opengl的坐标系统:


    opengl坐标系统.png

    二. Android中使用OpenGL基础实战,显示一张图片。

    A. 编写顶点着色器 和 片元着色器

    顶点着色器vertex_shader.glsl

    attribute vec4 av_Position; //定义顶点坐标 向量vec4  代表x(横坐标) y(纵坐标) z(z坐标) w(焦距)
    attribute vec2 af_Position; //定义纹理坐标 向量vec2
    varying vec2 v_texPosition; 
    void main() {
        v_texPosition = af_Position;
        gl_Position = av_Position;  //gl_Position是OpenGL中提供的变量,装载顶点坐标
    }
    //attribute 只能在Vertex Shader(顶点着色器)中使用, attribute表示一些顶点的数据,这些顶点数据包含了顶点坐标,法线,纹理坐标,顶点颜色等.
    //attribute 变量来表示一些顶点的数据,如:顶点坐标,法线,纹理坐标,顶点颜色等。
    //varying[易变量] 用于Vertex Shader(顶点着色器)和Fragment Shader(纹理着色器)之间传递值
    

    片元着色器fragment_shader.glsl

    precision mediump float;
    varying vec2 v_texPosition;
    uniform sampler2D sTexture;
    void main() {
        gl_FragColor=texture2D(sTexture, v_texPosition);
    }
    //uniform 变量一般用来表示:变换矩阵,材质,光照参数和颜色等信息。它可以在vertex和fragment共享使用。(相当于一个被vertex和fragment shader共享的全局变量)
    

    把vertex_shader.glsl 和 fragment_shader.glsl放到安卓工程中的res文件夹下的raw资源文件中。

    B. 编写Shader(着色器)工具类

    1.获取 raw文件下的 .glsl文件,读取文件转成String方便后续使用
    2.加载Shader(着色器)
    3.创建 并 链接 源程序(program)
    注意:OpenGL的使用过程代码中都已经注释,大体12步(1~12)

    import android.content.Context;
    import android.opengl.GLES20;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    
    /**
     * 着色器Shader工具类
     */
    public class ShaderUtil {
    
        /**
         * 获取 raw 文件下的 .glsl
         *
         * @param context 全局环境
         * @param resId   raw id
         * @return
         */
        public static String getRawResource(Context context, int resId) {
            InputStream inputStream = context.getResources().openRawResource(resId);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder stringBuilder = new StringBuilder();
            try {
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line).append("\n");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    bufferedReader.close();
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return stringBuilder.toString();
        }
    
        /**
         * 创建并加载Shader(着色器)
         *
         * @param shaderType shader类型
         * @param source     .glsl【以字符串的形式加载进入】
         * @return 0 失败   >0 成功
         */
        public static int loadShader(int shaderType, String source) {
            //1.创建Shader(着色器:顶点着色器 或  片元着色器)
            int shader = GLES20.glCreateShader(shaderType);
            if (shader != 0) {
                //2.加载shader.glsl{这里把.glsl的文件以String字符串的方式加载}   并编译shader
                GLES20.glShaderSource(shader, source);
                GLES20.glCompileShader(shader);
                //3.检查是否编译成功
                int[] compiled = new int[1];
                GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
                if (compiled[0] != GLES20.GL_TRUE) {
                    GLES20.glDeleteShader(shader);
                    shader = 0;
                }
                return shader;
            } else {
                return 0;
            }
        }
    
        /**
         * 创建 并 链接 源程序 program
         * @param vertex 顶点
         * @param fragment 纹理
         * @return 0失败  >0成功
         */
        public static int createProgram(String vertex, String fragment) {
            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertex);
            int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragment);
            if (vertexShader != 0 && fragmentShader != 0) {
                //4.创建一个渲染程序(源程序program)
                int prgram = GLES20.glCreateProgram();
                //5.将着色器程序添加到渲染程序中
                GLES20.glAttachShader(prgram, vertexShader);
                GLES20.glAttachShader(prgram, fragmentShader);
                //6.链接源程序
                GLES20.glLinkProgram(prgram);
                return prgram;
            } else {
                return 0;
            }
        }
    }
    
    C.在GLSurfaceView.Renderer 中使用OpenGL
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.opengl.GLES20;
    import android.opengl.GLSurfaceView;
    import android.opengl.GLUtils;
    import com.antony.cfav.R;
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.nio.FloatBuffer;
    import javax.microedition.khronos.egl.EGLConfig;
    import javax.microedition.khronos.opengles.GL10;
    
    public class AnRender implements GLSurfaceView.Renderer {
    
        private Context mContext;
        //顶点坐标系(-1, -1) (1, -1)  (-1, 1)  (1, 1)
        private float[] vertexData = {
                -1f, -1f,
                1f, -1f,
                -1f, 1f,
                1f, 1f
    
        };
    
        //纹理坐标系
        private float[] fragmentData = {
                0f, 1f,
                1f, 1f,
                0f, 0f,
                1f, 0f
        };
        private FloatBuffer vertexBuffer; //顶点buffer
        private FloatBuffer fragmentBuffer; //纹理buffer
        private int program; //源程序
        private int vPosition; //顶点位置
        private int fPosition; //纹理位置
        private int textureId; //纹理的ID
        private int sampler;
    
        public AnRender(Context context) {
            this.mContext = context;
            //为顶点坐标 分配本地内存地址
            //为什么要分配本地内存地址呢? 因为Opengl取顶点的时候,每一次都到内存中去取值,
            // 所以这个内存在运行过程中是不允许被java虚拟机GC回收的,我们就要把它搞成本地(底层)不受虚拟机控制的这种顶点
            vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4) //分配内存大小(分配了32个字节长度)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer()
                    .put(vertexData);
            vertexBuffer.position(0);
    
            fragmentBuffer = ByteBuffer.allocateDirect(fragmentData.length * 4)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer()
                    .put(fragmentData);
            fragmentBuffer.position(0);
        }
    
        private void initRender() {
            String vertexSource = ShaderUtil.getRawResource(mContext, R.raw.vertex_shader);
            String fragmentSource = ShaderUtil.getRawResource(mContext, R.raw.fragment_shader);
            program = ShaderUtil.createProgram(vertexSource, fragmentSource); //创建源程序 program
            //7.得到着色器中的属性  todo:我们就从源程序中获取他的属性了
            vPosition = GLES20.glGetAttribLocation(program, "av_Position"); //顶点的向量坐标 todo:一定要跟vertex_shader.glsl中的变量对应上
            fPosition = GLES20.glGetAttribLocation(program, "af_Position"); //纹理的向量坐标
            sampler = GLES20.glGetUniformLocation(program, "sTexture"); // sampler2D
    
            //a.创建纹理
            int[] textureIds = new int[1];
            GLES20.glGenTextures(1, textureIds, 0);
            //b.绑定纹理
            textureId = textureIds[0];
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
            //c.激活纹理 (激活texture0)
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            GLES20.glUniform1i(sampler, 0);
            //d.设置纹理 环绕和过滤方式
            //todo: 环绕(超出纹理坐标范围):(s==x  t==y GL_REPEAT重复)
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
            //todo: 过滤(纹理像素映射到坐标点):(缩小,放大:GL_LINEAR线性)
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            //e.把bitmap这张图片映射到Opengl上
            Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.androids);
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
            bitmap.recycle();
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);//这里 textre=0 相当于解绑了
        }
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            //TODO: OpenGL es 加载Shader
            initRender();
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            GLES20.glViewport(0, 0, width, height);
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); //这是清屏
            GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);//红色清屏
            //8.使用源程序
            GLES20.glUseProgram(program);
            //9.绑定纹理
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
            //10.使顶点属性数组有效,  使纹理属性数组有效
            GLES20.glEnableVertexAttribArray(vPosition);
            GLES20.glEnableVertexAttribArray(fPosition);
            //11.为顶点属性赋值 todo;就是把 vertexBuffer的数据给到  vPosition
            GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
            //为片元属性赋值    todo:  把textureBuffer的数据给到 fPosition
            GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, fragmentBuffer);
            //todo; 到这里 vertex_shader.glsl中的  "av_Position", "af_Position"就有数据了
            //12.绘制图形
            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);//这里 textre=0 相当于解绑了
        }
    }
    
    D. GLSurfaceView中加载 Renderer
    import android.content.Context;
    import android.opengl.GLSurfaceView;
    import android.util.AttributeSet;
    
    public class AnGLSurfaceView extends GLSurfaceView {
    
        public AnGLSurfaceView(Context context) {
            this(context, null);
        }
    
        public AnGLSurfaceView(Context context, AttributeSet attrs) {
            super(context, attrs);
            //AnRender implements GLSurfaceView.Renderer
            AnRender render = new AnRender(context);
            setEGLContextClientVersion(2);//通知默认的EGLContextFactory和EGLConfigChooser选择哪个EGLContext客户端版本。
            setRenderer(render); setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        }
    }
    
    E.布局中使用我们自定义的AnGLSurfaceView
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".activity.opengl.OpenGlDemoActivity">
    
        <com.antony.cfav.opengl.AnGLSurfaceView
            android:id="@+id/an_gl_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </LinearLayout>
    
    F.效果图如下:
    效果图.png
    源码地址:https://github.com/YuLingRui/AntonyCFAV
    如果对你有帮助,麻烦给个start。进步的道路上你我相互鼓励!

    相关文章

      网友评论

          本文标题:初识:OpenGL es在Android中实战的使用【渲染一张图

          本文链接:https://www.haomeiwen.com/subject/oxhaihtx.html