NDK-第一个OpenGL示例

代码结构如下:

一、Java层

LammyGLSurfaceView.java(用于展示渲染的OpenGL模型)

package com.example.lammy.openglstudy;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;

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);// 设置OpenGL渲染器
    }
}

LammyRender.java(定义OpenGL渲染器)

package com.example.lammy.openglstudy;

import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public 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();
    }
}

Log.java(打印日志)

package com.example.lammy.openglstudy;

public class  Log{
    public static  void loge(String s){
        android.util.Log.e("lammy-java-log:" , s );
    }
}

MainActivity.java(主入口程序)

package com.example.lammy.openglstudy;

import android.os.Bundle;
import android.os.Environment;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private LammyGLSurfaceView lammyGLSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        lammyGLSurfaceView = new LammyGLSurfaceView(this);
        setContentView(lammyGLSurfaceView);
        Log.loge(Environment.getExternalStorageState());
    }
}

Native.java(加载本地库并声明要调用的C函数)

package com.example.lammy.openglstudy;

import android.content.res.AssetManager;

public class Native {
    static {
        System.loadLibrary("native-lib");// 注意名称与CMakeLists.txt中一致
    }

    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();
}
二、C++层

导入GLM库(glm是数学库,用于处理矩阵):https://sourceforge.net/projects/ogl-math/
导入时候只要将整个文件拷贝至cpp文件。要注意在CMakeLists.txt文件中配置头文件路径。

ggl.h(打印日志)

#ifndef OPENGLSTUDY_GGL_H
    #define OPENGLSTUDY_GGL_H
#endif

#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:"
    #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

opengl.cpp(通过AAssetManager指针从JNI层读取文件)

#include "ggl.h"
#include "scene.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(&current, 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();
}

scene.h(绘制场景头文件)

#ifndef OPENGLSTUDY_SCENE_H
    #define OPENGLSTUDY_SCENE_H
#endif

#include "ggl.h"

void Init();// 初始化OpenGL数据
void SetViewPortSize(float width , float height);// 设置视口并更新透视矩阵
void Draw();// 完成绘制

scene.cpp(实现真正的场景绘制)

#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;
    // 从assets目录加载顶点着色器代码
    unsigned char *shaderCode = LoadFileContent("test.vs", fileSize);
    GLuint vsShader = CompileShader(GL_VERTEX_SHADER, (char *) shaderCode);
    delete shaderCode;
    // 从assets目录加载片段着色器代码
    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);
}

utils.h(完成创建纹理,程序,连接程序工具类)

#ifndef OPENGLSTUDY_UTILS_H
    #define OPENGLSTUDY_UTILS_H
#endif

#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);
    // 1 表示多少句代码,所有代码都放着。第三个表示如果多句代码,则要与前面的代码的长度。
    glShaderSource(shader, 1, &shaderCode, nullptr);
    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;
}
三、GLSL代码

test.vs(顶点着色器)

attribute vec4 position;
attribute vec4 color;
attribute vec2 texcoord;

uniform mat4 ModelMatrix;
uniform mat4 ViewMatrix;
uniform mat4 ProjectionMatrix;

varying vec4 V_Color;
varying vec2 V_Texcoord;

// gpu 是有很多个核,因此计算每个点到屏幕的位置是并行计算的
void main(){
    gl_Position = ProjectionMatrix * ViewMatrix * ModelMatrix * position;
    
	V_Color = color;
    V_Texcoord = texcoord;
}

test.fs.glsl(片段着色器)

# 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);
}
四、gradle、cmake文件

app/build.gradle

apply plugin: 'com.android.application'

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

                arguments "-DANDROID_ABI=armeabi-v7a"
                arguments "-DCMAKE_BUILD_TYPE=Release"
                version "3.21.1"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    compileOptions {
        targetCompatibility JavaVersion.VERSION_1_8
        sourceCompatibility JavaVersion.VERSION_1_8
    }
    buildToolsVersion '31.0.0'
    ndkVersion '23.0.7599858'
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

app/CMakeLists.txt

# Sets the minimum version of CMake required to build the native library
cmake_minimum_required(VERSION 3.10.2)

# 添加数学库GLM路径
include_directories(${CMAKE_SOURCE_DIR}/src/cpp/glm)
include_directories(${CMAKE_SOURCE_DIR}/src/cpp/glm/glm)

# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK
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
)

#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}
)

openglStudy/build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules
buildscript {
    repositories {
        mavenCentral()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.0.1'
        

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenCentral()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

运行程序,生成app-debug.apk,解压此APK文件,得到so文件:

注意,生成的so文件名称为:“lib” + “native-lib” + “.so”

程序用到的纹理图 test2.bmp:

最终APP运行效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzyjr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值