三、OpenGL ES给图形添加亮度

一、平滑着色

上一篇文章介绍了使用uniform来使用单一的颜色进行绘制,那么如何使用不同颜色和着色来表达一个复杂的场景呢?如果有一个方法可以在同一个三角形中混合不同的颜色,我们最终会得到一个平滑着色的三角形。

平滑着色是在顶点间完成的,OpenGL提供一个方法可以平滑的混合一条直线或者一个三角形的表面上的每个顶点的颜色。我们要使用这种类型的着色使得某一区域表现的更加明亮,例如在图形的中间要显得明亮一些,而其边缘要显得暗淡些,就好像有一盏灯在其上方挂着一样,如下图所示:

实现上图效果需要在矩形的中间定义一个顶点,然后在中间和边缘之间混合颜色。

二、引入三角形扇

当矩形中心加入顶点后,连接对角线,我们就可以划分为四个三角形

你可能会有疑问为什么只标了5个点,不应该是每个小三角形都要用3个点吗?的确每个三角形都需要3个点来表示,但是同一个顶点可以用于多个三角形, 相当于每一条边上的顶点都被2个三角形使用了,而中心的顶点就会被4个小三角共同使用。如果都要输入这些重复的坐标位置,那么就会很繁琐了。好在OpenGL可以重用这些顶点,我们将矩形正中的顶点作为所有小三角的起点坐标(0,0)4个小三角的组成如下(都是逆时针绘制的):

三角形1由顶点1,2,3组成;
三角形2由顶点1,3,4组成;
三角形3由顶点1,4,5组成;
三角形4由顶点1,5,2组成;

对应的缓冲区数组就可以定义如下:

val tableVerticesWithTriangles = floatArrayOf( 
        // Triangle Fan
        0,     0,
        -0.5f, -0.5f,
        0.5f, -0.5f,
        0.5f,  0.5f,
        -0.5f,  0.5f,
        -0.5f, -0.5f,

        // Line 1
        -0.5f, 0f,
        0.5f, 0f,

        // Mallets
        0f, -0.25f,
        0f,  0.25f
)

三、增加新的颜色属性

上面的步骤已经完成了矩形区域的位置,我们还需要加入颜色属性,后面会用到颜色来实现明亮度的控制,继续修改tableVerticesWithTriangles数组,在每个顶点上加入3个颜色分量,分别表示红、绿、蓝的颜色值

val tableVerticesWithTriangles = floatArrayOf( // Order of coordinates: X, Y, R, G, B
            // Triangle Fan
            0f, 0f, 1f, 1f, 1f, // 中心顶点,白色
            -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,  // 顶点2
            0.5f, -0.5f, 0.7f, 0.7f, 0.7f, // 顶点3
            0.5f, 0.5f, 0.7f, 0.7f, 0.7f,// 顶点4
            -0.5f, 0.5f, 0.7f, 0.7f, 0.7f,// 顶点5
            -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,  // 顶点2(为了能让1,5,2组成的三角形闭合,所以还是要重复定义顶点2)
            -0.5f, 0f, 1f, 0f, 0f,
            0.5f, 0f, 1f, 0f, 0f,
            // Mallets
            0f, -0.25f, 0f, 0f, 1f,
            0f, 0.25f, 1f, 0f, 0f
        )

上面的颜色值都是浮点数字不够清晰,那么安卓可以这么转换:

 float red = Color.red(Color.RED)/255f;
 或者
 int parsedColor = Color.parseColor("#0099cc");
 float red = Color.red(parsedColor)/255f;

因为 Color.red()、Color.green()、 Color.blue()返回值范围是[0,255],所以转成OpenGL的颜色值需要除以255。

四、新增顶点着色器和片段着色器源码文件

下一步就是编写着色器源码文件了,上一篇文章我们用到了uniform定义颜色,现在我们不需要了。定义新的顶点着色器源码文件,复制/res/raw/simple_vertex_shader.glsl文件为/res/raw/simple_vertex_shader2.glsl,内容修改如下:

attribute vec4 a_Position;
attribute vec4 a_Color;

varying vec4 v_Color;

void main()
{
    v_Color = a_Color;

    gl_Position = a_Position;
    gl_PointSize = 10.0;
}

新增了a_Color属性,同时加入了一个叫做v_Color的varying,它是可以让三角形平面上的颜色产生变化的。OpenGL在构建一条直线的时候,它会用到2个顶点来构建,并为这条直线生成片段;当构建三角形的时候就会使用三个顶点来构建,然后对于每一个生成的片段,片段着色器都会被执行一次。varying是一个特殊的变量类型,它把给它的那些值进行混合,并把混合后的值发送给片段着色器。以直线为例,如果顶点0的a_Color是红色,而顶点1的a_Color是绿色,然后把a_Color赋值给v_Color就会告诉OpenGL让每个片段着色器都接手一个混合后的颜色。接近顶点0的片段,混合后的颜色显得更红,而接近顶点1的片段,颜色就会更绿,类似于线性渐变的效果。

