- 一、Demo效果
- 二、Outline
- 三、基本Native逻辑框架
- 3.1 Load Library
- 3.2 Factory
- 3.3 基类
- 四、Shader资源存放
- 五、glsl简单语法
- 六、Loader Shader
- 七、Create Program
- 八、VAO绘制三角形
- 8.1 创建对应的Demo类
- 8.2 定义顶点
- 8.3 onInit
- 8.4 onSurfaceCreate
- 8.5 onSurfaceChange
- 8.6 onDraw
一、Demo效果
补充:
- 相关知识介绍见:LearnOpenGL CN
- 源码见:Github传送门
- 文章不具体针对理论知识的细节进行阐述,只阐述和Demo相关的必要知识点,如需了解学习更多的知识,请参考上面列的参考资料和源码
- CMakeList基本语法
- glViewPort
二、Outline
- 基本Native逻辑框架
- Shader资源存放
- glsl简单语法
- Loader Shader
- Create Program
- VAO
三、基本Native逻辑框架
在Android OpenGL ES 3.0开发实战(01)已经介绍了所需要的环境搭建,这里进行额外的补充说明。
3.1 Load Library
CMakeList:
1. 如需理解CmakeList的基本语法,请参考一、Demo效果的补充资料
2. CmakeList.txt最终会通过如下生成openglnative lib,需要在App刚刚启动的时候Load。
target_link_libraries(
openglnative
${platform-libs}
)
//源码:NativeLibsLoader.java文件
public static void loaderLibrarys() {
try {
System.loadLibrary("openglnative");
} catch (Throwable ignore) {
Log.e(TAG, "loadLibrary error");
}
}
3.2 Factory
单例类,用于创建Demos。
# NativeRenderJNI.cpp
JNIEXPORT void JNICALL
Java_com_scott_nativecode_NativeRenderJni_onSurfaceCreated(JNIEnv *env, jobject thiz) {
AssignFactory::getInstance()->onSurfaceCreated();
}
JNIEXPORT void JNICALL
Java_com_scott_nativecode_NativeRenderJni_onSurfaceChanged(JNIEnv *env, jobject thiz, jint width,
jint height) {
AssignFactory::getInstance()->onSurfaceChange(width,height);
}
JNIEXPORT void JNICALL
Java_com_scott_nativecode_NativeRenderJni_onDrawFrame(JNIEnv *env, jobject thiz) {
AssignFactory::getInstance()->onDraw();
}
JNIEXPORT void JNICALL
Java_com_scott_nativecode_NativeRenderJni_initV2(JNIEnv *env, jobject thiz, jobject asset_manager,
jint assign_type) {
AssignFactory::getInstance()->createAssignDemoV2(env,asset_manager,assign_type);
}
JNIEXPORT void JNICALL
Java_com_scott_nativecode_NativeRenderJni_onDestroy(JNIEnv *env, jobject thiz) {
AssignFactory::onDestroyDemoResources();
}
# AssignFactory.cpp
class AssignFactory {
public:
void createAssignDemoV2(JNIEnv *env,jobject asset_manager,int type);
void onInit(JNIEnv *env,jobject asset_manager,const string &vertexShaderAssetName,const string &fragmentShaderAssetName){
p_AssignDemo->onInit(env,asset_manager,vertexShaderAssetName,fragmentShaderAssetName);
}
void onDraw(){
p_AssignDemo->onDraw();
}
void onSurfaceCreated(){
p_AssignDemo->onSurfaceCreated();
}
void onSurfaceChange(int width,int height){
p_AssignDemo->onSurfaceChange(width,height);
}
void onDestroy(){
p_AssignDemo->onDestroy();
delete p_AssignDemo;
p_AssignDemo = nullptr;
}
static AssignFactory *getInstance();
static void onDestroyDemoResources();
private:
static AssignFactory *m_Instance;
GLAbsRender* p_AssignDemo;
};
说明:
1. NativeRenderJNI会通过单例类AssignFactory透传方法调用
2. onDestroy调用时机在Activity-> onDestroy方法;切记不可在onSurfaceDestroy里调用
3. AssignFactory包含p_AssignDemo的基类引用,在createAssignDemoV2方法里根据type创建对应的类
3.3 基类
GLAbsRender* p_AssignDemo;
# GLAbsRender.h
static GLuint DEFAULT_POS_LOCATION = 0;
static GLuint DEFAULT_COLOR_LOCATION = 1;
static GLuint DEFAULT_TEXTURE_LOCATION = 2;
class GLAbsRender {
public:
virtual void onInit(JNIEnv *env,jobject asset_manager,const string &vertexShaderAssetName,const string &fragmentShaderAssetName) {
LOGI(TAG_RENDER_TRIANGLE,"vertexShaderAssetName = %s, fragmentShaderAssetName = %s",vertexShaderAssetName.c_str(),fragmentShaderAssetName.c_str());
AAssetManager *pManager = AAssetManager_fromJava(env, asset_manager);
this->VERTEX_SHADER = AssetFun::readAssetFile(vertexShaderAssetName.c_str(), pManager);
this->FRAGMENT_SHADER = AssetFun::readAssetFile(fragmentShaderAssetName.c_str(), pManager);
LOGI(TAG_RENDER_TRIANGLE,"vertexShaderContent = %s",this->VERTEX_SHADER);
LOGI(TAG_RENDER_TRIANGLE,"fragmentShaderContent = %s",this->FRAGMENT_SHADER);
}
virtual void onDraw() = 0;
virtual void onSurfaceCreated() = 0;
virtual void onSurfaceChange(int width,int height){
LOGI(TAG_ABS_RENDER,"onSurfaceChange, width = %d, height = %d",width,height);
glViewport(0,0,width,height);
}
virtual void onDestroy(){
if (m_ProgramObj) {
glDeleteProgram(m_ProgramObj);
m_ProgramObj = GL_NONE;
}
if(VERTEX_SHADER != nullptr){
delete[] VERTEX_SHADER;
VERTEX_SHADER = nullptr;
}
if(FRAGMENT_SHADER!= nullptr){
delete[] FRAGMENT_SHADER;
FRAGMENT_SHADER = nullptr;
}
}
protected:
/**
* 程序对象
*/
GLuint m_ProgramObj;
/**
* 顶点着色器
*/
const char *VERTEX_SHADER;
/**
* 片段着色器脚本
*/
const char *FRAGMENT_SHADER;
};
GLAbsRender为所有类的基类
属性:
1. GLuint m_ProgramObj; //程序 project。
2. const char *VERTEX_SHADER; //vertex shader
3. const char *FRAGMENT_SHADER; //fragment shader
方法:
4. onDestroy销毁资源,在Act onDestroy时候会触发
5. onInit从asset里载入shader
6. onSurfaceChange里调用glViewport转换坐标系 参考资料part 1viewport链接:()
7. onSurfaceCreated和onDraw交给子类具体的去创建OpenGL project、设置对应参数和具体绘制
四、Shader资源存放
OpenGL ES开发会涉及到顶点着色器和片段着色器的编写;这里涉及到如何读取Shader。本人方法是放在Asset
里。从java层传入AssetManager
。然后在native层调用相关asset
api。具体代码如下:
# AssetFun.h
class AssetFun {
public:
static char *readAssetFile(const char *filename, AAssetManager *mgr);
};
char *AssetFun::readAssetFile(const char *filename, AAssetManager *mgr) {
AAsset *pAsset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN);
off_t len = AAsset_getLength(pAsset);
char *pBuffer = (char *) malloc(len + 1);
pBuffer[len] = '\0';
int numByte = AAsset_read(pAsset, pBuffer, len);
AAsset_close(pAsset);
return pBuffer;
}
五、glsl简单语法
这里直接贴上顶点着色器和片段着色器的内容
//hello_triangle_vertex_shader.glsl
#version 300 es // 表示OpenGL ES着色器语言V3.00
// 使用in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)。
// 声明一个输入属性数组:一个名为vPosition的4分量向量
// 图形编程中会使用向量,因此线性代数很重要
layout(location = 0) in vec4 vPosition;
void main()
{
//gl_Position是内置变量,更多的内置变量可参考:
//https://www.khronos.org/files/opengles32-quick-reference-card.pdf
gl_Position = vPosition;
}
#version 300 es
// 使用out关键字,(Output Fragment Attribute)。
// fragColor可任意取名字
out vec4 fragColor;
void main()
{
// vec4 rgba.
fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 ); //填充三角形区域为红色
}
至此我们定义了顶点着色器和片段着色器,有了顶点的位置坐标和颜色,其他的理论细节可参考文章开头给出的相关资料;
- 输入:vPosition; 并赋值给内部变量gl_Position;
- 输出fragColor。
六、Loader Shader
诉求如下:
- input params1: type(vertex or fragment);
- input params1: shader source str;
- output: shader id;
定义一个GLUtil.h类,添加loaderShader方法:
# GLUtil.h
class GLUtils {
public:
static GLuint loaderShader(GLenum type, const char** source);
};
//GLuint为open GL定义的数据类型即无符号整型
//type有2种:GL_VERTEX_SHADER 和 GL_FRAGMENT_SHADER
GLuint GLUtils::loaderShader(GLenum type, const char **source) {
GLuint shaderId;
//根据type创建对应的id
shaderId = glCreateShader(type);
if(shaderId == 0){
LOGE(TAG_GLUTILS,"create vertex shader error");
return GL_RESULT_ERROR;
}
//把source str和shaderId绑定
//glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);最后一个参数默认传nullptr
glShaderSource(shaderId,1,source, nullptr);
//编译
glCompileShader(shaderId);
//检查变异状态,如果出错打印log日志
GLint compileStatus;
glGetShaderiv(shaderId,GL_COMPILE_STATUS,&compileStatus);
if(compileStatus == GL_FALSE){
GLint infoLen = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
char *infoLog = (char *) malloc(sizeof(char) * infoLen);
if (infoLog) {
// 检索信息日志
glGetShaderInfoLog(shaderId, infoLen, nullptr, infoLog);
LOGE(TAG_GLUTILS,"GLUtils::loadShader error compiling shader:\n%s\n", infoLog);
free(infoLog);
}
// 删除Shader
glDeleteShader(shaderId);
return GL_RESULT_ERROR;
}
}
//成功则返回shaderId;
return shaderId;
}
七、Create Program
同样在GLUtil.h添加createProgram方法:
//这里千万注意 shaderSource的类型是const char** 因为最终调用的函数如下:
//glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);
//这里glShaderSource第三个参数的数据类型为const GLchar *const*
static GLuint createProgram(const char** vertexSource, const char** fragmentSource);
GLuint GLUtils::createProgram(const char **vertexSource, const char **fragmentSource) {
GLuint vertexShader = loaderShader(GL_VERTEX_SHADER, vertexSource);
if(vertexShader == 0) return GL_RESULT_ERROR;
GLuint fragmentShader = loaderShader(GL_FRAGMENT_SHADER, fragmentSource);
if(fragmentShader == 0) return GL_RESULT_ERROR;
//和shader同样的流程模式
GLuint programId;
programId = glCreateProgram();
if(programId == 0){return GL_RESULT_ERROR;}
//attach vertex and fragment shader
glAttachShader(programId,vertexShader);
glAttachShader(programId,fragmentShader);
//link
glLinkProgram(programId);
//check is error
GLint linkStatus;
glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
if(linkStatus == GL_FALSE){
GLint infoLen = 0;
glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
char *infoLog = (char *) malloc(sizeof(char) * infoLen);
if (infoLog) {
//获取信息
glGetProgramInfoLog(programId, infoLen, nullptr, infoLog);
LOGE(TAG_GLUTILS,"GLUtils::createProgram error linking program:\n%s\n", infoLog);
free(infoLog);
}
}
// 删除程序对象
glDeleteProgram(programId);
return GL_RESULT_ERROR;
}
//succ delete shader and return programId
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return programId;
}
八、VAO绘制三角形
完成了上述步骤,接下来就是具体绘制三角形了。有如下流程:
- 创建对应的Demo类
- 定义顶点
- onInit
- onSurfaceCreate
- onSurfaceChange
- onDraw
- onDestroy
8.1 创建对应的Demo类
1. 根据type -> p_mProgramDemo指向RenderTriangle.h和RenderTriangle.cpp
2. RenderTriangle继承了GLAbsRender,只需要重写onSurfaceCreated()和onDraw()方法就行
# RenderTriangle.h
class RenderTriangle : public GLAbsRender{
public:
virtual void onDraw();
virtual void onSurfaceCreated();
};
8.2 定义顶点
# RenderTriangle.cpp
static GLfloat vertices[] = {
0.0f, 0.5f, 0.0f, // 第一个点(x, y, z)
-0.5f, -0.5f, 0.0f, // 第二个点(x, y, z)
0.5f, -0.5f, 0.0f // 第三个点(x, y, z)
};
1. openGL坐标为[-1, 1]
2. openGl 内置gl_Positon为vec4 => x,y,z,w
3. x,y,z 代表三位空间。w是代表齐次坐标方便三维空间的矩阵运算,这里不具体阐述,可见后续文章
4. 第一个顶点在屏幕中心上方,第二个在屏幕中心左下,第三个在右下
8.3 onInit
在父类里
virtual void onInit(JNIEnv *env,jobject asset_manager,const string &vertexShaderAssetName,const string &fragmentShaderAssetName) {
AAssetManager *pManager = AAssetManager_fromJava(env, asset_manager);
this->VERTEX_SHADER = AssetFun::readAssetFile(vertexShaderAssetName.c_str(), pManager);
this->FRAGMENT_SHADER = AssetFun::readAssetFile(fragmentShaderAssetName.c_str(), pManager);
}
8.4 onSurfaceCreate
# RenderTriangle.cpp
void RenderTriangle::onSurfaceCreated() {
//设置背景默认颜色,当调用glClear会把屏幕背景颜色reset该设置的颜色
//必须在after onSurfaceCreated调用,之外不生效
glClearColor(1.0f, 0.5f, 0.0f, 1.0f);
// GLUtils::createProgram;
this->m_ProgramObj = GLUtils::createProgram(&this->VERTEX_SHADER, &this->FRAGMENT_SHADER);
// 设置清除颜色
// 激活
glUseProgram(this->m_ProgramObj);
// params1:DEFAULT_POS_LOCATION
//顶点着色器输入顶点变量位置即 part 五里: layout(location = 0) in vec4 vPosition; DEFAULT_POS_LOCATION对应着vPosition位置
// params2:size pos Size为3, x,y,z
// params3: GLfloat vertices 数据类型
// params4: 是否归一化到[0,1] false
// params5: stride 不需要 设置为0
// params6: vertices顶点位置变量
glVertexAttribPointer(DEFAULT_POS_LOCATION,3,GL_FLOAT,GL_FALSE,0,vertices );
glEnableVertexAttribArray(DEFAULT_POS_LOCATION);
}
8.5 onSurfaceChange
# GLAbsRender.h
virtual void onSurfaceChange(int width,int height){
glViewport(0,0,width,height);
}
8.6 onDraw
void RenderTriangle::onDraw() {
//reset background to the color seted by glClearColor;
glClear(GL_COLOR_BUFFER_BIT);
//params1: GL_TRIANGLES 画的primitive为triangle,primitive有point line triangle等
//params2: 从第几个点开始画
//params3: count
//vertices 位置有三个点,每个点有3个维度。因此从第index = 0开始画,画3个点。3个点形成一个三角形。
glDrawArrays(GL_TRIANGLES,0,3);
}
至此完成了整个三角形的绘制。