第一部分Java端
1)界面
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.opengl.GLSurfaceView
android:id="@+id/gl_surface"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
2)自定义MyGLSurfaceView
MyGLSurfaceView集成GLSurfaceView实现父类方法,并加载opengl相关参数和设置
自定义MyGLRender类实现GLSurfaceView.Renderer接口,并在其中引入jni相关的类方法
package com.example.onedrawtri2;
import android.content.Context;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MyGLSurfaceView extends GLSurfaceView {
private MyRenderer renderer;
public MyGLSurfaceView(Context context) {
super(context);
setEGLContextClientVersion(3);
renderer = new MyRenderer();
setRenderer(renderer);
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
private class MyRenderer implements GLSurfaceView.Renderer {
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
NativeImpl.NativeImpl_init();
}
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
NativeImpl.NativeImpl_OnSurfaceChanged(width, height);
}
@Override
public void onDrawFrame(GL10 gl10) {
NativeImpl.NativeImpl_draw();
}
}
}
3)jni方法
package com.example.onedrawtri2;
public class NativeImpl {
static {
System.loadLibrary("native-lib");
}
public static native void NativeImpl_init();
public static native void NativeImpl_OnSurfaceChanged(int width,int height);
public static native void NativeImpl_draw();
}
4)MainActivity引入加载MyGLSurfaceView
package com.example.onedrawtri2;
import androidx.appcompat.app.AppCompatActivity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private GLSurfaceView gLView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gLView = new MyGLSurfaceView(this);
setContentView(gLView);
}
}
第二部分C++端
1)native动态加载jni方法
//
// Created by CreatWall_zhouwen on 2023/4/11.
//
#include "jni.h"
#include <android/log.h>
#include <GLES2/gl2.h>
#include "Util.h"
#include "DrawTriangle.h"
#define NATIVE_RENDER_CLASS_NAME "com/example/onedrawtri2/NativeImpl"
#define TAG "GLTRIANGLE"
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_onedrawtri2_NativeImpl
* Method: init
* Signature: ()V
*/
JNIEXPORT void JNICALL NativeImpl_init(JNIEnv *env, jobject instance)
{
DrawTriangle::GetInstance();
DrawTriangle::GetInstance()->CreateProgram();
}
/*
* Class: com_example_onedrawtri2_NativeImpl
* Method: OnSurfaceChanged
* Signature: (II)V
*/
JNIEXPORT void JNICALL NativeImpl_OnSurfaceChanged(JNIEnv *env, jobject instance, jint width, jint height)
{
glViewport(0, 0, width, height);
}
/*
* Class: com_example_onedrawtri2_NativeImpl
* Method: draw
* Signature: ()V
*/
JNIEXPORT void JNICALL NativeImpl_draw(JNIEnv *env, jobject instance)
{
DrawTriangle::GetInstance()->Draw();
}
#ifdef __cplusplus
}
#endif
static JNINativeMethod g_RenderMethods[] = {
{"NativeImpl_init", "()V", (void *)(NativeImpl_init)},
{"NativeImpl_OnSurfaceChanged", "(II)V", (void *)(NativeImpl_OnSurfaceChanged)},
{"NativeImpl_draw", "()V", (void *)(NativeImpl_draw)},
};
static int RegisterNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int methodNum)
{
LOGD("RegisterNativeMethods");
jclass clazz = env->FindClass(className);
if (clazz == NULL)
{
LOGD("RegisterNativeMethods fail. clazz == NULL");
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, methods, methodNum) < 0)
{
LOGD("RegisterNativeMethods fail");
return JNI_FALSE;
}
return JNI_TRUE;
}
static void UnregisterNativeMethods(JNIEnv *env, const char *className)
{
LOGD("UnregisterNativeMethods");
jclass clazz = env->FindClass(className);
if (clazz == NULL)
{
LOGD("UnregisterNativeMethods fail. clazz == NULL");
return;
}
if (env != NULL)
{
env->UnregisterNatives(clazz);
}
}
// call this func when loading lib
extern "C" jint JNI_OnLoad(JavaVM *jvm, void *p)
{
LOGD("===== JNI_OnLoad =====");
jint jniRet = JNI_ERR;
JNIEnv *env = NULL;
if (jvm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK)
{
return jniRet;
}
jint regRet = RegisterNativeMethods(env, NATIVE_RENDER_CLASS_NAME, g_RenderMethods,
sizeof(g_RenderMethods) /
sizeof(g_RenderMethods[0]));
if (regRet != JNI_TRUE)
{
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
extern "C" void JNI_OnUnload(JavaVM *jvm, void *p)
{
JNIEnv *env = NULL;
if (jvm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK)
{
return;
}
UnregisterNativeMethods(env, NATIVE_RENDER_CLASS_NAME);
}
2)opengles画三角形类
在里面实现了编写shader着色器,附加编译链接绑定着色器等相关操作。
DrawTriangle.h文件
//
// Created by CreatWall_zhouwen on 2023/4/3.
//
#ifndef ONEDRAWTRIANGLE_DRAWTRIANGLE_H
#define ONEDRAWTRIANGLE_DRAWTRIANGLE_H
#include <GLES3/gl3.h>
class DrawTriangle {
public:
DrawTriangle(){};
~DrawTriangle(){};
void CreateProgram();
void Draw();
static DrawTriangle* GetInstance();
static void DestroyInstance();
private:
GLuint program;
GLuint vertexShaderHandle;
GLuint fragShaderHandle;
GLuint VBO, VAO;
};
#endif //ONEDRAWTRIANGLE_DRAWTRIANGLE_H
DrawTriangle.cpp文件
//
// Created by CreatWall_zhouwen on 2023/4/3.
//
#include "DrawTriangle.h"
#include <android/log.h>
#include "Util.h"
#define TAG "DRAWTRINGLE"
DrawTriangle* m_pContext = nullptr;
//编写顶点着色器代码
char vShaderStr[] =
"#version 300 es \n"
"layout(location = 0) in vec4 vPosition; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition;\n"
"} \n";
//编写顶点着色器代码
char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"out vec4 fragColor; \n"
"void main() \n"
"{ \n"
" fragColor = vec4 ( 1.0f, 0.2f, 0.2f, 1.0f ); \n"
"} \n";
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
void DrawTriangle::CreateProgram()
{
LOGD("CreateProgram Enter");
//程序中加载编译链接顶点着色器
vertexShaderHandle = glCreateShader(GL_VERTEX_SHADER);//创建顶点着色器
const char * s = vShaderStr;
glShaderSource(vertexShaderHandle, 1, &s, NULL);//绑定源码
glCompileShader(vertexShaderHandle);//编译
GLint compiled = 0;
glGetShaderiv(vertexShaderHandle, GL_COMPILE_STATUS, &compiled);//检查编译是否成功
if (!compiled)
{
LOGD("CreateProgram vertexShaderHandle error");
return;
}
//程序中加载编译链接片段着色器
fragShaderHandle = glCreateShader(GL_FRAGMENT_SHADER);//创建顶片段着色器
const char *s1 = fShaderStr;
glShaderSource(fragShaderHandle, 1, &s1, NULL);//绑定源码
glCompileShader(fragShaderHandle);//编译
glGetShaderiv(fragShaderHandle, GL_COMPILE_STATUS, &compiled);//检查编译是否成功
if (!compiled)
{
LOGD("CreateProgram fragShaderHandle error");
return;
}
//创建程序
program = glCreateProgram();
if (program)
{
GLint AttachStatus = GL_FALSE;
glAttachShader(program, vertexShaderHandle);//添加顶点着色器
glGetProgramiv(program, GL_ATTACHED_SHADERS, &AttachStatus);//检查链接是否成功GL_ATTACHED_SHADERS
if (AttachStatus != 1)
{
LOGD("AttachStatus vertexShaderHandle error");
return;
}
GLint AttachStatus1 = GL_FALSE;
glAttachShader(program, fragShaderHandle);//添加片段着色器
glGetProgramiv(program, GL_ATTACHED_SHADERS, &AttachStatus1);//检查链接是否成功
if (AttachStatus1 != 2)
{
LOGD("AttachStatus fragShaderHandle error glError (0x%x) %d",glGetError(),AttachStatus1);
return;
}
glLinkProgram(program);//链接
GLint linkStatus = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);//检查链接是否成功
if (linkStatus != GL_TRUE)
{
LOGD("CreateProgram glLinkProgram error");
return;
}
//成功之后可以解绑着色器并删除调,使用完则删除内存
glDetachShader(program, vertexShaderHandle);
glDeleteShader(vertexShaderHandle);
vertexShaderHandle = 0;
glDetachShader(program, fragShaderHandle);
glDeleteShader(fragShaderHandle);
fragShaderHandle = 0;
}
glUseProgram(program);//使用着色器程序
glGenVertexArrays(1, &VAO);//创建一个顶点数组 与顶点属性相绑定
glGenBuffers(1, &VBO);//创建顶点缓冲对象
glBindVertexArray(VAO);//绑定顶点数组
glBindBuffer(GL_ARRAY_BUFFER, VBO);//将GL_ARRAY_BUFFER类型的缓存与VBO绑定
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//之前定义的顶点数据复制到缓冲的内存
/*
0;指定我们要配置的顶点属性 就是顶点着色器里面location那个
*/
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
glEnableVertexAttribArray(0);//以顶点属性位置值0作为参数,启用顶点属性
//glBindBuffer(GL_ARRAY_BUFFER, 0);//VBO 已经与顶点属性数组VAO进行绑定了 那么GL_ARRAY_BUFFER就可以解除绑定
//glBindVertexArray(0);//您可以在之后解除绑定VAO,这样其他VAO调用就不会意外地修改这个VAO*/
LOGD("CreateProgram End");
return;
}
void DrawTriangle::Draw()
{
LOGD("Draw Enter");
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//使用状态函数
glClearColor(0.2f, 0.9f, 0.3f, 1.0f); //设置状态函数
glUseProgram(program);//使用着色器程序
glBindVertexArray(VAO);//绑定顶点数组 就是使用顶点属性
/*
我们希望绘制的是一个三角形,这里传递GL_TRIANGLES给它。
第二个参数指定了顶点数组的起始索引,我们这里填0。
最后一个参数指定我们打算绘制多少个顶点,这里是3
(我们只从我们的数据中渲染一个三角形,它只有3个顶点长)
*/
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
LOGD("Draw end");
}
DrawTriangle *DrawTriangle::GetInstance() {
if (m_pContext == nullptr)
{
m_pContext = new DrawTriangle();
}
return m_pContext;
}
void DrawTriangle::DestroyInstance() {
if (m_pContext)
{
delete m_pContext;
m_pContext = nullptr;
}
}
第三部分安卓配置文件配置
1)bulid.gradle(app)引入cmake
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags ''
abiFilters "arm64-v8a"
//arguments "-DANDROID_STL=c++_shared"
}
}
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.10.2'
}
}
2)settings.gradle文件配置链接
jcenter()
3)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.10.2)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
# Declares and names the project.
set(jnilibs ${CMAKE_SOURCE_DIR}/../jniLibs)
set(libname native-lib)
include_directories(
include
${CMAKE_SOURCE_DIR}
)
link_directories(
${jnilibs}/${ANDROID_ABI})
file(GLOB src-files
${CMAKE_SOURCE_DIR}/*.cpp
${CMAKE_SOURCE_DIR}/drawTriangle.cpp
)
# 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.
add_library( # Sets the name of the library.
${libname}
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${src-files}
)
# 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.
#set(third-party-libs
# GLES
# )
set(native-libs
android
log
m
z
)
target_link_libraries( # Specifies the target library.
${libname}
# Links the target library to the log library
# included in the NDK.
${log-lib}
${native-libs}
EGL
GLESv3
)
第四部分将着色器代码放到assets中引入
1)新建assets文件夹添加文件
在main文件夹上新建directory选择assets。
新建vertex.vs顶点着色器文件编写顶点着色器代码
#version 300 es
layout(location = 0) in vec4 vPosition;
void main()
{
gl_Position = vPosition;
}
新建fragment.fs片段着色器文件编写片段着色器代码
#version 300 es
precision mediump float;
out vec4 fragColor;
void main()
{
fragColor = vec4 ( 1.0f, 0.2f, 0.2f, 1.0f );
}
2)Java部分
private AssetManager mrg;
mrg = getResources().getAssets();
再调用native函数将mrg句柄传到native层次
NativeImpl.NativeImpl_InitScene(mrg);
JNI定义接口
public static native void NativeImpl_InitScene(Object mrg);
3)C++部分
创建一个读取assets资源的文件的接口
#ifndef C_NDKTEST_READFILEUTIL_H
#define C_NDKTEST_READFILEUTIL_H
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include "Util.h"
#define TAG "READFILEUTIL"
const char *LoadFileContent(const char *filepath);
AAssetManager *g_mrg;
const char *LoadFileContent(const char *filepath) {
// read shader code form asset
char *fileContent = nullptr;
AAsset *asset = AAssetManager_open(g_mrg, filepath, AASSET_MODE_UNKNOWN);
if (asset == nullptr) {
// LOGE("LoadFileContent asset is null, load shader error ");
LOGD("LoadFileContent asset is null, load shader error ");
}
int filesSize_v = AAsset_getLength(asset);
fileContent = new char[filesSize_v];
AAsset_read(asset, fileContent, filesSize_v);
fileContent[filesSize_v] = '\0';
AAsset_close(asset);
LOGD("LoadFileContent asset is %s", fileContent);
return fileContent;
}
#endif //C_NDKTEST_READFILEUTIL_H
在jni中使用
JNI函数native接口是实现
/*
* Class: com_example_onedrawtri2_NativeImpl
* Method: NativeImpl_InitScene
* Signature: (Ljava/lang/Object;)V
*/
JNIEXPORT void JNICALL NativeImpl_InitScene(JNIEnv *env, jobject instance, jobject msg)
{
g_mrg = AAssetManager_fromJava(env, msg);
}
//使用读取内容
/*
* Class: com_example_onedrawtri2_NativeImpl
* Method: init
* Signature: ()V
*/
JNIEXPORT void JNICALL NativeImpl_init(JNIEnv *env, jobject instance)
{
DrawTriangle::GetInstance();
DrawTriangle::GetInstance()->CreateProgram(LoadFileContent("vertex.vs"), LoadFileContent("fragment.fs"));
}
第五部分多种颜色的三角形
添加 vColor属性
1)顶点着色器
#version 300 es
layout(location = 0) in vec4 vPosition;
layout(location = 1) in vec3 vColor;
out vec3 ourColor;
void main()
{
gl_Position = vPosition;
ourColor = vColor;
}
片段着色器
#version 300 es
precision mediump float;
out vec4 fragColor;
in vec3 ourColor;
void main()
{
fragColor = vec4 ( ourColor.rgb, 1.0 );
}
顶点坐标添加颜色属性
float verticesCor[] = {
// positions // colors
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top
};
2)添加颜色属性绑定顶点坐标
/*
0;指定我们要配置的顶点属性 就是顶点着色器里面location那个
*/
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void *)0);
glEnableVertexAttribArray(0);//以顶点属性位置值0作为参数,启用顶点属性
/*
* 1、配置颜色属性
*/
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void *)(3*sizeof(float)));
glEnableVertexAttribArray(1);//以顶点属性位置值0作为参数,启用顶点属性
参考链接:
https://blog.csdn.net/one_chow_chow/article/details/125888904 NDK OpenGL ES 3.0 :画个三角形