目录
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的执行效果如下