OpenGL 学习教程
Android OpenGL ES 学习(一) – 基本概念
Android OpenGL ES 学习(二) – 图形渲染管线和GLSL
Android OpenGL ES 学习(三) – 绘制平面图形
Android OpenGL ES 学习(四) – 正交投屏
Android OpenGL ES 学习(五) – 渐变色
Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序
Android OpenGL ES 学习(七) – 纹理
代码工程地址: https://github.com/LillteZheng/OpenGLDemo.git
这次要完成的效果:
前面的代码中,我们的颜色是写死一种的,如何实现上面的渐变色呢?
这里就需要用到光栅化:
再复习一下光栅化的概念:它会图元映射成屏幕上相应的像素,生成供片段着色器使用上色的片段。
前面说道,顶点数据不止包含位置,还有其他信息,所以,在绘制顶点位置的时候,也传递顶点颜色,由 OpenGL 实现栅格化的效果。
这里,你可能会有疑惑,传递了三个颜色,也应该也是三个颜色啊,怎么会有渐变色呢?
带着疑问,我们来试试。
一. 着色器代码
前面说道,GLSL 在3.0 使用 in 和 out 来在着色器之间,传递数值。所以,我们在顶点着色器中使用 out 定义相同名字的颜色,在片段着色器中,使用 in 接收端相同的名字的颜色值。
private const val VERTEX_SHADER = """#version 300 es
layout(location = 0) in vec4 a_Position;
// mat4:4×4的矩阵
uniform mat4 u_Matrix;
//定义可以给外部赋值的顶点数据
layout(location = 1) in vec4 a_Color;
//给片段着色器的颜色顶点
out vec4 vTextColor;
void main()
{
// 矩阵与向量相乘得到最终的位置
gl_Position = u_Matrix * a_Position;
gl_PointSize = 30.0;
//传递给片段着色器的颜色
vTextColor = a_Color;
}
"""
private const val FRAGMENT_SHADER = """#version 300 es
precision mediump float;
out vec4 FragColor;
//接收端顶点着色器的数据,名字要相同
in vec4 vTextColor;
void main()
{
FragColor = vTextColor;
}
"""
1.1 定义三角形的顶点位置和颜色
private val POINT_DATA = floatArrayOf(
//三角形,用三个分量,z 分量为 0
0f,0.5f,0f,
-0.5f,-0.5f,0f,
0.5f,-0.5f,0f
)
private val COLOR_DATA = floatArrayOf(
//颜色值 RGB
1f,0.5f,0.5f,
1f,0f,1f,
0f,0.5f,1f
)
//加载到内存
private var vertexData = BufferUtil.createFloatBuffer(POINT_DATA)
private var colorData = BufferUtil.createFloatBuffer(COLOR_DATA)
1.2 关联和使用顶点索引数据:
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
GLES30.glClearColor(1f, 1f, 1f, 1f)
makeProgram(VERTEX_SHADER, FRAGMENT_SHADER)
uMatrix = getUniform(U_MATRIX)
GLES30.glVertexAttribPointer(
0, 3, GLES30.GL_FLOAT,
false, 0, vertexData
)
GLES30.glEnableVertexAttribArray(0)
GLES30.glVertexAttribPointer(
1, 3, GLES30.GL_FLOAT,
false, 0, colorData
)
GLES30.glEnableVertexAttribArray(1)
}
1.3 绘制:
override fun onDrawFrame(gl: GL10?) {
//步骤1:使用glClearColor设置的颜色,刷新Surface
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,3)
}
效果:
嗯嗯。。。 ,是的,为啥是渐变色的?不是传了三个颜色吗?
看看官网的解释:
这个图片可能不是你所期望的那种,因为我们只提供了3个颜色,而不是我们现在看到的大调色板。这是在片段着色器中进行的所谓片段插值(Fragment Interpolation)的结果。当渲染一个三角形时,光栅化(Rasterization)阶段通常会造成比原指定顶点更多的片段。光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置。
基于这些位置,它会插值(Interpolate)所有片段着色器的输入变量。比如说,我们有一个线段,上面的端点是绿色的,下面的端点是蓝色的。如果一个片段着色器在线段的70%的位置运行,它的颜色输入属性就会是一个绿色和蓝色的线性结合;更精确地说就是30%蓝 + 70%绿。
这正是在这个三角形中发生了什么。我们有3个顶点,和相应的3个颜色,从这个三角形的像素来看它可能包含50000左右的片段,片段着色器为这些像素进行插值颜色。
什么意思呢,我的理解是,这个三角形在光栅化的时候,被分割成N多个小像素点,再填充颜色时候,也是按像素去填充的,从三角形的颜色来看,也可以知道,它是从酒红色过度到蓝色的。
为了验证这个说法也比较简单,我们绘制一条先,它只有两个点,红色和蓝色,看看表现如何,修改顶点数据为线:
private val POINT_DATA = floatArrayOf(
//线段
-0.5f,0f,0f,
0.5f,0f,0f
)
private val COLOR_DATA = floatArrayOf(
//颜色值 RGB
1f,0f,0f,
0f,0f,1f,
)
绘制那里从三角形改成线
override fun onDrawFrame(gl: GL10?) {
//步骤1:使用glClearColor设置的颜色,刷新Surface
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
GLES30.glLineWidth(10f)
GLES30.glDrawArrays(GLES30.GL_LINES,0,2)
}
二. 优化数据
上面的数据,位置和颜色是分开的,一个位置数组,对应一个颜色数组,他们需要一一对应。
但我们可以用另外一种方式,把位置和颜色放到同个数组里面,如:
private val POINT_COLOR_DATA = floatArrayOf(
//定点+颜色
0f,0.5f,0f,1f,0.5f,0.5f,
-0.5f,-0.5f,0f,1f,0f,1f,
0.5f,-0.5f,0f,0f,0.5f,1f
)
private var vertexData = BufferUtil.createFloatBuffer(POINT_COLOR_DATA)
这样,我们只需要一个数组,加载一次内存就可以了。
修改加载索引的方式:
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
GLES30.glClearColor(1f, 1f, 1f, 1f)
makeProgram(VERTEX_SHADER, FRAGMENT_SHADER)
uMatrix = getUniform(U_MATRIX)
vertexData.position(0)
//步进为 24
GLES30.glVertexAttribPointer(
0, 3, GLES30.GL_FLOAT,
false, 24, vertexData
)
GLES30.glEnableVertexAttribArray(0)
//颜色地址从3开始,前面3个为位置
vertexData.position(3)
GLES30.glVertexAttribPointer(
1, 3, GLES30.GL_FLOAT,
false, 24, vertexData
)
GLES30.glEnableVertexAttribArray(1)
}
两个点要解释:
步进为啥是24:
顶点着色器允许我们指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时,它还的确意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。所以,我们必须在渲染前指定OpenGL该如何解释顶点数据。
数据之间是紧密排列的,所以,当只有顶点数据的时候,我们认为它是正确能被获取的:
- 位置数据被储存为32位(4字节)浮点值。
- 每个位置包含3个这样的值。
- 在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
- 数据中第一个值在缓冲开始的位置。
从这里的解释来看,我们第一次完成的渐变色,也可以修改成:
//旧方案
GLES30.glVertexAttribPointer(
0, 3, GLES30.GL_FLOAT,
false, 0, vertexData
)
// 根据步进定义,定个分量个数 * 4(字节)
GLES30.glVertexAttribPointer(
0, 3, GLES30.GL_FLOAT,
false, 3 * 4, vertexData
)
也能正常绘制渐变色的三角形。
现在插入了颜色值,所以它的步进为 6 * 4 = 24:
vertexData.position(3)
对每个顶点属性来说,他们的其实位置都不同,位置默认为0,而颜色值的偏移量是在位置之后,所以偏移量为3。
这样,渐变色我们就学习完了。
参考:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/
https://juejin.cn/post/7145094035521470500