Android OpenGL ES 3.0开发实战(02):Hello World,绘制一个三角形Triangle

一、Demo效果

在这里插入图片描述在这里插入图片描述

补充:

  1. 相关知识介绍见:LearnOpenGL CN
  2. 源码见:Github传送门
  3. 文章不具体针对理论知识的细节进行阐述,只阐述和Demo相关的必要知识点,如需了解学习更多的知识,请参考上面列的参考资料和源码
  4. CMakeList基本语法
  5. glViewPort

二、Outline

  1. 基本Native逻辑框架
  2. Shader资源存放
  3. glsl简单语法
  4. Loader Shader
  5. Create Program
  6. 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链接:(https://blog.csdn.net/Scott_S/article/details/86565873)
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绘制三角形

完成了上述步骤,接下来就是具体绘制三角形了。有如下流程:

  1. 创建对应的Demo类
  2. 定义顶点
  3. onInit
  4. onSurfaceCreate
  5. onSurfaceChange
  6. onDraw
  7. 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);
}

至此完成了整个三角形的绘制。

猜你喜欢

转载自blog.csdn.net/Scott_S/article/details/125153767