上篇文章,介绍了glsl语言的基本语法,现在我们用这些语法写个顶点着色器,和片元着色器。结合api绘制一个四边形。
为什么是四边形呢?别人的文章都是从三角形开始的,因为我想介绍绘制方式,用三角形感觉不好介绍,于是就直接来四边形。
一、基本知识
基本图元
因为在OpenGL中呢,只有点、线和三角形这三种图元,要想画一个正方形,把两个三角形拼起来即可。当然绘制其他更复杂图型的时侯会借助别的工具去构建。
OpenGLES世界坐标
屏幕中心点位原点,x轴向右,y轴向上,z轴向屏幕外,方向为正,反之为负
二、开始撸码
先说一下用opengl绘制的步骤吧:
- 初始化GLSurfaceView
- 编写顶点着色器和片元着色器
- 实现Renderer接口
- onSurfaceCreated方法:初始化顶点颜色,加载着色器,初始化纹理等操作
- onSurfaceChanged方法:设置视口,投影
- onDrawFrame方法:传值给着色器(传入顶点,颜色,纹理等),
绘制
先开始第一步,初始化GLSurfaceView
new_gl_surface.setEGLContextClientVersion(2)//设置版本,这里使用OpenGLES 2
new_gl_surface.setRenderer(NewGLRenderer())//设置自定义渲染器
new_gl_surface.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY//设置为主动渲染
创建顶点着色器和片元着色器,用glsl语言写的(上一篇文章有glsl的基本语法)
我在这里简单解释一下,为什么这么写,可以看到main方法,跟java差不多,这是程序的入口。然后定义一个顶点变量名为aPosition,后面的代码我们会给这个变量赋值(其实就是要绘制正方形的点的坐标数据),然后和矩阵uMatrix相乘(转换成3D),赋值到内置变量gl_Position 上。而这里还有vColor和aColor,aColor是java里传进来的颜色的数据,vColor是连接片元着色器的变量,将java传来的数据传到片元着色器里边,最后赋值给内置变量gl_FragColor(这是opengl的基本套路)
//顶点
val ver="attribute vec4 aPosition;" +
"uniform mat4 uMatrix;" +
"varying vec4 vColor;" +
"attribute vec4 aColor;" +
"void main() {" +
" gl_Position = uMatrix*aPosition;" +
" vColor=aColor;" +
"}";
//片元
val frag = "precision mediump float;" +//片元一定要指定精度
"varying vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
然后新建类NewGLRenderer继承Renderer,实现其三个方法
初始化顶点,加载着色器
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
val verArr = floatArrayOf(
-0.6f, 0.6f, 0.0f, //左上角坐标xyz
0.6f, 0.6f, 0.0f, // 右上角
-0.6f, -0.6f, 0.0f,//左下角
0.6f, -0.6f, 0.0f)
verBuffer = util.getFloadBuffer(verArr)
//颜色数据
val colorArr = floatArrayOf(
0f, 1f, 0f, 1f,//左上角颜色,rgba
0f, 1f, 0f, 1f,
0f, 1f, 0f, 1f,
1f, 0f, 0f, 1f)
colorBuffer = util.getFloadBuffer(colorArr)
//加载顶点和片元着色器
val verShader = loadShader(GLES20.GL_VERTEX_SHADER, ver)
val fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, frag)
var program = GLES20.glCreateProgram()
if (program != 0) {
GLES20.glAttachShader(program, verShader)
GLES20.glAttachShader(program, fragShader)
GLES20.glLinkProgram(program)
//检查有没有加载出错
val linkStatus = IntArray(1)
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0)
if (linkStatus[0] == 0) {
Log.e("gl_util-------", "link program error")
GLES20.glDeleteProgram(program)
program = 0;
}
}
//得到着色器 的变量的引用
positionHandle = GLES20.glGetAttribLocation(program, "aPosition")
colorHandle = GLES20.glGetAttribLocation(program, "aColor")
matrixHandle = GLES20.glGetUniformLocation(program, "uMatrix")
}
util的方法
fun getFloadBuffer(data: FloatArray): FloatBuffer {
val buffer = ByteBuffer.allocateDirect(data.size * 4)
buffer.order(ByteOrder.nativeOrder())
val floatBuffer = buffer.asFloatBuffer()
floatBuffer.put(data)
floatBuffer.position(0)
return floatBuffer
}
设置视口,和投影,因为OpenGL是3D的世界,需要把3D的景物呈现到屏幕中,则需要矩阵的变换操作,这个可以先这样写,要想深入的同学可以看这个文章
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
//设置视口,跟view的大小相同
GLES20.glViewport(0, 0,width, height);
val r = width.toFloat() / height
//设置透视投影
// Matrix.frustumM(projectionArray, 0,
// -r, r,//Left,right
// -1f, 1f,
// 1f, 10f);
//正交投影
Matrix.orthoM(projectionArray,0,-r,r,-1f,1f,1f,10f)
//设置相机
Matrix.setLookAtM(cameraArray, 0,
0f, 0f, 1f, //相机位置
0f, 0f, 0f,//观察点
0f, 1f, 0f);//UP向量在xyz轴上的分量
//将两个矩阵相乘,赋值到 mMatrix中
Matrix.multiplyMM(mMatrix,0,projectionArray,0,cameraArray,0)
}
最后是复制和绘制
override fun onDrawFrame(gl: GL10?) {
//清除颜色和和深度 缓存
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT or GLES20.GL_COLOR_BUFFER_BIT)
//使用某套着色器程序
GLES20.glUseProgram(program);
GLES20.glUniformMatrix4fv(matrixHandle,1,false,mMatrix,0)
//给画笔设置顶点数据
GLES20.glVertexAttribPointer(positionHandle,//GLSL的顶点变量id
3, //xyz三个值组成
GLES20.GL_FLOAT,//顶点类型
false,//(当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)。)
0, //连续顶点属性之间的偏移量。(如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0。)
verBuffer);//顶点数据
//给画笔设置顶点颜色数据
GLES20.glVertexAttribPointer(colorHandle,//GLSL的顶点变量id
4, //RGBA三个值组成
GLES20.GL_FLOAT,//顶点类型
false,
0, //偏移量
colorBuffer);//顶点颜色数据
//开启顶点和顶点颜色绘制
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(colorHandle);
//绘制,使用数组法绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,//图元(绘制方式,,,三角形绘制)
0,//从数组缓存中的哪一位开始绘制
4);//数组的顶点数据个数
}
这里使用了数组法(glDrawArrays)绘制,除了这个还有索引法(glDrawElements)可以绘制,我会在下一篇文章使用介绍,这里先解释先数组法的第一个参数的含义吧
GL_POINTS //将传入的顶点坐标作为单独的点绘制
GL_LINES //将传入的坐标作为单独线条绘制,ABCDEFG六个顶点,绘制AB、CD、EF三条线
GL_LINE_STRIP //将传入的顶点作为折线绘制,ABCD四个顶点,绘制AB、BC、CD三条线
GL_LINE_LOOP //将传入的顶点作为闭合折线绘制,ABCD四个顶点,绘制AB、BC、CD、DA四条线。
GL_TRIANGLES //将传入的顶点作为单独的三角形绘制,ABCDEF绘制ABC,DEF两个三角形
GL_TRIANGLE_FAN //将传入的顶点作为扇面绘制,ABCDEF绘制ABC、ACD、ADE、AEF四个三角形
GL_TRIANGLE_STRIP //将传入的顶点作为三角条带绘制,ABCDEF绘制ABC,BCD,CDE,DEF四个三角形
我们这里使用的是GL_TRIANGLE_STRIP,上面含义已经解释了很清楚了,这里提一下,如果是用GL_TRIANGLES来绘制的话,则需要6个顶点,因为两个三角形。大家可以试一下。(这也是我为什么要先绘制正方形的原因,要是三角形的话怎么样都是三个顶点就行了)
最後是效果图,我们最后一个顶的颜色改了,所以出现的是下面的效果