[关闭]
@cxm-2016 2016-11-21T12:40:38.000000Z 字数 7486 阅读 7801

OpenGL-ES 3.0学习指南(五)——一个三角形

OpenGL-ES 0

版本:1
作者:陈小默
版权声明:禁止商用,禁止转载

该文章仅被发布于作业部落(原)简书


上一篇:OpenGL-ES 3.0学习指南(五)——EGL基础


四、第一个三角形


图4.1-1
在开始书写程序之前,我们先看一下目录结构是什么样的(如下图):
图4.1-2

4.1 TriabgleLib

该文件是Java文件,其内容如下,不再详细解释。

  1. /**
  2. * 陈小默 16/10/24.
  3. */
  4. public class TriangleLib {
  5. static {
  6. System.loadLibrary("triangle-lib");
  7. }
  8. //初始化本地GLES
  9. public static native boolean init();
  10. //为本地GLES设置宽和高
  11. public static native void resize(int width, int height);
  12. //用来绘制图形
  13. public static native void step();
  14. }

4.2 TriangleView

这里是一个Kotlin文件,我们在Android Studio右键New中可以找到。

  1. /**
  2. * 陈小默 16/10/24.
  3. */
  4. class TriangleView(context: Context):GLSurfaceView(context) {
  5. init {
  6. setEGLConfigChooser(8,8,8,0,16,0)
  7. setEGLContextClientVersion(3)
  8. setRenderer(TriangleRender())
  9. }
  10. class TriangleRender:GLSurfaceView.Renderer{
  11. override fun onDrawFrame(gl: GL10?) {
  12. TriangleLib.step()
  13. }
  14. override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
  15. TriangleLib.resize(width, height)
  16. }
  17. override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
  18. TriangleLib.init()
  19. }
  20. }
  21. }

在这个文件中,我们创建一个View继承GLSurfaceView,然后调用其三个方法用来启用OpenGL,
如果没有调用setEGLConfigChooser方法,则默认设置颜色模式为RGB_888,色彩深度16位。setEGLContextClientVersion方法指定了EL的版本,这里指定为了3,如果你的手机不支持OpenGL ES 3.0 则需要设置为2。接下来,我们创建了一个内部类TriangleRender,该类继承自GLSurfaceView.Renderer,这是Android SDK 中实现 OpenGL ES的方式,但是这里我们需要借助它的着色器进入本地环境。我们可以从实现中看到,我们调用的TriangleLib中的方法并没有跟这个Render产生任何交互,那么我们为什么还要通过Render去启动我们的本地方法呢?这涉及到了OpenGL ES的实现其实是基于管线的,整个操作过程就像水流一样分为上游、下游等,Render中的各个方法就是水流的不同阶段,我们的native方法是需要在特定的阶段调用的,所以才需要借助Render来标志当前进行到哪一个阶段了。但实际上,我们也可以在本地方法自己判断水流并进行相应的操作。在这里我们尽量不要使用Java中面向对象的思想去思考这些问题,应该将思想转变为面向过程。具体详细我们将会在后续章节中介绍。

Kotlin小贴士:

  • 跟随类名的括号表示主构造器,如果不提供默认以外的构造方法可以省略括号
  • 冒号表示继承关系
  • init为对象创建时的初始化函数,在构造器之后被调用

4.3 TriangleActivity

这个Activity没有别的功能,不解释

  1. class TriangleActivity : AppCompatActivity() {
  2. lateinit var triangleView :TriangleView
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. triangleView = TriangleView(this)
  6. setContentView(triangleView)
  7. }
  8. override fun onPause() {
  9. super.onPause()
  10. triangleView.onPause()
  11. }
  12. override fun onResume() {
  13. super.onResume()
  14. triangleView.onResume()
  15. }
  16. }

Kotlin小贴士:

  • var 关键字表示这是一个可读可写的方法集(如果不好理解可以当做Java中实现了Getter/Setter方法的属性),当然还有一个val关键字表示一个可读不可写的方法集(Java中只实现了Getter的属性)。在Kotlin中域被封装到了val/var的内部,对外是不可见的(包括类本身)。
  • lateinit 由于Kotlin中的安全检查机制,当你声明一个属性时,必须对其进行初始化以保证在任何位置访问其值的操作都是安全的,但是这回带来一些不便,于是其提供了此关键字用来说明我现在不想初始化。注意:访问任何未初始化的属性都是非法的。

