目录

1、上层的创建

2、jni层的配置

创建头文件ggl.h

jni函数接口 assetsManager jni层读取文件

utils 创建纹理,程序,连接程序工具类

scene.h 里实现真正的绘制,

glm库的导入

cmake中配置环境

app gradle 配置

着色器

demo下载


opengl学习了好久了,之前一直再java层开发,但随着对性能的要求,一些特效和编解码都需要再底层来实现,为了避免大量的java层和上层数据的传输浪费时间,就需要将所以的处理都放在底层,上层只有一些UI操作。

1、上层的创建

上层很简单,就是创建一个render 和 一个 glsurfaceview,然后将render 设置给glsurfaceview。

surfaceview

public class LammyGLSurfaceView extends GLSurfaceView {
    private LammyRenderer lammyRenderer;
    public LammyGLSurfaceView(Context context) {
        super(context);
        init();
    }

    public LammyGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void  init(){
        lammyRenderer = new LammyRenderer();
        Native.InitAssetManager(getContext().getAssets());
        // 这里申请 2 版本环境,若想3 则需要在 jni层面去做
        setEGLContextClientVersion(2);
        setRenderer(lammyRenderer);
    }
    
}

renderer

class LammyRenderer implements GLSurfaceView.Renderer {

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//        gl.glClearColor(0.1f , 0.4f,0.6f , 1f);

       Log.loge("onSurfaceCreated ...........");
        Native.InitOpenGL();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
//        gl.glViewport(0,0,width ,height);
        Log.loge("onSurfaceChanged ...........");
        Native.OnViewportChanged((float)width ,(float) height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
//        gl.glClear(gl.GL_COLOR_BUFFER_BIT);
  //      Log.loge("onDrawFrame ...........");
            Native.RenderOneFrame();
    }
    
}

然后就是natvie类,实现jni接口的类

public class Native {

    static {
        System.loadLibrary("native-lib");
    }

    public static native void InitAssetManager(AssetManager am);
    public static native void InitOpenGL();
    public static native void OnViewportChanged(float width, float height);
    public static native void RenderOneFrame();

}

2、jni层的配置

创建头文件ggl.h

包含opengl 和 一些要重用到的库和android log,这样每次只需要每次导入ggl.h,而不需要写一大堆导入库

ggl.h

#include <jni.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <android/asset_manager_jni.h>
#include <android/asset_manager.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <string>
#include <sstream>
#include <vector>
#include <functional>
#include <map>
#include "glm/glm/glm.hpp"


#define __DEBUG__ANDROID__ON
//write debug images
#ifdef  __DEBUG__ANDROID__ON
#include <android/log.h>
// Define the LOGI and others for print debug infomation like the log.i in java
#define LOG_TAG    "lammy-jni-log:"
//#undef LOG
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#endif

jni函数接口 assetsManager jni层读取文件

#include "ggl.h"
#include "scene.h"
#include "model.h"
AAssetManager* aAssetManager = nullptr;

