这一节我们通过画一个最简单三角形来熟悉OpenGL的一些基本概念,关于kotlin的一些语法本文就不解释了
着色器
图形渲染可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的。
顶点着色器
在OpenGL中我们的屏幕中心点的坐标为(0,0,0),并不是我们常用的左上角为(0,0)。要画一个3D的图形我们就需要他每个顶点的坐标,下面我们还是以三角形为例子
var triangleCoords = floatArrayOf(// 按逆时针方向顺序:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
)
我们只是在平面上定义所以z的坐标都是0。接下来我们定义一个顶点着色器
val vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}"
我们有了着色器但是这在Android中只是一个字符串而已所以我们需要编译这段GLSL语句,并且创建一个着色器对象。
fun loadShader(type: Int, shaderCode: String): Int {
val shader = GLES20.glCreateShader(type)
// add the source code to the shader and compile it
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
return shader
}
这样我们的顶点着色器就创建好了。
片段着色器
片段着色器所做的是计算像素最后的颜色。我们就简单设置一个颜色
var color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)
val fragmentShaderCode =
"void main() {" +
" gl_FragColor =vec4(1.0f, 0.5f, 0.2f, 1.0f);;" +
"}"
同样我们也用上面的loadShader()方法来编译着色器。到这里我们就把需要的着色器准备好了。
着色器程序
我们把着色器都创建好了该怎么用呢,这时着色器程序就出现了。我们需要创建一个着色器程序并把我们的着色器都连接到这个着色器程序中。
mProgram = GLES20.glCreateProgram()
GLES20.glAttachShader(mProgram, vertexShader)
GLES20.glAttachShader(mProgram, fragmentShader)
GLES20.glLinkProgram(mProgram)
渲染三角形
为了把定义好的顶点和颜色与着色器连接起来我们需要先使用FloatBuffer把顶点加载到缓冲中,关于nio的一些知识可以在http://ifeve.com/overview/得到很好的学习。
val bb = ByteBuffer.allocateDirect(
// (坐标数 * 4)float占四字节
triangleCoords.size * 4)
// 设用设备的本点字节序
bb.order(ByteOrder.nativeOrder())
// 从ByteBuffer创建一个浮点缓冲
vertexBuffer = bb.asFloatBuffer()
// 把坐标们加入FloatBuffer中
vertexBuffer!!.put(triangleCoords)
// 设置buffer,从第一个坐标开始读
vertexBuffer!!.position(0)
在渲染的时候我们先要GLES20.glUseProgram()确定我们使用哪个着色器程序.接下来我们要通过GLES20.glGetAttribLocation()来拿到相应的attribute变量的句柄然后就把刚才我们存下的顶点赋值给变量。
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition")
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle)
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, 3,
GLES20.GL_FLOAT, false,
12, vertexBuffer)
这里我要解释一下public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, int offset)这个方法的参数
- index
这就是我们前面拿到的句柄,这个参数指定了我们是向哪个变量赋值 - size
这个表示我们这个变量需要几个参数,向我们一个点需要三个数来表示所以我们这里写的3 - type
我们数据的类型一般GLES20.GL_FLOAT就可以了 - normalized
表示是否需要归一化,我们一般编写时就按照标准就不需要 - stride
表示了在这个数组中我们隔多少字节取一次值,我们这个每三个数字中取三个值所以我们stride为3*4是12,4是float所占的字节 - offset
就是偏移量了,因为我们是每次取三个数所以偏移量都是0,如果我们的数组是(1f,1f,1f,1f)中取后三个数那么我们的offset就是偏移第一个数的字节就是4。
最后通过glDrawArrays(GL_TRIANGLES, 0, 3);我们就把三角形画出来了
参考:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
https://developer.android.google.cn/training/graphics/opengl/shapes