4.4 MainActivity

我们在MainActivity的布局文件中增加一个按钮

  1. <Button
  2. android:text="绘制三角形"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:id="@+id/btn_drawTriangle" />

然后在MainActivity中给按钮添加事件监听,使其能够打开TriangleActivity。

  1. class MainActivity : AppCompatActivity() {
  2. override fun onCreate(savedInstanceState: Bundle?) {
  3. super.onCreate(savedInstanceState)
  4. setContentView(R.layout.activity_main)
  5. btn_drawTriangle.setOnClickListener {
  6. val intent = Intent(this,TriangleActivity::class.java)
  7. startActivity(intent)
  8. }
  9. }
  10. }

Kotlin小贴士:
如果你按照上一章的教程进行过配置的话,是可以在Activity中使用布局ID的。另外这里使用了Lambda语法,如果你对Lambda表达式不了解的话,可以查阅Kotlin Lambda 说明Kotlin 函数式接口

4.5 CMakeList.txt配置

  1. cmake_minimum_required(VERSION 3.4.1)
  2. #设置编译指令
  3. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
  4. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-rtti -fno-exceptions -Wall")
  5. #如果当前系统版本过低设置拒绝编译
  6. if (${ANDROID_PLATFORM_LEVEL} LESS 11)
  7. message(FATAL_ERROR "OpenGL 2 is not supported before API level 11 (currently using ${ANDROID_PLATFORM_LEVEL}).")
  8. return()
  9. elseif (${ANDROID_PLATFORM_LEVEL} LESS 18)
  10. add_definitions("-DDYNAMIC_ES3")
  11. set(OPENGL_LIB GLESv2)
  12. else ()
  13. set(OPENGL_LIB GLESv3)
  14. endif (${ANDROID_PLATFORM_LEVEL} LESS 11)
  15. # Include libraries needed for Triangle lib
  16. add_library( triangle-lib
  17. SHARED
  18. src/main/cpp/triangle-lib.cpp)
  19. target_link_libraries(triangle-lib
  20. ${OPENGL_LIB}
  21. android
  22. EGL
  23. log
  24. m)

4.6 triangle-lib.cpp

