OpenGL ES 着色器(5)

OpenGL ES 着色器(5)

简述

着色器是在GPU上运行的程序,它会对每一个点都执行一次程序,并且计算出每个像素需要渲染的颜色,我们主要关注着色器的怎么传递数据,在OpenGL ES中,着色器传递数据分几种场景,一种是Cpu传递数据给GPU,一种是顶点缓冲区的数据传递到着色器,还有一种是顶点着色器数据传递给片段着色器。

着色器主要有三种类型传递数据参数:

  • attribute,用于顶点缓冲区数据传递到着色器中。
  • uniform,统一变量,由CPU将数据传递到GPU。
  • varying,用于顶点着色器数据向片段着色器传递。

着色器使用

我们以实现一个颜色渐变的圆形来介绍着色器的使用。

我们之前说过OpenGL渲染都是以三角形为基础的,那应该怎么渲染圆形呢,我的第一反应就是高数里微积分,通过无数个三角形拼接起来,不过这个方案有个问题,我们需要使用多少个三角形才合适,才能让圆形足够圆滑。

虽然GPU更善于执行计算,但是其实GPU也是可以处理逻辑的,所以其实只需要在着色器里做一个简单的逻辑就可以了。

所以我们的思路就是绘制一个正方形,在片段着色器里判断坐标,如果坐标在圆形范围内则为白色, 否则就配置透明的颜色,这样就可以渲染出一个圆形了。

在开始之前,我们需要了解用到的顶点着色器和片段着色器。

顶点着色器

顶点着色器主要用于获取顶点数据,一般是从顶点缓冲区中获取,里面有内置变量gl_Position作为顶点位置的输出,顶点着色器在读取顶点数据并且做预处理后,可以将坐标信息给gl_Position作为输出。

顶点着色器会对每一个顶点都执行一次。

片段着色器

片段着色器用于计算输出的颜色,有一个内置变量gl_FragColor,作为颜色的输出,片段着色器会对每一个像素都执行一次,然后返回该像素需要显示的颜色。

片段着色器可以获取从顶点着色器中获取数据,这里传递数据有两种模式,区别为是否插值,默认是平滑的,会插值的。(就是说几个顶点中的数据会根据坐标线性平滑的渐变,我们之前在顶点缓冲区章节的渐变色三角形demo就是这个原理)

我们想要实现圆形的判断逻辑,需要在片段着色器中处理逻辑,因为片段着色器会对每个像素执行一次。

顶点数据

我们顶点数据为四个顶点,每个顶点7个数据,前三个是坐标,后四个是颜色。

使用索引缓冲区,这里和之前绘制正方形的demo差不多,这里坐标覆盖了全屏幕,作用相当于画板,获取我们会通过一个统一变量来控制圆形半径。

private float[] vertexArray = new float[] {
        -1.0f, -1.0f, 0.0f, 1f, 1f, 1f, 1f,
        1.0f, -1.0f, 0.0f, 1f, 1f, 1f, 1f,
        -1.0f, 1.0f, 0.0f, 1f, 1f, 1f, 1f,
        1.0f, 1.0f, 0.0f, 1f, 1f, 1f, 1f
};

private short[] indexArray = new short[] {
        0,1,2,
        1,2,3
};

着色器代码

顶点着色器逻辑:

两个属性,vPosition是坐标,除了直接传给gl_Position外,还通过一个varying的变量传给片段着色器vertexPosition,我们说过这个参数传递默认是会插值的,所以片段着色器拿到的值相当于每个点的坐标。

属性inputColor是从顶点缓冲区中拿的数据,用作颜色,这里通过一个varying变量outputColor传给片段着色器。

private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
                "attribute vec4 inputColor;" +
                "varying vec4 outputColor;" +
                "varying vec4 vertexPosition;" +
                "void main() {" +
                "  gl_Position = vPosition;" +
                "  vertexPosition = vPosition;" +
                "  outputColor = inputColor;" +
                "}";

片段着色器逻辑:

这里有两个传过来的参数,一个为vertexPosition,为像素点坐标位置,一个为outputColor,作为输出颜色。

