Android之OpenGL学习

2023-11-02

1.前言

本来一直就想做音视频开发这方面,包括我的毕业论文也是,可惜却太久没有接触有些陌生,遂写文章来复习。在这里有几个目标需要订下:第一个就是需要实现相机使用OpenGL ES进行渲染,第二个就是搞定实现一些初步的滤镜,第三个就是了解视频的各种知识。

2.正文

2.1大概介绍

老规矩,从绘制一个三角形开始。大家不防回想一下,我们使用之前是怎么绘制三角形(以Android自定义View举例)。首先就是new一个paint和path,然后paint只需要颜色,而path则是只需要提供坐标即可。如下

public class TriangleView extends View {
    private final Paint paint = new Paint();
    private final Path path = new Path();

    public TriangleView(Context context) {
        super(context);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLUE);
        paint.setAntiAlias(true);

        path.moveTo(120, 120);
        path.lineTo(100, 200);
        path.lineTo(140, 200);
        path.close();
    }

    public TriangleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TriangleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public TriangleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLUE);
        paint.setAntiAlias(true);

        path.moveTo(120, 120);
        path.lineTo(100, 200);
        path.lineTo(140, 200);
        path.close();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(path, paint);
    }
}

其中paint和path为了防止在onDraw中不断创建而消耗性能,故直接final,在构造函数中进行设置。其中moveTo中的x120y120则是顶点坐标,而x100y200和x140y200则是剩下的坐标,十分简单。

显示效果如下

一个小小的三角形就在上面,很简单的例子,我们只需要知道坐标即可。但是OpenGL ES大为不同,麻烦的很。好在现在Java上能够直接操作却也还算简单。

首先在AndroidManifest.xml中添加uses-feature

    <!-- Tell the system this app requires OpenGL ES 3.1. -->
    <uses-feature android:glEsVersion="0x00030001" android:required="true" />

 由于我的测试手机是百分百支持的,所以我就不会去检测是否支持,其他的兼容性需要大家自己去处理。我们直接new一个GLSurfaceView,然后设置一个Renderer即可。

        new GLSurfaceView.Renderer(){

            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {

            }

            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {

            }

            @Override
            public void onDrawFrame(GL10 gl) {

            }
        };

其中onSurfaceCreated是创建的时候callback一次,也仅此一次。在这里我们一般会通过glClearColor设置清屏的颜色,其参数为rgba。而onSurfaceChanged则是Surface发生改变的时候,比如横竖屏的改变,我们则是使用glViewport函数设置绘制区域。这当中有两个个坐标系需要记住,那就是首先Viewport的坐标系。

            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {
                gl.glViewport(0, 0, width / 2, height / 2);
            }

这个代码很明显就是只使用了1/4的屏幕,但是是其原点是处于手机的坐下,这很符合我们的直觉。但是当我们绘制的时候,这个坐标系却不太一样了。它是将屏幕四个顶点都是1,只不过有正负之分,上一张Google的原图。

比如一台IQOO Z6的手机,他的像素是2408*1080,那么他的x系1单位的像素是540px,而y系1单位则是1204px。最后一个则是重新绘制时会callback。然后直接setContentView即可。

2.2正式开始

我们首先需要知道的是OpenGL是状态机,和我们平时的面向对象编程都是不太一样,这也导致了我最开始编程的时候其实是没有看的太懂的。这是因为我们是使用CPU来控制GPU进行编程,要首先切换GPU的状态,然后才能去控制GPU进行相对应的计算(应该是?个人理解,如果不正确,麻烦您指正,我将会修改),这就是为什么OpenGL有点模版化,但是却又不符合常理。为此,我们将数据送入GPU的方式也是颇为奇特的。

    public static FloatBuffer createFloatBuffer(final float[] data) {
        FloatBuffer dst = ByteBuffer.allocateDirect(data.length << 2)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        dst.put(data).position(0);
        return dst;
    }

