代码结构如下:
一、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(¤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();
}
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运行效果: