本篇会继续上一篇的工作开始。我们首先加载并编译前面定义的着色器,然后把它们链接在一起放在OpenGL的一个程序里。我们接下来就可以用着色器程序在屏幕上绘制矩形图形了。
加载着色器
首先我们需要新建一个帮助类,用来读取我们在raw目录下写入的着色器文件,该类命名为TextResourceReader。
object TextResourceReader {
fun readTextFromResource(context:Context,resId:Int):String{
var bufferReader:BufferedReader? = null
try {
var body = StringBuilder()
var inputStream = context.resources.openRawResource(resId)
var inputStreamReader = InputStreamReader(inputStream)
bufferReader = BufferedReader(inputStreamReader)
var line:String? = bufferReader.readLine()
while (line != null) {
body.append(line)
body.append("\n")
line = bufferReader.readLine()
}
return body.toString()
}catch (e: IOException){
throw RuntimeException("Could not open resource: $resId",e)
}catch(nfe: Resources.NotFoundException){
throw RuntimeException("Resource not found: $resId",nfe)
}finally {
bufferReader?.close()
}
}
}
定义了一个名为readTextFileFromResource()的方法,用于从资源文件中读取文本。该方法需要传入Android上下文(context)和资源标识符(resource ID)来调用此方法。
代码中检测了两种常见异常情况,资源不存在或读取资源时发生错误,并在这些情况下捕捉错误并抛出异常。
读取着色器代码
由于我们读取着色器代码需要用到Context,所以第一篇中的FirstOpenGLDemoRenderer需要改造下,添加构造函数并且在创建时传入Context。
private var context: Context
constructor(context: Context){
this.context = context
}
之后在onSurfaceCreated()方法中,glClearColor()调用之后。添加读取着色器的代码,这段新代码将用于读取顶点着色器和片段着色器代码。
var vertexShaderSource = TextResourceReader.readTextFromResource(context,R.raw.simple_vertex_shader)
var fragShaderSource = TextResourceReader.readTextFromResource(context,R.raw.simple_fragment_shader)
日志添加
当代码变得复杂时,使用日志记录可以帮助我们了解错误发生的情况和追踪问题的源头。在Android中,可以使用Log类将信息记录到系统日志中。为了灵活控制日志记录的开启和关闭,创建了一个名为LoggerConfig的新类。
object LoggerConfig {
const val ON = true
}
后续代码中将通过LoggerConfig.ON常量的值为true或false来决定是否记录日志信息。
编译着色器
我们已经成功从文件中读取出着色器的源代码。接下来的步骤是编译每个着色器。为了简化创建和编译着色器的过程,需要创建一个新的辅助类ShaderHelper,它将创建新的OpenGL着色器对象,然后编译着色器代码并返回一个代表编译后着色器代码的着色器对象。
编写的样板代码可以在后续的实践中重用。
object ShaderHelper {
const val TAG = "ShaderHelper"
fun compileVertexShader(shaderSource:String):Int{
return ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, shaderSource)
}
fun compileFragmentShader(shaderSource:String):Int{
return ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, shaderSource)
}
private fun compileShader(type:Int, shaderSource:String):Int{
}
}
接下来我们将会逐步构建compileShader方法。
1.创建一个着色器对象
在编译着色器之前,我们应该做的第一件事就是创建一个新的着色器对象,并且检查这个创建是否成功。
在compileShader()中添加如下代码。
var shaderObjectId = GLES20.glCreateShader(type)
if(shaderObjectId == 0){
if(LoggerConfig.ON){
Log.w(com.zlgspcae.firstopengldrawdemo.utils.ShaderHelper.TAG,"Could not create new shader.")
}
return 0
}
使用glCreateShader()函数创建一个新的着色器对象,并将对象的ID存储在变量shaderObjectId中。type参数指定着色器的类型,可以是GL_VERTEX_SHADER(顶点着色器)或GL_FRAGMENT_SHADER(片段着色器)。
使用类似glCreateShader()的调用创建对象,它返回一个整数值,作为OpenGL对象的引用。这个整数值在后续需要引用该对象时传回给OpenGL。返回值0表示对象创建失败,类似于Java中的null。
如果对象创建失败,返回0而不是抛出异常。OpenGL不会抛出异常,而是通过glGetError()方法来指示错误。这个方法用来查询OpenGL是否因为某个API调用导致了错误。
2.上传并编译着色器源码
上传着色器源代码
GLES20.glShaderSource(shaderObjectId,shaderSource)
当我们成功拿到着色器对象之后,就可以使用glShaderSource()函数将着色器的源代码字符串shaderCode上传到着色器对象中,上传的代码将与shaderObjectId所引用的着色器对象关联起来。然后就可以编译着色器了。
GLES20.glCompileShader(shaderObjectId)