我们首先需要使用ByteBuffer的allocateDirect去申请一个堆外内存,并且确定大小段额和数据写入、设置position为0。然后才能使用glVertexPotiner来讲数据送入GPU。而顶点的坐标位置和Color都是这样子送进去的。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GLSurfaceView glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
                gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            }

            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {
                gl.glViewport(0, 0, width, height);
            }

            @Override
            public void onDrawFrame(GL10 gl) {
                gl.glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
                float[] vVertices = {
                        0.0f, 0.5f, 0.0f,
                        -0.5f, -0.5f, 0.0f,
                        0.5f, -0.5f, 0.0f,
                };
                float[] vColor = {
                        1, 1, 0, 1,
                        0, 1, 1, 1,
                        1, 0, 1, 1
                };
                FloatBuffer vertexBuffer = createFloatBuffer(vVertices);
                FloatBuffer colorBuffer = createFloatBuffer(vColor);

                gl.glEnableClientState(GL_VERTEX_ARRAY);
                gl.glEnableClientState(GL_COLOR_ARRAY);

                gl.glVertexPointer(3, GL_FLOAT, 0, vertexBuffer);
                gl.glColorPointer(4, GL_FLOAT, 0, colorBuffer);
                gl.glDrawArrays(GL_TRIANGLES, 0, 3);
                gl.glFinish();
            }
        });
        setContentView(glSurfaceView);
    }

    public static FloatBuffer createFloatBuffer(final float[] data) {
        FloatBuffer dst = ByteBuffer.allocateDirect(data.length << 2)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        dst.put(data).position(0);
        return dst;
    }
}

显示效果如下,效果还是不错的。

但是随着使用,大家会发现一个问题,那就是shader去哪里?是的,不少人之前学习时都会对这个shader发生困惑,但是我这里又怎么连shader都没有呢?接下来,我将会使用shader再写一个。但是在此之前,我需要先上一张图片(ps:Hello-Triangle)。

请牢记这张图,他将会贯彻始终。首先请记住,我们是在CPU里面编码,然后去控制GPU计算,这其中我们也可以编写一段用于GPU的代码,然后通过CPU传入,而这个代码在OpenGL叫做shader。所以,在这里我们首先需要创建program,然后创建两个shader,将他们进行编译和链接,并且使用和赋值。这两个shader分别是vertexShader和fragmentShader,图上还有geometryShader,但是我们并不常用。其中vertxShader是顶点着色器,这个shader程序主要是负责输出顶点的坐标位置信息。我们来看一端最经典的vertex shader example code。

#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

大家可能看到一头雾水,我们不是需要输出顶点的坐标位置信息,返回值呢?其实就是这个gl_Position,这个是已经定义好的,当你把值赋给gl_Position时就已经是输出顶点的位置坐标信息了,随后就是大家也在想另一个问题,那么就是我们如何把我们的数据传给他呢,并且这个gl_Position只能传一个,我们的坐标那么多,怎么处理的?

我们来一一解惑。首先就是传输和找到我们传输在这个程序里面的数据,仔细看代码,大家可以看到上面有一个我们写的变量信息。

layout (location = 0) in vec3 aPos;

in关键词表示这个是我们传入参数,vec3表示这是个float数组,最后的数字表示数组长度为3;layout ( location = 0 )表示属性位置为0,所以我们使用glVertexAttribPointer传输的时候第一个参数index就可以设置为0了。这当然还有另外一种就是直接使用attribute关键词,然后使用glGetAttribLocation获取index了。

#version 330 core
attribute vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
        int location = GLES32.glGetAttribLocation(programId, "aPos");
        GLES32.glEnableVertexAttribArray(location);
        GLES32.glVertexAttribPointer(location, 3, GL_FLOAT, false,0, buffer)

例子如上。随后就是第二个问题,那就是大家不用担心,由于GPU的特殊性,确实是一次只用处理一个即可。fragmentShader则是片段着色器,看图片也知道,这个是负责上色,我们主要赋值给gl_FragColor即可。

接下来就上我写的代码。

