学习了OpenGL有一段时间,在绘制出属于自己的三角形之前,会接触许多理论上的知识。用简单的方式写下自己对OpenGL的一些见解。望大家取其精华去其糟粕
OpenGL需要我们至少设置一个顶点和一个片段着色器,
如果不了解什么是着色器和他的坐标规则,可以看看上一篇的内容,不然你会看的很难受=v=!!。
通俗易懂的 OpenGL ES 3.0(一)入门必备知识!!
可以去blog看:通俗易懂的 OpenGL ES 3.0(二)渲染三角形
本文deme: OpenGl30Demo
triangle_vertex.glsl
顶点着色器: 接受坐标数据 ,目的是为了确定坐标位置,并且输出传递一个颜色值
//指定版本号
#version 300 es
//设定了输入变量的位置值
layout (location = 0) in vec3 aPos;
//out输出字段,传递给片段着色器的颜色
out vec4 color;
void main()
{
// gl_Position (内置函数) 赋值位置
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
//赋值暗红色下面的参数分别是 rgba
color = vec4(0.5, 0.0, 0.0, 1.0);
}
triangle_fragment.glsl
片段着色器:接受颜色,并输出最终需要渲染的颜色值
#version 300 es
precision mediump float;
out vec4 fragColor;
//从顶点着色器传来的输入变量(名称相同、类型相同)
in vec4 color;
void main()
{
//将颜色输出
fragColor = color;
}
如果不理解,你就想象这个工厂的流水线。从一开始的顶点填充数据并传递,一直到片段接受并输出渲染,输入与输出用 In 和 out表示
OpenGL运行在EGLContext创建的GL环境。而GLSurfaceView与SurfaceView不同之处在于,它拥有这个对EGLContext的管理,创建了GL环境。所以可以用来显示我们OpenGL的渲染结果
写布局
在activity的onCreate实例化,并设置一些方法
val glSurfaceView = findViewById(R.id.gl_surface)
//设置版本号 OpenGL ES 3.0
glSurfaceView.setEGLContextClientVersion(3)
//设置渲染实现
glSurfaceView.setRenderer(TriangleRenderer())
//创建和调用requestRender()时才会刷新
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
编写我们的渲染实现
/**
* onDrawFrame:绘制时调用
* onSurfaceChanged:改变大小时调用
* onSurfaceCreated:创建时调用
*/
inner class TriangleRenderer : GLSurfaceView.Renderer {
//顶点坐标 xyz 一共三个点
private val vertices = floatArrayOf(
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f)
//当前要传递给OpenGL转换好的坐标数据
private val verticesBuffer: FloatBuffer
//当前程序
var program = 0
init {
//分配空间,并且转为FloatBuffer
verticesBuffer = OpenGlUtils.toBuffer(vertices)
}
override fun onDrawFrame(gl: GL10?) {
// 绘制背景颜色 render
GLES30.glClearColor(0.2f, 0.3f, 0.3f, 1.0f)
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
//将程序加入到OpenGLES2.0环境
GLES30.glUseProgram(program)
//绘制三角形
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3)
//禁止顶点数组的句柄
GLES30.glDisableVertexAttribArray(0)
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
//编译顶点与片段脚本文件
program = OpenGlUtils.uCreateGlProgram("triangle_vertex.glsl", "triangle_fragment.glsl", resources)
//启用三角形顶点的句柄
GLES30.glEnableVertexAttribArray(0)
//准备三角形的坐标数据
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, verticesBuffer)
}
}
OpenGlUtils 工具类
class OpenGlUtils {
companion object {
private const val TAG = "OpenGlUtils"
//通过路径加载Assets中的文本内容
private fun uRes(mRes: Resources, path: String): String? {
val result = StringBuilder()
try {
val ass = mRes.assets.open(path)
var ch: Int
val buffer = ByteArray(1024)
ch = ass.read(buffer)
while (-1 != ch) {
result.append(String(buffer, 0, ch))
ch = ass.read(buffer)
}
} catch (e: Exception) {
return null
}
return result.toString().replace("\\r\\n".toRegex(), "\n")
}
//创建GL程序
fun uCreateGlProgram(vertexSource: String, fragmentSource: String, resources: Resources): Int {
var program = GLES30.glCreateProgram()
//当读取到的数据都不为空时才加载程序
uRes(resources, vertexSource)?.let {
uRes(resources, fragmentSource)?.run {
val vertex = uLoadShader(GLES30.GL_VERTEX_SHADER, it)
if (vertex == 0) return 0
val fragment = uLoadShader(GLES30.GL_FRAGMENT_SHADER, this)
if (fragment == 0) return 0
if (program != 0) {
GLES30.glAttachShader(program, vertex)
GLES30.glAttachShader(program, fragment)
GLES30.glLinkProgram(program)
val linkStatus = IntArray(1)
GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0)
if (linkStatus[0] != GLES30.GL_TRUE) {
glError(1, "Could not link program:" + GLES30.glGetProgramInfoLog(program))
GLES30.glDeleteProgram(program)
program = 0
}
}
}
}
return program
}
private fun glError(code: Int, index: Any) {
if (BuildConfig.DEBUG && code != 0) {
Log.e(TAG, "glError:$code---$index")
}
}
//加载shader
private fun uLoadShader(shaderType: Int, source: String): Int {
var shader = GLES30.glCreateShader(shaderType)
if (0 != shader) {
GLES30.glShaderSource(shader, source)
GLES30.glCompileShader(shader)
val compiled = IntArray(1)
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0)
if (compiled[0] == 0) {
glError(1, "Could not compile shader:$shaderType")
glError(1, "GLES30 Error:" + GLES30.glGetShaderInfoLog(shader))
GLES30.glDeleteShader(shader)
shader = 0
}
}
return shader
}
/**
* Buffer初始化
*/
fun toBuffer(pos: FloatArray): FloatBuffer {
val a = ByteBuffer.allocateDirect(pos.count() * 4)
a.order(ByteOrder.nativeOrder())
val mVerBuffer = a.asFloatBuffer()
mVerBuffer.put(pos)
mVerBuffer.position(0)
return mVerBuffer
}
}
}
做完上面的步骤就可以渲染出属于自己的三角形啦!!!
其实无非就是编译着色器,给着色器赋值,然后配合GLSurfaceView,在合适得回调初始化OpenGL,并渲染。
当然这只是最基本的步骤,一个合理的渲染应该还得配合VAO,VBO,EBO技术,提高我们的渲染效率。下篇就来说说这几个技术 -v-!!!
demo:OpenGl30Demo
强烈推荐的文章,虽然不是Android的,但是里面有很多关于OpenGL的一些解释。认真看完吧
Learnopengl-cn 你好三角形