@cxm-2016
2016-11-21T12:40:38.000000Z
字数 7486
阅读 7725
OpenGL-ES
0
版本:1
作者:陈小默
版权声明:禁止商用,禁止转载
上一篇:OpenGL-ES 3.0学习指南(五)——EGL基础
在开始书写程序之前,我们先看一下目录结构是什么样的(如下图):
该文件是Java文件,其内容如下,不再详细解释。
/**
* 陈小默 16/10/24.
*/
public class TriangleLib {
static {
System.loadLibrary("triangle-lib");
}
//初始化本地GLES
public static native boolean init();
//为本地GLES设置宽和高
public static native void resize(int width, int height);
//用来绘制图形
public static native void step();
}
这里是一个Kotlin文件,我们在Android Studio右键New中可以找到。
/**
* 陈小默 16/10/24.
*/
class TriangleView(context: Context):GLSurfaceView(context) {
init {
setEGLConfigChooser(8,8,8,0,16,0)
setEGLContextClientVersion(3)
setRenderer(TriangleRender())
}
class TriangleRender:GLSurfaceView.Renderer{
override fun onDrawFrame(gl: GL10?) {
TriangleLib.step()
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
TriangleLib.resize(width, height)
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
TriangleLib.init()
}
}
}
在这个文件中,我们创建一个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为对象创建时的初始化函数,在构造器之后被调用
这个Activity没有别的功能,不解释
class TriangleActivity : AppCompatActivity() {
lateinit var triangleView :TriangleView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
triangleView = TriangleView(this)
setContentView(triangleView)
}
override fun onPause() {
super.onPause()
triangleView.onPause()
}
override fun onResume() {
super.onResume()
triangleView.onResume()
}
}
Kotlin小贴士:
- var 关键字表示这是一个可读可写的方法集(如果不好理解可以当做Java中实现了Getter/Setter方法的属性),当然还有一个val关键字表示一个可读不可写的方法集(Java中只实现了Getter的属性)。在Kotlin中域被封装到了val/var的内部,对外是不可见的(包括类本身)。
- lateinit 由于Kotlin中的安全检查机制,当你声明一个属性时,必须对其进行初始化以保证在任何位置访问其值的操作都是安全的,但是这回带来一些不便,于是其提供了此关键字用来说明我现在不想初始化。注意:访问任何未初始化的属性都是非法的。
我们在MainActivity的布局文件中增加一个按钮
<Button
android:text="绘制三角形"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_drawTriangle" />
然后在MainActivity中给按钮添加事件监听,使其能够打开TriangleActivity。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_drawTriangle.setOnClickListener {
val intent = Intent(this,TriangleActivity::class.java)
startActivity(intent)
}
}
}
Kotlin小贴士:
如果你按照上一章的教程进行过配置的话,是可以在Activity中使用布局ID的。另外这里使用了Lambda语法,如果你对Lambda表达式不了解的话,可以查阅Kotlin Lambda 说明和Kotlin 函数式接口。
cmake_minimum_required(VERSION 3.4.1)
#设置编译指令
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-rtti -fno-exceptions -Wall")
#如果当前系统版本过低设置拒绝编译
if (${ANDROID_PLATFORM_LEVEL} LESS 11)
message(FATAL_ERROR "OpenGL 2 is not supported before API level 11 (currently using ${ANDROID_PLATFORM_LEVEL}).")
return()
elseif (${ANDROID_PLATFORM_LEVEL} LESS 18)
add_definitions("-DDYNAMIC_ES3")
set(OPENGL_LIB GLESv2)
else ()
set(OPENGL_LIB GLESv3)
endif (${ANDROID_PLATFORM_LEVEL} LESS 11)
# Include libraries needed for Triangle lib
add_library( triangle-lib
SHARED
src/main/cpp/triangle-lib.cpp)
target_link_libraries(triangle-lib
${OPENGL_LIB}
android
EGL
log
m)
这里才是重中之重的重点,接下来我们会使用C代码实现一个在屏幕上绘制三角形的程序。在开始之前我们先进行一些说明。首先是着色器,这里的着色器跟Android中的不一样,这里的着色器是一段符合GLSL规范的字符串,其会在程序运行时被编译为GPU(图形处理器-显卡)能够识别的代码,也可以说这是一种独立的编程语言,这些着色器的目标是GPU而不是CPU。这是符合设计规范的,首先GPU是专为图像处理而设计的,其次如果将所有处理交给CPU后果可想而知。所以我们在编写着色器代码的时候可能会对其语法感到困惑,这是正常的,毕竟我们之前写的代码都是面向内存和CPU的。如果你对其中的流程和各种着色器仍有疑问,不用担心,我们会在后续章节中继续学习。接下来是我们的代码部分:
#include <GLES3/gl3.h>
#include <android/log.h>
#define LOG_TAG "TRIANGLE-LIB"
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#include <jni.h>
#include <stdlib.h>
static const char VERTEX_SHADER[]=
"#version 300 es\n"
"layout(location = 0) in vec4 vPosition;\n"
"void main(){\n"
"gl_Position = vPosition;\n"
"}\n";
static const char FRAGMENT_SHADER[]=
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 fragColor;\n"
"void main(){\n"
"fragColor = vec4(1.0,0.0,0.0,1.0);\n"
"}\n";
static const GLfloat VERTEX[]={
0.0f,0.5f,0.0f,
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f
};
bool checkGlError(const char* funcName) {
GLint err = glGetError();
if (err != GL_NO_ERROR) {
ALOGE("GL error after %s(): 0x%08x\n", funcName, err);
return true;
}
return false;
}
GLuint createShader(GLenum shaderType, const char* src) {
GLuint shader = glCreateShader(shaderType);
if (!shader) {
checkGlError("glCreateShader");
return 0;
}
glShaderSource(shader, 1, &src, NULL);
GLint compiled = GL_FALSE;
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLogLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
if (infoLogLen > 0) {
GLchar* infoLog = (GLchar*)malloc(infoLogLen);
if (infoLog) {
glGetShaderInfoLog(shader, infoLogLen, NULL, infoLog);
ALOGE("Could not compile %s shader:\n%s\n",
shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment",
infoLog);
free(infoLog);
}
}
glDeleteShader(shader);
return 0;
}
return shader;
}
GLuint createProgram(const char* vtxSrc, const char* fragSrc) {
GLuint vtxShader = 0;
GLuint fragShader = 0;
GLuint program = 0;
GLint linked = GL_FALSE;
vtxShader = createShader(GL_VERTEX_SHADER, vtxSrc);
if (!vtxShader)
goto exit;
fragShader = createShader(GL_FRAGMENT_SHADER, fragSrc);
if (!fragShader)
goto exit;
program = glCreateProgram();
if (!program) {
checkGlError("glCreateProgram");
goto exit;
}
glAttachShader(program, vtxShader);
glAttachShader(program, fragShader);
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
ALOGE("Could not link program");
GLint infoLogLen = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
if (infoLogLen) {
GLchar* infoLog = (GLchar*)malloc(infoLogLen);
if (infoLog) {
glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
ALOGE("Could not link program:\n%s\n", infoLog);
free(infoLog);
}
}
glDeleteProgram(program);
program = 0;
}
exit:
glDeleteShader(vtxShader);
glDeleteShader(fragShader);
return program;
}
GLuint program;
extern "C"{
JNIEXPORT jboolean JNICALL Java_com_github_cccxm_gles_model_TriangleLib_init(JNIEnv* env, jobject obj);
JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_resize(JNIEnv* env, jobject obj, jint width, jint height);
JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_step(JNIEnv* env, jobject obj);
}
JNIEXPORT jboolean JNICALL Java_com_github_cccxm_gles_model_TriangleLib_init(JNIEnv* env, jobject obj){
program = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
if (!program){
ALOGE("程序创建失败");
return JNI_FALSE;
}
glClearColor(0,0,0,0);
return JNI_TRUE;
}
JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_resize(JNIEnv* env, jobject obj, jint width, jint height){
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
}
JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_step(JNIEnv* env, jobject obj){
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,VERTEX);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES,0,3);
}
到现在为止我们的第一个三角形程序就完成了,现在运行程序就能看到开篇图片上的三角形了。
下一篇: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