public class MainActivity extends AppCompatActivity {
    private final static int COORDS_PER_VERTEX = 3;
    private final static int VERTEX_Stride = COORDS_PER_VERTEX << 2; // 4 bytes per vertex
    final static String vertexShaderSource = "attribute vec3 aPos;" +
            "attribute vec3 aColor;" +
            "varying vec3 outColor;\n" +
            "void main() {" +
            "   gl_Position = vec4(aPos, 1.0f);" +
            "   outColor = aColor;" +
            "}";
    final static String fragmentShaderSource = "varying vec3 outColor;\n" +
            "void main() {" +
            "   gl_FragColor = vec4(outColor, 1.0f);" +
            "}";
    final static float[] vertexWithColor = {
            0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
            -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
            0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GLSurfaceView glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 0, 0);
        glSurfaceView.setEGLContextClientVersion(3);
        glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
                int vertexShader = createShader(GLES30.GL_VERTEX_SHADER, vertexShaderSource);
                int fragmentShader = createShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderSource);

                int programId = GLES30.glCreateProgram();
                GLES30.glAttachShader(programId, vertexShader);
                GLES30.glAttachShader(programId, fragmentShader);
                GLES30.glLinkProgram(programId);
                GLES30.glUseProgram(programId);

                int[] VBOs = new int[1];
                GLES30.glGenBuffers(1, VBOs, 0);
                GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, VBOs[0]);
                FloatBuffer floatBuffer = createFloatBuffer(vertexWithColor);
                GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexWithColor.length << 2,
                        floatBuffer, GL_STATIC_DRAW);

                int positionHandle = GLES30.glGetAttribLocation(programId, "aPos");
                GLES30.glEnableVertexAttribArray(positionHandle);
                GLES30.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GL_FLOAT, false, VERTEX_Stride << 1, 0);
                int colorHandle = GLES30.glGetAttribLocation(programId, "aColor");
                GLES30.glEnableVertexAttribArray(colorHandle);
                GLES30.glVertexAttribPointer(colorHandle, COORDS_PER_VERTEX, GL_FLOAT, false, VERTEX_Stride << 1, VERTEX_Stride);
            }

            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {
                GLES30.glViewport(0, 0, width, height);
                GLES30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            }

            @Override
            public void onDrawFrame(GL10 gl) {
                GLES30.glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

                GLES30.glDrawArrays(GL_TRIANGLES, 0, 3);
            }
        });
        setContentView(glSurfaceView);
    }

    public static int createShader(int type, String source) {
        int shaderId = GLES30.glCreateShader(type);
        GLES30.glShaderSource(shaderId, source);
        GLES30.glCompileShader(shaderId);
        int[] compiled = new int[1];
        GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, compiled, 0);
        if (compiled[0] == 0) {
            GLES30.glDeleteShader(shaderId);
            GLES30.glGetShaderInfoLog(shaderId);
            shaderId = 0;
        }
        return shaderId;
    }

    public static FloatBuffer createFloatBuffer(final float[] data) {
        FloatBuffer dst = ByteBuffer.allocateDirect(data.length << 2)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        dst.put(data).position(0);
        return dst;
    }
}

代码效果

 

应该还是可以的,其中首先就是这两个shaderSource,本来我是想使用layout location的,但是不知道为什么却报错,识别不到vec3,然后查询Google官方资料后(绘制形状),也是使用的attribute关键词,所以就放弃挣扎了。然后解析参数

                int positionHandle = GLES30.glGetAttribLocation(programId, "aPos");
                GLES30.glEnableVertexAttribArray(positionHandle);
                GLES30.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GL_FLOAT, false, VERTEX_Stride << 1, 0);
                int colorHandle = GLES30.glGetAttribLocation(programId, "aColor");
                GLES30.glEnableVertexAttribArray(colorHandle);
                GLES30.glVertexAttribPointer(colorHandle, COORDS_PER_VERTEX, GL_FLOAT, false, VERTEX_Stride << 1, VERTEX_Stride);

其中有几个点需要注意的是这个stride的单位是4byte,所以导致,并且还要加上自身。所以这里的实际上是24。而glBufferData其中的size单位也是byte,这两个地方一定要注意,否则很难调试发现。在这里我们使用了VBO,下一步就是纹理啦。

2.3纹理部分