unsigned char * LoadFileContent(const char *path , int &filesSize){

    unsigned char * fileContent = nullptr;
    filesSize = 0 ;
    AAsset * asset = AAssetManager_open(aAssetManager, path , AASSET_MODE_UNKNOWN);
    if(asset== nullptr){
        LOGE("LoadFileContent asset is null, load shader error ");
        return  nullptr;
    }
    filesSize = AAsset_getLength(asset);
    fileContent = new unsigned char[filesSize];
    AAsset_read(asset , fileContent,filesSize);
    fileContent[filesSize]='\0';
    AAsset_close(asset);
    LOGE("LoadFileContent success ...%s",path);
    return fileContent;

}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_lammy_openglstudy_Native_InitAssetManager(JNIEnv *env, jclass type, jobject am) {
    aAssetManager = AAssetManager_fromJava(env , am);
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_lammy_openglstudy_Native_InitOpenGL(JNIEnv *env, jclass type) {

//    glClearColor(0.1 , 0.4,0.6 , 1);
    Init();
 //   InitModel(aAssetManager , "model/Cube.obj" );
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_lammy_openglstudy_Native_OnViewportChanged(JNIEnv *env, jclass type, jfloat width,
                                                            jfloat height) {
//    glViewport(0,0,width ,height);
    SetViewPortSize(width ,height);

}

float GetFrameTime(){
    static unsigned  long long lastTime = 0,currentTime =0;
    timeval current;
    gettimeofday(¤t , nullptr);
    // 将时间转化为毫秒
    currentTime = current.tv_sec * 1000 + current.tv_usec/1000;
    unsigned  long long frameTime = lastTime == 0?0:currentTime - lastTime;
    lastTime = currentTime;
    return float(frameTime)/1000.0f;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_lammy_openglstudy_Native_RenderOneFrame(JNIEnv *env, jclass type) {

//    glClear(GL_COLOR_BUFFER_BIT);
    Draw();
}
InitAssetManager java层传入assetManager,再底层可以利用该对象读取 assets里的文件。利用LoadFileContent即可读取内容。如着色器,或者图片。下一篇会专门讲到读取文件的方法。

utils 创建纹理,程序,连接程序工具类

utils.h

#include "ggl.h"

unsigned char * LoadFileContent(const char *path , int &fileSize);

GLuint CompileShader(GLenum shaderType , const char * shaderCode);

GLuint CreateProgram(GLuint vsShader , GLuint fsShader);

GLuint CreateTextureFromBMP(const char * bmpPath);

GLuint CreateTexture2D(unsigned char *pixelData, int Width, int height ,GLenum type);

utils.cpp

#include "utils.h"

GLuint CompileShader(GLenum shaderType , const char * shaderCode){
    GLuint shader = glCreateShader(shaderType);
    glShaderSource(shader, 1 , &shaderCode, nullptr);//1 表示多少句代码,所有代码都放着。第三个表示如果多句代码,
    //则要与前面的代码的长度。
    glCompileShader(shader);

    GLint compileResult = GL_TRUE;
    // 查看编译状态
    glGetShaderiv(shader,GL_COMPILE_STATUS, &compileResult);
    if(compileResult == GL_FALSE){
        char szLog[1024] = {0};
        GLsizei  logLen = 0;// 存储日志长度
        glGetShaderInfoLog(shader , 1024 , &logLen , szLog);//1024 为日志的最大长度
        LOGE("compile error , log: %s" , szLog);
        LOGE("compile error ,shader %s" , shaderCode);
        glDeleteShader(shader);
        return  0;
    }
    return shader;

}

GLuint CreateProgram(GLuint vsShader , GLuint fsShader){
    GLuint program = glCreateProgram();
    glAttachShader(program, vsShader);
    glAttachShader(program, fsShader);
    glLinkProgram(program);
    glDetachShader(program, vsShader);
    glDetachShader(program, fsShader);
    GLint nResult;
    glGetProgramiv(program , GL_LINK_STATUS, &nResult);
    if(nResult == GL_FALSE){
        char log[1024] = {0};
        GLsizei  len = 0;// 存储日志长度
        glGetShaderInfoLog(program , 1024 , &len , log);//1024 为日志的最大长度
        LOGE("create program error , log: %s" , log);
        glDeleteProgram(program);
        return  0;
    }
    LOGE("create program success  " );
    return program;
}


GLuint CreateTexture2D(unsigned char *pixelData, int width, int height ,GLenum type){
    GLuint texture;
    glGenTextures(1 , &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// 表示图像放大时候,使用线性过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);// 表示图像缩小时候,使用线性过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0 , type, width ,height, 0, type,GL_UNSIGNED_BYTE, pixelData);//GL_RGBA
    glBindTexture(GL_TEXTURE_2D, 0);
    return texture;
}

unsigned  char* DecodeBMP(unsigned char * bmpFileData, int &width ,int &height){
    if(0x4D42 == *((unsigned short*)bmpFileData)){ // 数据头是否为0x4D42 判断是否是 24位的位图,
        // 读格式头
        int pixelDataOffset = * ((int *)(bmpFileData + 10));// 取出 像素数据在内存块的偏移地址
        width = *((int *)(bmpFileData + 18));
        height= *((int *)(bmpFileData + 22));
        unsigned char *pixelData = bmpFileData + pixelDataOffset;
        // 位图 像素数据 是 bgr排布的,所以 更换 r b的位置
        for(int i =0 ; i < width * height * 3 ; i += 3){
            unsigned char temp = pixelData[i];
            pixelData[i] = pixelData[i + 2];
            pixelData[i+2] = temp;

        }
        LOGE("DecodeBMP success " );
        return pixelData;
    }
    LOGE("DecodeBMP error " );
    return nullptr;
}

GLuint CreateTextureFromBMP(const char * bmpPath){
    int nFileSize = 0;
    unsigned char *bmpFileContent = LoadFileContent(bmpPath, nFileSize);
    if(bmpFileContent==NULL){
        return 0;
    }
    int bmpWidth= 0,bmpHeight =0;
    unsigned char *pixelData = DecodeBMP(bmpFileContent,bmpWidth,bmpHeight);
    if(pixelData==NULL){
        delete[] bmpFileContent;
        LOGE("CreateTextureFromBMP error " );
        return 0;
    }
    GLuint texture = CreateTexture2D(pixelData, bmpWidth, bmpHeight,GL_RGB);
    delete [] bmpFileContent;
    LOGE("CreateTextureFromBMP success " );
    return texture;
}

scene.h 里实现真正的绘制,

这里绘制的是三角形。

//
// Created by zhangpeng30 on 2019/3/19.
//

#include "scene.h"
#include "ggl.h"
#include "utils.h"
#include "glm/glm/gtc/matrix_transform.hpp"
#include "glm/glm/ext.hpp"
#include "glm/glm/detail/_noise.hpp"

// 将数据从cpu放到 gpu
GLuint vbo, ebo;
GLuint program;
GLint positionLocation,modelMatrixLocation,viewMatrixLocation,projectMatrixLocation, colorLoction;
GLint texcoordLocation,textureLocation;
GLint texture;
// 不初始化就是单位矩阵
glm::mat4 modelMatrix ,viewMatrix, projectMatrix;
void Init(){
    float data[] = {
            -0.2f,-0.2f,0.0f , 1.0f,1.0f ,1.0f ,1.0f ,1.0f ,0.0f, 0.0f,
            0.2f, -0.2f ,0.0f , 1.0f,0.0f, 1.0f , 0.0f, 1.0f,1.0f,0.0f,
            0.0f, 0.2f , 0.0f , 1.0f, 1.0f, 0.0f , 0.0f, 1.0f,0.5f,1.0f
    };

    glGenBuffers(1, &vbo); // 1 表示需要一个vbo , 后面的vbo 指向显存块
    glBindBuffer(GL_ARRAY_BUFFER , vbo);//绑定显存地址
    glBufferData(GL_ARRAY_BUFFER, sizeof(float)*30 , data , GL_STATIC_DRAW);
//  最后一个参数,表示 放入显卡 不会修改 ,这里数据跑到显卡了
    glBindBuffer(GL_ARRAY_BUFFER,0);// 设置当前buffer为0 即解绑

    /**     利用element绘制 ebo来控制绘制点的顺序                        **/
    unsigned short indexes[] = {0, 1, 2};
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER , ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*3, indexes,GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER , 0);

    int fileSize = 0;
    unsigned  char * shaderCode = LoadFileContent("test.vs",fileSize);
    GLuint vsShader = CompileShader(GL_VERTEX_SHADER,(char *)shaderCode);
    delete shaderCode;
    shaderCode = LoadFileContent("test.fs.glsl", fileSize);
    GLint fsShader = CompileShader(GL_FRAGMENT_SHADER , (char *)shaderCode);
    program = CreateProgram(vsShader , fsShader);
    glDeleteShader(vsShader);
    glDeleteShader(fsShader);

    // attribute 的插槽 和 uniform插槽 不一样,并且都是从0 开始的
    positionLocation = glGetAttribLocation(program, "position");
    colorLoction = glGetAttribLocation(program, "color");
    modelMatrixLocation = glGetUniformLocation(program , "ModelMatrix");
    viewMatrixLocation = glGetUniformLocation(program , "ViewMatrix");
    projectMatrixLocation = glGetUniformLocation(program , "ProjectionMatrix");
    textureLocation = glGetUniformLocation(program , "U_texture");
    texcoordLocation = glGetAttribLocation(program , "texcoord");

    glm::mat4 model;
    modelMatrix = glm::translate(model , glm::vec3(0.0f, 0.0f, -0.6f));
    texture = CreateTextureFromBMP("test2.bmp");
}
void SetViewPortSize(float width , float height){
    glViewport(0,0,width,height);
    // 参数分别为,视角,宽高比、最近看到的距离、最远看到的距离
    projectMatrix = glm::perspective(45.0f, width/height , 0.1f , 1000.0f);
}
void Draw(){

    glClearColor(0.1f,0.4f,0.6f,1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(program);

    glUniformMatrix4fv(modelMatrixLocation , 1 , GL_FALSE , glm::value_ptr(modelMatrix));
    glUniformMatrix4fv(viewMatrixLocation , 1 , GL_FALSE , glm::value_ptr(viewMatrix));
    glUniformMatrix4fv(projectMatrixLocation , 1 , GL_FALSE , glm::value_ptr(projectMatrix));

    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(textureLocation,0);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glEnableVertexAttribArray(positionLocation);
    glEnableVertexAttribArray(texcoordLocation);
    glEnableVertexAttribArray(colorLoction);
    // 参数说明: GL_FALSE 表示是否需要将数据映射到0-1,这里本来是浮点,不需要;
    //0 表示vbo 中数据的起始位置
    glVertexAttribPointer(positionLocation , 4 , GL_FLOAT , GL_FALSE , sizeof(float)*10, 0);
    glVertexAttribPointer(colorLoction , 4 , GL_FLOAT , GL_FALSE , sizeof(float)*10, (void *)(sizeof(float)*4));
    glVertexAttribPointer(texcoordLocation , 2 , GL_FLOAT , GL_FALSE , sizeof(float)*10, (void *)(sizeof(float)*8));

//    glDrawArrays(GL_TRIANGLES, 0 , 3);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ebo);
    glDrawElements(GL_TRIANGLES , 3 , GL_UNSIGNED_SHORT, 0 );

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glUseProgram(0);
}

vbo是存储定点信息,我们需要将定点数组从cpu 移到gpu显存。

ebo 是显卡上另外的一种插槽,是控制绘制定点顺序的。

glm库的导入

下载地址,导入时候只要将整个文件拷贝至cpp文件,注意在cmakelist文件中配置头文件路径

glm是数学库,用于处理矩阵的。需要注意的是矩阵是具有叠加性的。

如先平移 后旋转, 和先旋转后平移,得到的结果是不一样的。

cmake中配置环境

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.


include_directories(${CMAKE_SOURCE_DIR}/src/cpp/glm)
include_directories(${CMAKE_SOURCE_DIR}/src/cpp/glm/glm)

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/opengl.cpp
             src/main/cpp/scene.cpp
             src/main/cpp/utils.cpp
             src/main/cpp/model.cpp

             )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )



# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib
                        GLESv1_CM
                        GLESv2
                        android
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib}
                       )

 