还有一个统一变量radius作为圆形半径,我们以(0,0)为圆形,这里一个简单几何逻辑,x * x + y * y < radius * radius的部分就是圆形部分,属于圆形部分颜色输出outputColor,否则输出一个透明色。

private final String fragmentShaderCode =
        "precision mediump float;" +
                "varying vec4 vertexPosition;" +
                "varying vec4 outputColor;" +
                "uniform float radius;" +
                "void main() {" +
                "  if (vertexPosition.x * vertexPosition.x + vertexPosition.y * vertexPosition.y > radius * radius) {" +
                "    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);" +
                "  } else {" +
                "    gl_FragColor = outputColor;" +
                "  }" +
                "}";

顶点缓冲区数据填充

这里的逻辑和之前正方形绘制基本一样,就不细说了。

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // 清除颜色
    GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    // 创建顶点缓冲区
    int[] idBuffer = new int[2];
    GLES30.glGenBuffers(2, idBuffer, 0);
    vertexBufferId = idBuffer[0];
    elementBufferId = idBuffer[1];

    // 顶点缓冲区数据填充
    FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
    vertexBuffer.put(vertexArray);
    vertexBuffer.position(0);

    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);
    GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            vertexArray.length * 4,
            vertexBuffer,
            GLES30.GL_STATIC_DRAW
    );
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);

    ShortBuffer indexBuffer = ByteBuffer.allocateDirect(indexArray.length * 4).order(ByteOrder.nativeOrder()).asShortBuffer();
    indexBuffer.put(indexArray);
    indexBuffer.position(0);
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, elementBufferId);
    GLES30.glBufferData(
            GLES30.GL_ELEMENT_ARRAY_BUFFER,
            indexArray.length * 4,
            indexBuffer,
            GLES30.GL_STATIC_DRAW
    );
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);

    // shader
    shaderProgramId = initShaderProgram(vertexShaderCode, fragmentShaderCode);
}

顶点布局以及渲染

配置顶点缓冲区布局,前三个数作为坐标,后4个数作为颜色。

设置统一变量radius,作为圆形半径。

public void onDrawFrame(GL10 gl) {
    // 清除屏幕
    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
    // 使能着色器程序
    GLES30.glUseProgram(shaderProgramId);

    // 顶点缓冲区每个顶点前3个数作为坐标
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);
    int positionLocation = GLES30.glGetAttribLocation(shaderProgramId, "vPosition");
    GLES30.glEnableVertexAttribArray(positionLocation);
    GLES30.glVertexAttribPointer(positionLocation, 3, GLES30.GL_FLOAT, false, 7 * 4, 0);

    // 顶点缓冲区每个顶点后4个数作为颜色
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);
    int inputColorLocation = GLES30.glGetAttribLocation(shaderProgramId, "inputColor");
    GLES30.glEnableVertexAttribArray(inputColorLocation);
    GLES30.glVertexAttribPointer(inputColorLocation, 4, GLES30.GL_FLOAT, false, 7 * 4, 3 * 4);

    // 配置统一变量,圆形半径
    int radiusLocation = GLES30.glGetUniformLocation(shaderProgramId, "radius");
    GLES30.glUniform1f(radiusLocation, 0.5f);
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, elementBufferId);
    // 调用DrawCall绘制三角形
    GLES30.glDrawElements(GLES30.GL_TRIANGLES, 6, GLES30.GL_UNSIGNED_SHORT, 0);

    // 清除配置
    GLES30.glDisableVertexAttribArray(positionLocation);
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
    GLES30.glUseProgram(0);
}

效果

小结

看完本节的demo,应该对着色器的使用有了一定的,着色器的语法和C语言有一些类似,不过它其实不是C语言,而是自定义的语言,然后会编辑成显卡驱动可以理解的字节码。

OpenGL ES章节更多的都是以一些简单的demo来介绍,这样会更加容易理解,后面我们会介绍纹理,也会需要在片段着色器中做处理。

相关推荐
长亭外的少年34 分钟前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿3 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神4 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛4 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法5 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter6 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快7 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl7 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江8 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-8 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记