我们首先需要知道纹理(textures)是什么?纹理就是一个OpenGL Object,里面可以存放2D\3D图片,也可以是一张,或者多张。是为了方便我们使用所弄出来的。在Java中使用Bitmap创建纹理还是比较简单的。代码如下

    public static int createTexture(@NonNull Bitmap bitmap) {
        int[] textureHandles = new int[1];
        GLES30.glGenTextures(1, textureHandles, 0);
        int textureId = textureHandles[0];
        GLES30.glBindTexture(GL_TEXTURE_2D, textureId);

        GLES30.glTexParameterf(GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES30.glTexParameterf(GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        GLES30.glTexParameterf(GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES30.glTexParameterf(GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
        return textureId;
    }

    public Bitmap getImageFromAssets(String fileName) {
        Bitmap bitmap = null;
        try (InputStream is = getAssets().open(fileName)) {
            bitmap = BitmapFactory.decodeStream(is);
        } catch (IOException ignored) {
        }
        return bitmap;
    }

其中glTexParameterf方法,这是一个设置纹理参数的函数。可以自行看自己需求来配置,一般情况,直接copy就行,随后就是绑定纹理了。

                //texture
                int textureId = createTexture(bitmap);
                GLES30.glActiveTexture(textureId);
                GLES30.glBindTexture(GL_TEXTURE_2D, textureId);

大家可以看下,我们似乎并没有如之前那般去传入program中,这是因为OpenGL自己就帮我传入了。随后我们再看下这个shaderSourceCode。

    final static String vertexShaderSource = "attribute vec3 inPos;" +
            "attribute vec2 inTexCoord;" +
            "varying vec2 TexCoord;\n" +
            "void main() {" +
            "   gl_Position = vec4(inPos, 1.0f);" +
            "   TexCoord = inTexCoord;" +
            "}";
    final static String fragmentShaderSource = "varying vec2 TexCoord;" +
            "uniform sampler2D inTexture;\n" +
            "void main() {" +
            "   gl_FragColor = texture2D(inTexture, TexCoord);" +
            "}";

其中只有两个不同,那么就是uniform和sample2D,而sample2D就表示是纹理了,而uniform关键词则是表明是全局变量,只不过既然OpenGL自己处理,那么其实只需要明白就行了。随后就是其中的TexCoord,这个是纹理坐标系。其中2D纹理坐标系则是下图,手画。

 

再配合我的代码,大家就可以看明白了。

    final static float[] vertexWithTexture = {
            -1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
            -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, -1.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f, 0.0f
    };

我这里是一一对应的。前三个是顶点坐标,后两个则纹理坐标,所以当你想要实现颠倒或者其他操作时,可以在这里进行修改,当然,最好还是一一对应,然后再进行旋转或者其他操作以达到相同目标。最后需要讲解的就是绘制操作了。我们需要用glDrawElements进行绘制。

    final static short[] drawOrder = {0, 1, 2, 0, 2, 3};
    final static Buffer drawOrderBuffer = ByteBuffer.allocateDirect(drawOrder.length << 1)
            .order(ByteOrder.nativeOrder()).asShortBuffer().put(drawOrder).position(0);

这个是绘制顺序,分别绘制了两个三角形(我也不知道为什么是这样?莫非天下所有的图案都是以无数个三角形来进行绘制的吗?)。最后附上完整代码。

public class MainActivity extends AppCompatActivity {
    private final static int COORDS_PER_VERTEX = 3;
    private final static int VERTEX_Stride = COORDS_PER_VERTEX << 2; // 4 bytes per vertex
    final static String vertexShaderSource = "attribute vec3 inPos;" +
            "attribute vec2 inTexCoord;" +
            "varying vec2 TexCoord;\n" +
            "void main() {" +
            "   gl_Position = vec4(inPos, 1.0f);" +
            "   TexCoord = inTexCoord;" +
            "}";
    final static String fragmentShaderSource = "varying vec2 TexCoord;" +
            "uniform sampler2D inTexture;\n" +
            "void main() {" +
            "   gl_FragColor = texture2D(inTexture, TexCoord);" +
            "}";
    final static float[] vertexWithTexture = {
            -1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
            -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, -1.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f, 0.0f
    };

    final static short[] drawOrder = {0, 1, 2, 0, 2, 3};
    final static Buffer drawOrderBuffer = ByteBuffer.allocateDirect(drawOrder.length << 1)
            .order(ByteOrder.nativeOrder()).asShortBuffer().put(drawOrder).position(0);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Bitmap bitmap = getImageFromAssets("yymjr.png");
        GLSurfaceView glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 0, 0);
        glSurfaceView.setEGLContextClientVersion(3);
        glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
                int vertexShader = createShader(GLES30.GL_VERTEX_SHADER, vertexShaderSource);
                int fragmentShader = createShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderSource);

                int programId = GLES30.glCreateProgram();
                GLES30.glAttachShader(programId, vertexShader);
                GLES30.glAttachShader(programId, fragmentShader);
                GLES30.glLinkProgram(programId);
                GLES30.glUseProgram(programId);

                //texture
                int textureId = createTexture(bitmap);
                GLES30.glActiveTexture(textureId);
                GLES30.glBindTexture(GL_TEXTURE_2D, textureId);

                //vbo
                int[] VBOs = new int[1];
                GLES30.glGenBuffers(1, VBOs, 0);

                GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, VBOs[0]);
                FloatBuffer floatBuffer = createFloatBuffer(vertexWithTexture);
                GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexWithTexture.length << 2, floatBuffer, GL_STATIC_DRAW);


                int positionHandle = GLES30.glGetAttribLocation(programId, "inPos");
                GLES30.glEnableVertexAttribArray(positionHandle);
                GLES30.glVertexAttribPointer(positionHandle, 3, GL_FLOAT, false, (3 + 2) << 2, 0);
                int colorHandle = GLES30.glGetAttribLocation(programId, "inTexCoord");
                GLES30.glEnableVertexAttribArray(colorHandle);
                GLES30.glVertexAttribPointer(colorHandle, 2, GL_FLOAT, false, (3 + 2) << 2, VERTEX_Stride);
            }

            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {
                GLES30.glViewport(0, 0, width, height);
                GLES30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            }

            @Override
            public void onDrawFrame(GL10 gl) {
                GLES30.glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
                GLES30.glDrawElements(GL_TRIANGLE_STRIP, drawOrder.length, GL_UNSIGNED_SHORT, drawOrderBuffer);
            }
        });
        setContentView(glSurfaceView);
    }

    public static int createShader(int type, String source) {
        int shaderId = GLES30.glCreateShader(type);
        GLES30.glShaderSource(shaderId, source);
        GLES30.glCompileShader(shaderId);
        int[] compiled = new int[1];
        GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, compiled, 0);
        if (compiled[0] == 0) {
            GLES30.glDeleteShader(shaderId);
            GLES30.glGetShaderInfoLog(shaderId);
            shaderId = 0;
        }
        return shaderId;
    }

    public static FloatBuffer createFloatBuffer(final float[] data) {
        FloatBuffer dst = ByteBuffer.allocateDirect(data.length << 2)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        dst.put(data).position(0);
        return dst;
    }

    public static int createTexture(@NonNull Bitmap bitmap) {
        int[] textureHandles = new int[1];
        GLES30.glGenTextures(1, textureHandles, 0);
        int textureId = textureHandles[0];
        GLES30.glBindTexture(GL_TEXTURE_2D, textureId);

        GLES30.glTexParameterf(GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES30.glTexParameterf(GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        GLES30.glTexParameterf(GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES30.glTexParameterf(GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
        return textureId;
    }

    public Bitmap getImageFromAssets(String fileName) {
        Bitmap bitmap = null;
        try (InputStream is = getAssets().open(fileName)) {
            bitmap = BitmapFactory.decodeStream(is);
        } catch (IOException ignored) {
        }
        return bitmap;
    }
}

还是老样子,看下效果。

3.结尾

等待下一篇的时候我上传到github吧,应该是使用MediaCodec进行视频解码,然后外加添加水印、滤镜等功能的实现。应该不会太久?欢迎大家指正我的错误!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android之OpenGL学习 的相关文章

随机推荐

  • IDEA+Gradle创建Springboot项目整合mybatis

    1 之前用的maven管理项目 现在公司用gradle 所以自己学习创建一个 特此记录一下 前提是你得配置了gradle 1 首先是build gradle文件 buildscript ext springBootVersion 2 1 2
  • Axios使用AbortController取消请求

    从 v0 22 0 开始 Axios 支持以 fetch API 方式 AbortController 取消请求 const controller new AbortController axios get foo bar1 signal
  • VBA怎么获取单元格的内容/值(数字,文本,公式)(如需获取选中单元格内容,使用select命令即可)

    通过本地窗口可以清晰看出三者的区别 记得按 F8 走调试 直接运行本地窗口在这里出不来结果 Value是单元格的数字内容 Text是文本内容 Formula是最原本的 输入内容 Sub 宏3 测试 就不去定义变量类型了 a Range b1
  • 大语言模型生态系统:助你自由调教 AI 模型

    这些开源项目都是在语言模型领域具有重要影响力的优秀项目 它们共同的特点是强调了对大规模语言模型进行训练和推理的高效性 灵活性和可扩展性 无论是通过提供定制化的语言模型 支持并行计算和分布式训练 还是通过优化内存管理和硬件资源利用效率来提高运
  • Java import 详解

    Java import 详解 1 package 机制 Java 的 package 机制类似于 C 的 namespace 机制 在编写 Java 程序时 随着程序架构越来越大 类的个数也越来越多 这时就会发现管理程序中维护类名称也是一件
  • el-form-item rules validator validate函数传参

    validator只能传3个参数 rule value cb 如果想传入额外的参数来做校验 那么需要通过在rules上嵌套一层 传入参数 如row 之后在函数中定义validator 就可以直接用到自己需要的参数了 我这边需要的是row 校
  • Android Studio apk 文件路径

    out production
  • java实现图片与base64转换

    如果你是一个软件开发 不论前端后端工程师 图片的处理你是肯定要会的 关于图片的Base64编码 你可能有点陌生 但是这是一个软件工程师应该要掌握的知识点 现在很多网友把图片与base64转换都做成了小工具如 http www yzcopen
  • Android 使用OpenCV的三种方式(Android Studio)

    其实最早接触OpenCV是很久很久之前的事了 大概在2013年的5 6月份 当时还是个菜逼 虽然现在也是个菜逼 在那一段时间 学了一段时间的android 并不算学 一个月都不到 之后再也没接触android 而是一直在接触java web
  • linux常见报错种类

    说起来日常的故障 其实 首先应该相到的就是 备份 毕竟再怎么牢固的系统或硬件都会有故障的时候 所以 备份放第一位 作为linux运维 多多少少会碰见这样那样的问题或故障 从中总结经验 查找问题 汇总并分析故障的原因 这是一个Linux运维工
  • [春秋云镜]CVE-2022-22733

    声明 中所涉及的技术 思路和 具仅供以安全为 的的学习交流使 任何 不得将其 于 法 途以及盈利等 的 否则后果 承担 所有渗透都需获取授权 靶场介绍 Apache ShardingSphere ElasticJob UI由于返回 toke
  • Kali 2020.1版本 更新不能解析域名问题解决

    Kali 2020 1版本 更新不能解析域名问题解决 问题阐述 解决方法 1 添加DNS解析服务器的ip地址 2 重启 3 kali联网即可更新
  • IC工程师入门必学《Verilog超详细教程》(附下载)

    Verilog HDL 简称 Verilog 是一种硬件描述语言 用于数字电路的系统设计 可对算法级 门级 开关级等多种抽象设计层次进行建模 Verilog 继承了 C 语言的多种操作符和结构 与另一种硬件描述语言 VHDL 相比 语法不是
  • 用U盘给虚拟机装系统——U深度

    下载一个u深度 将要安装的系统镜像放进 重装系统 创建虚拟机 按shift 修改位如下所示 按fn f10确认 选择第二个 进行磁盘分区 开始装机 完成后关机并把启动时间修改回去 如果拔出U盘后出现以下情况 把新添加的硬盘移除即可
  • 北京的三甲医院都是定点医院吗?不列入医保卡范围不能报销?

    北京有19家三甲医院看病 用医保卡实时报销 其他的三甲医院需要在蓝本上定点后 才能报销 1 中国医学科学院北京协和医院 2 首都医科大学附属北京同仁医院 3 首都医科大学宣武医院 4 首都医科大学附属北京友谊医院 5 北京大学第一医院 6
  • 不错的在线视频下载软件

    发现了一款非常好的下载在线视频软件 而且可以跨浏览器使用 它几乎支持所有的web浏览器 如IE Chrome Firefox Opera Safari等浏览器 支持Youtube Youku Ku6 6间房 凤凰卫视视频网等在线视频网站的视
  • 破解世界数学难题!GPT-4 得出P≠NP

    Datawhale干货 编辑 陈萍 来源 机器之心 这是对 LLM for Science 一次有希望的探索 对于身处科研领域的人来说 或多或少的都听到过 P NP 问题 该问题被克雷数学研究所收录在千禧年大奖难题中 里面有七大难题 大家熟
  • 一些关于CV和deeplearning的干货链接(长期更新)

    目录 yolo系列汇总 关于batch normalization的理解 各类归一化方法的总结及代码 YJango的卷积神经网络介绍 目标检测SSD讲解 关于AP PR曲线计算的理解 内附代码 生肉 英文 解释了yolov3的forward
  • vue2与vue3有什么区别?

    今天要说的vue3基本兼容我们所熟悉的vue2代码 一 两者基本的不同点 1 vue3固然是优点多多的 其3个主要的优点有 1 按需引用 2 组合式api 更加接近原生js 更加直观 3 vue3新增的set up中没有this 也就是说v
  • Android之OpenGL学习

    1 前言 本来一直就想做音视频开发这方面 包括我的毕业论文也是 可惜却太久没有接触有些陌生 遂写文章来复习 在这里有几个目标需要订下 第一个就是需要实现相机使用OpenGL ES进行渲染 第二个就是搞定实现一些初步的滤镜 第三个就是了解视频