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来介绍,这样会更加容易理解,后面我们会介绍纹理,也会需要在片段着色器中做处理。

相关推荐
松仔log1 小时前
Java中级——组合和继承
android·java·开发语言
我命由我123455 小时前
Jetpack Compose - MaterialExpressiveTheme 与 MaterialTheme、ColorScheme
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime
lilian2335 小时前
Harmony os 技术实战|拼豆制图06:收藏 ID、生成记录与重启恢复怎么不打架
android·java·数据库·harmonyos
2601_955759626 小时前
ClaudeAPI成本中心与业务标签设计指南
android·java·数据库
余衫马6 小时前
2026最新版!Android Studio 极速安装与配置指南
android·ide·android studio
AFinalStone6 小时前
Android 7系统异常问题排查(五)Framework层(下)—System Server崩溃
android·tombstone·系统异常
AFinalStone6 小时前
Android 7系统异常问题排查(八)系统追踪—Trace机制与性能诊断
android·系统异常
极客猴子10 小时前
Android录音转写频繁卡顿?多款APP长时间会议场景稳定性实测
android·人工智能·智能手机·飞书
Kapaseker10 小时前
Boolean 变量到底该怎么命名?
android·kotlin
AFinalStone10 小时前
Android 7系统异常问题排查(六)应用异常(上)—ANR机制全解
android·系统异常