下面开始定义新的片段着色器的源码文件,复制/res/raw/simple_fragment_shader.glsl文件为simple_fragment_shader2.glsl,内容修改如下:

precision mediump float;
varying vec4 v_Color;

void main()
{
    gl_FragColor = v_Color;
}

这次我用varying的变量v_Color替换了原来代码中的uniform,如果那个片段属于一条直线,那么OpenGL就会用构成那条直线的两个顶点计算其混合后的颜色;如果那个片段是三角形,那么就会用三角形的三个顶点来计算其混合后的颜色。

五、关于varying如何生成每个片段上混合后的颜色

上面介绍了直线或者三角形上的每个片段混合后的颜色可以用一个varying生成,我们不仅能混合颜色,还可以给varying传递任何值,OpenGL会选取属于那条直线的2个顶点或者三角形上的3个顶点的值,平滑的在基本图元身上混合这些值,每个片段都会有一个不同的值,这种混合是使用线性插值实现的,可以理解为渐变的过程。

对于直线而言,为了计算直线上每个片段的颜色值,每个片段的颜色强度会依赖该片段与包含那个颜色的顶点的距离,距离仅仅是0~100直接的百分比,0%是左边顶点,100%是右边顶点,当我们从左向右移动,这个距离比例也会从0%向100%线性增加,如下图所示:

使用线性差值计算实际混合后的值,可以用如下公式:

blended_value = (vertex_0_value *(100% - distance_ration))+(vertex_1_value * distance_ration)

这个计算公式是应用于每个颜色分量上的,因此,如果我们处理颜色值,这个计算就会分别应用在红色、绿色、蓝色和透明度分量上,并将计算结果的结果合并成一个新的颜色值。

假设vertex_0_value为红色,它的RGB只是(1,0,0),设vertex_1_value为绿色,它的RGB值是(0,1,0),那么套入公式的计算结果值如下:

要注意到,任何时候两个颜色的权重加起来都是100%,如果红色是100%,那么绿色必然是0%。
使用一个varying就可以把任何两种颜色混合一起了,当然并不只限于颜色,任何其他属性也可以应用插值技术。

对于三角形而言,线性插值的工作原理也是一样的,只不过是需要3个点和3种颜色

对于三角形内任何给定的点,从这个点向每个顶点画一条线就可以生成三个内部三角形,这三个内部三角形的面积比例就决定了那个点上每种颜色的权重,和直线一样权重的总和是100%,那么计算公式如下:

blended_value = (vertex_0_value * vertex_0_weight)
                          +(vertex_1_value * vertex_1_weight)
                          +(vetex_2_value*(100%-vertex_0_weight-vertex_1_weight))

六、使用新的颜色属性渲染

前面的步骤,我们已经给数据增加了颜色属性,并且新增了顶点着色器和片段着色器,接下来就是编辑Renderer类了,直接上完整代码吧

package com.example.openglstudy.demo3

/**
 * @Author: mChenys
 * @Date: 2020/12/11
 * @Description: 处理平滑着色
 */
import android.content.Context
import android.opengl.GLES20.*
import android.opengl.GLSurfaceView
import com.example.openglstudy.R
import com.example.openglstudy.util.LoggerConfig
import com.example.openglstudy.util.ShaderHelper.compileFragmentShader
import com.example.openglstudy.util.ShaderHelper.compileVertexShader
import com.example.openglstudy.util.ShaderHelper.linkProgram
import com.example.openglstudy.util.ShaderHelper.validateProgram
import com.example.openglstudy.util.TextResourceReader.readTextFileFromResource
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10


class AirHockeyRenderer(private val context: Context) : GLSurfaceView.Renderer {
    private val vertexData: FloatBuffer
    private var program = 0
    private var aPositionLocation = 0
    private var aColorLocation = 0