这里才是重中之重的重点,接下来我们会使用C代码实现一个在屏幕上绘制三角形的程序。在开始之前我们先进行一些说明。首先是着色器,这里的着色器跟Android中的不一样,这里的着色器是一段符合GLSL规范的字符串,其会在程序运行时被编译为GPU(图形处理器-显卡)能够识别的代码,也可以说这是一种独立的编程语言,这些着色器的目标是GPU而不是CPU。这是符合设计规范的,首先GPU是专为图像处理而设计的,其次如果将所有处理交给CPU后果可想而知。所以我们在编写着色器代码的时候可能会对其语法感到困惑,这是正常的,毕竟我们之前写的代码都是面向内存和CPU的。如果你对其中的流程和各种着色器仍有疑问,不用担心,我们会在后续章节中继续学习。接下来是我们的代码部分:

  1. #include <GLES3/gl3.h>
  2. #include <android/log.h>
  3. #define LOG_TAG "TRIANGLE-LIB"
  4. #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
  5. #include <jni.h>
  6. #include <stdlib.h>
  7. static const char VERTEX_SHADER[]=
  8. "#version 300 es\n"
  9. "layout(location = 0) in vec4 vPosition;\n"
  10. "void main(){\n"
  11. "gl_Position = vPosition;\n"
  12. "}\n";
  13. static const char FRAGMENT_SHADER[]=
  14. "#version 300 es\n"
  15. "precision mediump float;\n"
  16. "out vec4 fragColor;\n"
  17. "void main(){\n"
  18. "fragColor = vec4(1.0,0.0,0.0,1.0);\n"
  19. "}\n";
  20. static const GLfloat VERTEX[]={
  21. 0.0f,0.5f,0.0f,
  22. -0.5f,-0.5f,0.0f,
  23. 0.5f,-0.5f,0.0f
  24. };
  25. bool checkGlError(const char* funcName) {
  26. GLint err = glGetError();
  27. if (err != GL_NO_ERROR) {
  28. ALOGE("GL error after %s(): 0x%08x\n", funcName, err);
  29. return true;
  30. }
  31. return false;
  32. }
  33. GLuint createShader(GLenum shaderType, const char* src) {
  34. GLuint shader = glCreateShader(shaderType);
  35. if (!shader) {
  36. checkGlError("glCreateShader");
  37. return 0;
  38. }
  39. glShaderSource(shader, 1, &src, NULL);
  40. GLint compiled = GL_FALSE;
  41. glCompileShader(shader);
  42. glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
  43. if (!compiled) {
  44. GLint infoLogLen = 0;
  45. glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
  46. if (infoLogLen > 0) {
  47. GLchar* infoLog = (GLchar*)malloc(infoLogLen);
  48. if (infoLog) {
  49. glGetShaderInfoLog(shader, infoLogLen, NULL, infoLog);
  50. ALOGE("Could not compile %s shader:\n%s\n",
  51. shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment",
  52. infoLog);
  53. free(infoLog);
  54. }
  55. }
  56. glDeleteShader(shader);
  57. return 0;
  58. }
  59. return shader;
  60. }
  61. GLuint createProgram(const char* vtxSrc, const char* fragSrc) {
  62. GLuint vtxShader = 0;
  63. GLuint fragShader = 0;
  64. GLuint program = 0;
  65. GLint linked = GL_FALSE;
  66. vtxShader = createShader(GL_VERTEX_SHADER, vtxSrc);
  67. if (!vtxShader)
  68. goto exit;
  69. fragShader = createShader(GL_FRAGMENT_SHADER, fragSrc);
  70. if (!fragShader)
  71. goto exit;
  72. program = glCreateProgram();
  73. if (!program) {
  74. checkGlError("glCreateProgram");
  75. goto exit;
  76. }
  77. glAttachShader(program, vtxShader);
  78. glAttachShader(program, fragShader);
  79. glLinkProgram(program);
  80. glGetProgramiv(program, GL_LINK_STATUS, &linked);
  81. if (!linked) {
  82. ALOGE("Could not link program");
  83. GLint infoLogLen = 0;
  84. glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
  85. if (infoLogLen) {
  86. GLchar* infoLog = (GLchar*)malloc(infoLogLen);
  87. if (infoLog) {
  88. glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
  89. ALOGE("Could not link program:\n%s\n", infoLog);
  90. free(infoLog);
  91. }
  92. }
  93. glDeleteProgram(program);
  94. program = 0;
  95. }
  96. exit:
  97. glDeleteShader(vtxShader);
  98. glDeleteShader(fragShader);
  99. return program;
  100. }
  101. GLuint program;
  102. extern "C"{
  103. JNIEXPORT jboolean JNICALL Java_com_github_cccxm_gles_model_TriangleLib_init(JNIEnv* env, jobject obj);
  104. JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_resize(JNIEnv* env, jobject obj, jint width, jint height);
  105. JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_step(JNIEnv* env, jobject obj);
  106. }
  107. JNIEXPORT jboolean JNICALL Java_com_github_cccxm_gles_model_TriangleLib_init(JNIEnv* env, jobject obj){
  108. program = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
  109. if (!program){
  110. ALOGE("程序创建失败");
  111. return JNI_FALSE;
  112. }
  113. glClearColor(0,0,0,0);
  114. return JNI_TRUE;
  115. }
  116. JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_resize(JNIEnv* env, jobject obj, jint width, jint height){
  117. glViewport(0, 0, width, height);
  118. glClear(GL_COLOR_BUFFER_BIT);
  119. }
  120. JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_step(JNIEnv* env, jobject obj){
  121. glClear(GL_COLOR_BUFFER_BIT);
  122. glUseProgram(program);
  123. glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,VERTEX);
  124. glEnableVertexAttribArray(0);
  125. glDrawArrays(GL_TRIANGLES,0,3);
  126. }

到现在为止我们的第一个三角形程序就完成了,现在运行程序就能看到开篇图片上的三角形了。


下一篇:NDK开发OpenGL ES 3.0(三)——着色器基础


[1]Donald Hearn,M.Pauline Barker.计算机图形学 第四版(蔡士杰 译).北京:电子工业出版社
[2]Dave Shreiner,Graham Sellers.OpenGL编程指南 第八版(王锐 译).北京:机械工业出版社
[3]Dan Ginsburg,Budirjanto Purnomo.OpenGL ES 3.0 编程指南 第二版(姚军 译).北京:机械工业出版社
[4]GoogleSamples - Android NDK

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注