1、opengl 导入 GLESv1_CM、GLESv2、 

2、要用到assetmanager 所以导入android 

3、配置glm的头文件路径

 

node: 如果使用的是opengles 3.0版本,则需要导入GLESv3,否则就会报一些函数为定义的错误:

如调用:


glDrawBuffers(2,buffers);


报错:

undefined reference to `glDrawBuffers'
collect2.exe: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.

app gradle 配置

主要是要加上 cpp文件以c++11 来编译这条

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.lammy.openglstudy"
        minSdkVersion 23
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk{
            abiFilters 'armeabi-v7a'//,'arm64-v8a','armeabi'
        }
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions "
                arguments "-DANDROID_TOOLCHAIN=gcc"

                arguments "-DANDROID_ABI=armeabi-v7a"
                arguments "-DCMAKE_BUILD_TYPE=Release"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

着色器

着色器是绘制的基础,这里给出2个简单的着色器

定点着色器

attribute vec4 position;
attribute vec4 color;
varying vec4 V_Color;

// 模型视口
uniform mat4 ModelMatrix;

uniform mat4 ViewMatrix;
uniform mat4 ProjectionMatrix;

attribute vec2 texcoord;
varying vec2 V_Texcoord;

/***
*   gpu 是有很多个核,因此计算每个点到屏幕的位置是 并行计算的
*/
void main(){

    V_Color = color;
    gl_Position = ProjectionMatrix*ViewMatrix*ModelMatrix*position;//

    V_Texcoord = texcoord;
}

fragment着色器

//#version 120
# ifdef GL_ES // 如果是es的环境
precision mediump float;
varying vec4 V_Color;
uniform sampler2D U_texture;
varying vec2 V_Texcoord;
#endif
/**
* 也是多个块 运算的 并行运算
*/
void main() {

//    gl_FragColor= vec4(1.0, 1.0 , 0.0, 1.0);
    gl_FragColor= V_Color * texture2D(U_texture, V_Texcoord);
//    gl_FragColor=  texture2D(U_texture, V_Texcoord);
}

demo下载

这里给出demo,方便大家学习。demo的执行效果如下

android opengl详解 android opengl开发_android opengl详解