    companion object {
        private const val A_POSITION = "a_Position"// a_Position名称对应attribute vec4 a_Position;中的名字
        private const val A_COLOR = "a_Color"// a_Color的名称对应attribute vec4 a_Color;
        private const val POSITION_COMPONENT_COUNT = 2 // 顶点分量
        private const val COLOR_COMPONENT_COUNT = 3 // 颜色分量
        private const val BYTES_PER_FLOAT = 4// 每个浮点数占的字节大小

        // 每个顶点的分量X,Y,R,G,B(包括位置分量和颜色分量)*每个分量占用的字节大小
        private const val STRIDE =
            (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT
    }

    init {
        // Vertex data is stored in the following manner:
        // The first two numbers are part of the position: X, Y
        // The next three numbers are part of the color: R, G, B
        // 加上颜色分量后的数组结构
        val tableVerticesWithTriangles = floatArrayOf( // Order of coordinates: X, Y, R, G, B
            // Triangle Fan
            0f, 0f, 1f, 1f, 1f, // 中心顶点,白色
            -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,  // 顶点2
            0.5f, -0.5f, 0.7f, 0.7f, 0.7f, // 顶点3
            0.5f, 0.5f, 0.7f, 0.7f, 0.7f,// 顶点4
            -0.5f, 0.5f, 0.7f, 0.7f, 0.7f,// 顶点5
            -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,  // 顶点2(为了能让1,5,2组成的三角形闭合,所以还是要重复定义顶点2)
            -0.5f, 0f, 1f, 0f, 0f,
            0.5f, 0f, 1f, 0f, 0f,
            // Mallets
            0f, -0.25f, 0f, 0f, 1f,
            0f, 0.25f, 1f, 0f, 0f
        )


        // 把内存从java堆复制到本地堆
        vertexData = ByteBuffer
            .allocateDirect(tableVerticesWithTriangles.size * BYTES_PER_FLOAT)
            .order(ByteOrder.nativeOrder()).asFloatBuffer()
        vertexData.put(tableVerticesWithTriangles)
    }

    override fun onSurfaceCreated(glUnused: GL10, config: EGLConfig) {
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
        // 读取着色器的代码
        val vertexShaderSource = readTextFileFromResource(context, R.raw.simple_vertex_shader2)
        val fragmentShaderSource = readTextFileFromResource(context, R.raw.simple_fragment_shader2)

        // 编译着色器源码
        val vertexShader = compileVertexShader(vertexShaderSource)
        val fragmentShader = compileFragmentShader(fragmentShaderSource)

        // 链接程序并返回程序对象
        program = linkProgram(vertexShader, fragmentShader)
        if (LoggerConfig.ON) {
            validateProgram(program)
        }

        // 使用OpenGL程序
        glUseProgram(program)

        // 获取位置属性
        aPositionLocation = glGetAttribLocation(program, A_POSITION)
        // 获取颜色属性
        aColorLocation = glGetAttribLocation(program, A_COLOR)

        // 绑定位置数据
        vertexData.position(0)
        glVertexAttribPointer(
            aPositionLocation,
            POSITION_COMPONENT_COUNT,
            GL_FLOAT,
            false,
            STRIDE,
            vertexData
        )
        glEnableVertexAttribArray(aPositionLocation)

        // 绑定颜色数据
        vertexData.position(POSITION_COMPONENT_COUNT)
        glVertexAttribPointer(
            aColorLocation,
            COLOR_COMPONENT_COUNT,// 第一个颜色是从缓冲区位置2开始
            GL_FLOAT,
            false,
            STRIDE,
            vertexData
        )
        glEnableVertexAttribArray(aColorLocation)
    }

    /**
     * onSurfaceChanged is called whenever the surface has changed. This is
     * called at least once when the surface is initialized. Keep in mind that
     * Android normally restarts an Activity on rotation, and in that case, the
     * renderer will be destroyed and a new one created.
     *
     * @param width
     * The new width, in pixels.
     * @param height
     * The new height, in pixels.
     */
    override fun onSurfaceChanged(glUnused: GL10, width: Int, height: Int) {
        // Set the OpenGL viewport to fill the entire surface.
        glViewport(0, 0, width, height)
    }

    /**
     * OnDrawFrame is called whenever a new frame needs to be drawn. Normally,
     * this is done at the refresh rate of the screen.
     */
    override fun onDrawFrame(glUnused: GL10) {
        // Clear the rendering surface.
        glClear(GL_COLOR_BUFFER_BIT)

        // 现在不需要调用glUniform4f,因为我们已经将顶点数据和a_Color关联起来了,只需要调用glDrawArrays即可,
        // OpenGL会自动从顶点数据读入颜色属性
        // Draw the table.
        glDrawArrays(GL_TRIANGLE_FAN, 0, 6)

        // Draw the center dividing line.
        glDrawArrays(GL_LINES, 6, 2)

        // Draw the first mallet.
        glDrawArrays(GL_POINTS, 8, 1)

        // Draw the second mallet.
        glDrawArrays(GL_POINTS, 9, 1)
    }


}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值