1 前言
1.1 开发该框架的动机
OpenGL ES 是一个渲染指令接口集合,每渲染一帧图像都是一系列渲染指令的排列组合。常用的渲染指令约有 70 个,记住这些渲染指令及其排列组合方式,是一件痛苦的事情。另外,在图形开发中,经常因为功耗、丢帧等问题需要性能优化,如何从框架层面进行性能优化是一件有挑战的问题。
基于上述原因,笔者手撕了一个 nimi 版的渲染框架,将这些常用的渲染指令有条理地封装、组织、归类,方便愉快并高效地进行 OpenGL ES 渲染开发。笔者在 OpenGL ES 领域从业也有些时日,对现有碎片化的知识进行归纳凝练,形成系统的认知,是件势在必行的事。
1.2 为什么选择 native
之所以选择在 native 中开发该渲染框架,是为了使该框架具有更好的跨平台特性和渲染效率。目前大多数平台的 OpenGL ES API 基于 C++ 实现,因此只需更改少量代码就可以将该框架迁移到其他平台上;另外,C++ 代码相较于 Java 等代码具有更高的执行效率。Windows 上的实现详见 → 在Windows上手撕一个mini版的渲染框架。
1.3 一个 mini 版的渲染框架应该具备哪些能力
一个 mini 版的渲染框架需要对 OpenGL ES 的常用指令进行归类(如下图),封装 EGL、error check、Shader Program、Mesh、VAO、VBO、IBO、Texture、FBO 等类,方便开发者快速开发渲染程序,将更多的注意力聚焦在业务上,而不是如何去组织 OpenGL ES 指令上。

1.4 为什么强调 mini 版渲染框架
从渲染指令的角度来看,OpenGL ES 3.0 约有 300 个渲染指令,本文框架只封装其中最常用的 70 个,指令覆盖程度仍有较大提升空间。
从功能的角度来看,笔者深知一个成熟完备的渲染框架应该具备相机、光源、光照模型(Lambert、Phong、PBR 等)、阴影、射线拾取、重力、碰撞检测、粒子系统等功能。
鉴于上述原因,笔者审慎地保留了 "mini" 前缀。
1.5 本框架的优势
本框架具有以下优势。
- 封装友好:对常用的 EGL 和 GL 指令(约 70 个)进行封装,提供了 EGL 环境搭建、着色器程序生成、网格构建、纹理贴图、离屏渲染、异常检测等基础能力,方便开发者快速开发渲染程序,将精力从繁杂的渲染指令中解放出来,将更多的注意力聚焦到业务上。
- 代码规整:框架中多处设计了 bind 和 unbind 接口,用于绑定和解绑 OpenGL ES 状态机相关 “插槽”,如:VBO、IBO、VAO 中都设计了 bind 和 unbind 接口,ShaderProgram、Texture、FBO、TextureAction 中都设计了 bind 接口;另外,在 FBO 中设计了 begin 和 end 接口,很直观地告诉用户夹在这中间的内容将渲染到 FBO。接口规整简洁,方便用户记忆。
- 易于扩展:定义了 TextureAction 接口,并提供 bind 函数,GLTexture、FBO 都继承了 TextureAction,用户自定义的渲染器或特效类也可以继承 TextureAction,将它们统一视为纹理活动(可绑定),这在特效叠加(或后处理)中非常有用,方便管理多渲染目标图层,易于扩展。
- 性能高效:封装了 VBO、IBO、VAO,用于缓存顶点数据、索引、格式等信息到显存,减少 CPU 到 GPU 的数据传输,提高渲染效率;缓存了 attribute 和 uniform 变量的 location,避免 CPU 频繁向 GPU 查询 location,进一步提高渲染效率;基于 C++ 语言实现渲染框架,代码执行效率较高。
- 跨平台:基于 C++ 语言实现,具有更好的跨平台特性;封装了 core_lib,使得平台相关头文件可以轻松替换;封装了 Application,使得平台相关 api 可以轻松替换。
- 方便调试:设计了 EGL_CALL 和 GL_CALL 两个宏,对每个 EGL 和 GL 指令进行异常检测,方便调试渲染指令,并且通过预编译宏 DEBUG 开关动态控制是否生成异常检测的代码,Release 版本会自动屏蔽异常检测代码,避免带来额外功耗。
2 渲染框架
经过深思熟虑,笔者给该渲染框架命名为 glcore,命名空间也是 glcore。本文完整资源(包含 glcore 框架和第 4 节的应用)详见 → 【OpenGL ES】一个mini版的Android native渲染框架 。Windows 版本的 glcore 实现详见 → 在Windows上手撕一个mini版的渲染框架。
2.1 框架结构

2.2 CMakeLists
CMakeLists.txt
# 设置库名 set(LIB_NAME "glcore") # 递归添加源文件列表 file(GLOB_RECURSE GL_CORE_SOURCES src *.cpp) # 添加预构建库 add_library(${LIB_NAME} ${GL_CORE_SOURCES}) # 将当前目录设为公共头文件目录 (任何链接glcore库的目标都能自动获得这个头文件路径) target_include_directories(${LIB_NAME} PUBLIC .) # 添加链接的三方库文件 target_link_libraries(${LIB_NAME} PRIVATE android log EGL GLESv3)
2.3 核心头文件
核心头文件分为对内和对外的,即内部依赖 core_lib,外部开放 core。
core_lib.h
#pragma once /** * glcore 依赖的核心 GL 库, 便于将 glcore 移植到其他平台 * Android: EGL + GLESv3 * Windows: glfw / freeglut + glad / glew * * @author little fat sheep */ #include <EGL/egl.h> #include <GLES3/gl3.h>
之所以要单独拎出 core_lib.h,是为了方便将该框架迁移到其他平台,如 Windows 上依赖的三方渲染库是 glfw / freeglut + glad / glew,如果不抽出 core_lib.h,就需要将很多地方的 egl.h + gl3.h 改为 glfw3.h / freeglut.h + glad.h / glew.h,工作量大,也容易漏改。
core.h
#pragma once /** * glcore核心头文件 * 该头文件是留给外部使用的, glcore内部不能使用, 避免自己包含自己 * @author little fat sheep */ // OpenGL ES API #include "core_lib.h" // glcore 核心头文件 #include "application.h" #include "elg_surface_view.h" #include "format.h" #include "frame_buffer_object.h" #include "gl_inspector.h" #include "gl_texture.h" #include "mesh.h" #include "mesh_utils.h" #include "shader_program.h" #include "texture_action.h" #include "vertex_attribute.h"
core.h 只提供给外部使用,方便外部只需要包含一个头文件,就能获取 glcore 的基础能力。
2.4 Application
Application 主要用于管理全局环境,使用单例模式,方便获取一些全局的变量。它也是 glcore 中唯一一个依赖平台相关的接口(除日志 log 接口外),如:jniEnv、context、m_window 都是 Android 特有的,如果将 glcore 迁移到 Windows 中,这些变量全都要替换或删除,将这些平台相关变量都集中在 Application 中,迁移平台时修改起来也比较容易,避免太分散容易漏掉。
application.h
#pragma once #include <android/native_window.h> #include <jni.h> #define app Application::getInstance() namespace glcore { /** * 应用程序, 存储全局的参数, 便于访问 * @author little fat sheep */ class Application { private: static Application* sInstance; public: JNIEnv* jniEnv = nullptr; jobject context = nullptr; int width = 0; int height = 0; float aspect = 1.0f; private: ANativeWindow* m_window = nullptr; public: static Application* getInstance(); ~Application(); void resize(int width, int height); ANativeWindow* getWindow() { return m_window; } void setWindow(ANativeWindow* window); void releaseWindow(); private: Application() {}; }; } // namespace glcore
application.cpp
#include "glcore/application.h" namespace glcore { Application* Application::sInstance = nullptr; Application *Application::getInstance() { if (sInstance == nullptr) { sInstance = new Application(); } return sInstance; } Application::~Application() { jniEnv->DeleteGlobalRef(context); releaseWindow(); } void Application::resize(int width, int height) { this->width = width; this->height = height; this->aspect = (float) width / (float) height; } void Application::setWindow(ANativeWindow* window) { m_window = window; resize(ANativeWindow_getWidth(window), ANativeWindow_getHeight(window)); } void Application::releaseWindow() { if (m_window) { ANativeWindow_release(m_window); m_window = nullptr; } } } // namespace glcore
2.5 GLInspector
GLInspector 主要用于异常信息检测,另外设计了 EGL_CALL 和 GL_CALL 两个宏,分别对 EGL 和 GL 指令进行装饰。如果定义了 DEBUG 宏,就会对每个 EGL 和 GL 指令进行异常检测,方便调试代码;如果未定义了 DEBUG 宏,就不会进行异常检测。
用户可以在 CMakeLists.txt 中添加预编译宏 DEBUG,这样就可以根据 Release 和 Debug 版本自动构建不同的版本。
if (CMAKE_BUILD_TYPE STREQUAL "Debug") # 添加预编译宏 add_definitions(-DDEBUG) endif ()
gl_inspector.h
#pragma once #include "core_lib.h" #ifdef DEBUG #define EGL_CALL(func) func;GLInspector::checkEGLError(); #define GL_CALL(func) func;GLInspector::checkGLError(); #else #define EGL_CALL(func) func; #define GL_CALL(func) func; #endif namespace glcore { /** * OpenGL ES命令报错监视器 * @author little fat sheep */ class GLInspector { public: static void checkEGLError(const char* tag); // 检查EGL报错信息 static void checkEGLError(); // 通用检查EGL错误 static void printShaderInfoLog(GLuint shader, const char* tag); // 打印Shader错误日志 static void printProgramInfoLog(GLuint program, const char* tag); // 打印Program错误日志 static void checkGLError(const char* tag); // 检查GL报错信息 static void checkGLError(); // 通用检查GL报错信息 }; } // namespace glcore
gl_inspector.cpp
#include <android/log.h> #include <assert.h> #include <string> #include "glcore/gl_inspector.h" #define LOG_TAG "Native: GLInspector" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) using namespace std; namespace glcore { void GLInspector::checkEGLError(const char *tag) { int error = eglGetError(); if (error != EGL_SUCCESS) { LOGE("%s failed: 0x%x", tag, error); } } void GLInspector::checkEGLError() { GLenum errorCode = eglGetError(); if (errorCode != EGL_SUCCESS) { string error; switch (errorCode) { case EGL_BAD_DISPLAY: error = "EGL_BAD_DISPLAY"; break; case EGL_NOT_INITIALIZED: error = "EGL_NOT_INITIALIZED"; break; case EGL_BAD_CONFIG: error = "EGL_BAD_CONFIG"; break; case EGL_BAD_CONTEXT: error = "EGL_BAD_CONTEXT"; break; case EGL_BAD_NATIVE_WINDOW: error = "EGL_BAD_NATIVE_WINDOW"; break; case EGL_BAD_SURFACE: error = "EGL_BAD_SURFACE"; break; case EGL_BAD_CURRENT_SURFACE: error = "EGL_BAD_CURRENT_SURFACE"; break; case EGL_BAD_ACCESS: error = "EGL_BAD_ACCESS"; break; case EGL_BAD_ALLOC: error = "EGL_BAD_ALLOC"; break; case EGL_BAD_ATTRIBUTE: error = "EGL_BAD_ATTRIBUTE"; break; case EGL_BAD_PARAMETER: error = "EGL_BAD_PARAMETER"; break; case EGL_BAD_NATIVE_PIXMAP: error = "EGL_BAD_NATIVE_PIXMAP"; break; case EGL_BAD_MATCH: error = "EGL_BAD_MATCH"; break; case EGL_CONTEXT_LOST: error = "EGL_CONTEXT_LOST"; break; default: error = "UNKNOW"; break; } LOGE("checkEGLError failed: %s, 0x%x", error.c_str(), errorCode); assert(false); } } void GLInspector::printShaderInfoLog(GLuint shader, const char* tag) { char infoLog[512]; glGetShaderInfoLog(shader, 512, nullptr, infoLog); LOGE("%s failed: %s", tag, infoLog); } void GLInspector::printProgramInfoLog(GLuint program, const char* tag) { char infoLog[512]; glGetProgramInfoLog(program, 512, nullptr, infoLog); LOGE("%s failed: %s", tag, infoLog); } void GLInspector::checkGLError(const char *tag) { GLenum error = glGetError(); if(error != GL_NO_ERROR) { LOGE("%s failed: 0x%x", tag, error); } } void GLInspector::checkGLError() { GLenum errorCode = glGetError(); if (errorCode != GL_NO_ERROR) { string error; switch (errorCode) { case GL_INVALID_ENUM: error = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: error = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: error = "GL_INVALID_OPERATION"; break; case GL_INVALID_INDEX: error = "GL_INVALID_INDEX"; break; case GL_INVALID_FRAMEBUFFER_OPERATION: error = "GL_INVALID_FRAMEBUFFER_OPERATION"; break; case GL_OUT_OF_MEMORY: error = "GL_OUT_OF_MEMORY"; break; default: error = "UNKNOW"; break; } LOGE("checkGLError failed: %s, 0x%x", error.c_str(), errorCode); assert(false); } } } // namespace glcore
2.6 EGLSurfaceView
EGLSurfaceView 主要承载了 EGL 环境搭建。EGL 详细介绍见 → 【OpenGL ES】EGL+FBO离屏渲染。
elg_surface_view.h
#include <android/log.h> #include "glcore/application.h" #include "glcore/elg_surface_view.h" #include "glcore/gl_inspector.h" #define LOG_TAG "Native: EGLSurfaceView" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) namespace glcore { EGLSurfaceView::EGLSurfaceView() { LOGI("init"); createDisplay(); createConfig(); createContext(); } EGLSurfaceView::~EGLSurfaceView() { LOGI("destroy"); if (m_renderer) { delete m_renderer; m_renderer = nullptr; } if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY) { // 与显示设备解绑 EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); // 销毁 EGLSurface if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE) { EGL_CALL(eglDestroySurface(m_eglDisplay, m_eglSurface)); delete &m_eglSurface; } // 销毁 EGLContext if (m_eglContext && m_eglContext != EGL_NO_CONTEXT) { EGL_CALL(eglDestroyContext(m_eglDisplay, m_eglContext)); delete &m_eglContext; } // 销毁 EGLDisplay (显示设备) EGL_CALL(eglTerminate(m_eglDisplay)); delete &m_eglDisplay; } delete app; } void EGLSurfaceView::setRenderer(Renderer *renderer) { LOGI("setRenderer"); m_renderer = renderer; } bool EGLSurfaceView::surfaceCreated() { LOGI("createSurface"); createSurface(); makeCurrent(); if (m_renderer && m_firstCreateSurface) { m_renderer->onSurfaceCreated(); m_firstCreateSurface = false; } return true; } void EGLSurfaceView::surfaceChanged(int width, int height) { LOGI("surfaceChanged, width: %d, height: %d", width, height); app->resize(width, height); if (m_renderer) { m_renderer->onSurfaceChanged(width, height); } } void EGLSurfaceView::drawFrame() { if (!m_eglSurface || m_eglSurface == EGL_NO_SURFACE || !m_renderer) { return; } m_renderer->onDrawFrame(); EGL_CALL(eglSwapBuffers(m_eglDisplay, m_eglSurface)); } void EGLSurfaceView::surfaceDestroy() { LOGI("surfaceDestroy"); if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY) { // 与显示设备解绑 EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); // 销毁 EGLSurface if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE) { EGL_CALL(eglDestroySurface(m_eglDisplay, m_eglSurface)); m_eglSurface = nullptr; } } app->releaseWindow(); } // 1.创建EGLDisplay void EGLSurfaceView::createDisplay() { EGL_CALL(m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY)); EGL_CALL(eglInitialize(m_eglDisplay, nullptr, nullptr)); } // 2.创建EGLConfig void EGLSurfaceView::createConfig() { if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY) { const EGLint configAttrs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; EGLint numConfigs; EGL_CALL(eglChooseConfig(m_eglDisplay, configAttrs, &m_eglConfig, 1, &numConfigs)); } } // 3.创建EGLContext void EGLSurfaceView::createContext() { if (m_eglConfig) { const EGLint contextAttrs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; EGL_CALL(m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttrs)); } } // 4.创建EGLSurface void EGLSurfaceView::createSurface() { if (m_eglContext && m_eglContext != EGL_NO_CONTEXT) { EGL_CALL(m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, app->getWindow(), nullptr)); } } // 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay) void EGLSurfaceView::makeCurrent() { if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE) { EGL_CALL(eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)); } } } // namespace glcore
elg_surface_view.cpp
#include <android/log.h> #include "glcore/application.h" #include "glcore/elg_surface_view.h" #include "glcore/gl_inspector.h" #define LOG_TAG "Native: EGLSurfaceView" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) namespace glcore { EGLSurfaceView::EGLSurfaceView() { LOGI("init"); createDisplay(); createConfig(); createContext(); } EGLSurfaceView::~EGLSurfaceView() { LOGI("destroy"); if (m_renderer) { delete m_renderer; m_renderer = nullptr; } if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY) { // 与显示设备解绑 EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); // 销毁 EGLSurface if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE) { EGL_CALL(eglDestroySurface(m_eglDisplay, m_eglSurface)); //GLInspector::checkEGLConfig("eglDestroySurface"); delete &m_eglSurface; } // 销毁 EGLContext if (m_eglContext && m_eglContext != EGL_NO_CONTEXT) { EGL_CALL(eglDestroyContext(m_eglDisplay, m_eglContext)); //GLInspector::checkEGLConfig("eglDestroyContext"); delete &m_eglContext; } // 销毁 EGLDisplay (显示设备) EGL_CALL(eglTerminate(m_eglDisplay)); //GLInspector::checkEGLConfig("eglTerminate"); delete &m_eglDisplay; } delete app; } void EGLSurfaceView::setRenderer(Renderer *renderer) { LOGI("setRenderer"); m_renderer = renderer; } bool EGLSurfaceView::surfaceCreated() { LOGI("createSurface"); createSurface(); makeCurrent(); if (m_renderer && m_firstCreateSurface) { m_renderer->onSurfaceCreated(); m_firstCreateSurface = false; } return true; } void EGLSurfaceView::surfaceChanged(int width, int height) { LOGI("surfaceChanged, width: %d, height: %d", width, height); app->resize(width, height); if (m_renderer) { m_renderer->onSurfaceChanged(width, height); } } void EGLSurfaceView::drawFrame() { if (!m_eglSurface || m_eglSurface == EGL_NO_SURFACE || !m_renderer) { return; } m_renderer->onDrawFrame(); EGL_CALL(eglSwapBuffers(m_eglDisplay, m_eglSurface)); //GLInspector::checkEGLConfig("eglSwapBuffers"); } void EGLSurfaceView::surfaceDestroy() { LOGI("surfaceDestroy"); if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY) { // 与显示设备解绑 EGL_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); // 销毁 EGLSurface if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE) { EGL_CALL(eglDestroySurface(m_eglDisplay, m_eglSurface)); //GLInspector::checkEGLConfig("eglDestroySurface"); m_eglSurface = nullptr; } } app->releaseWindow(); } // 1.创建EGLDisplay void EGLSurfaceView::createDisplay() { EGL_CALL(m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY)); EGL_CALL(eglInitialize(m_eglDisplay, nullptr, nullptr)); //GLInspector::checkEGLConfig("eglInitialize"); } // 2.创建EGLConfig void EGLSurfaceView::createConfig() { if (m_eglDisplay && m_eglDisplay != EGL_NO_DISPLAY) { const EGLint configAttrs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; EGLint numConfigs; EGL_CALL(eglChooseConfig(m_eglDisplay, configAttrs, &m_eglConfig, 1, &numConfigs)); //GLInspector::checkEGLConfig("eglChooseConfig"); } } // 3.创建EGLContext void EGLSurfaceView::createContext() { if (m_eglConfig) { const EGLint contextAttrs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; EGL_CALL(m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttrs)); //GLInspector::checkEGLConfig("eglCreateContext"); } } // 4.创建EGLSurface void EGLSurfaceView::createSurface() { if (m_eglContext && m_eglContext != EGL_NO_CONTEXT) { EGL_CALL(m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, app->getWindow(), nullptr)); //GLInspector::checkEGLConfig("eglCreateWindowSurface"); } } // 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay) void EGLSurfaceView::makeCurrent() { if (m_eglSurface && m_eglSurface != EGL_NO_SURFACE) { EGL_CALL(eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)); //GLInspector::checkEGLConfig("eglMakeCurrent"); } } } // namespace glcore
2.7 ShaderProgram
ShaderProgram 主要用于编译 Shader、链接 Program、设置 attribute 属性、更新 uniform 属性。
glGetAttribLocation、glGetUniformLocation 两个接口需要 CPU 向 GPU 查询 location 信息,并且会频繁调用,为提高性能,笔者设计了 m_attributes 和 m_uniforms 两个 map 存储 name 到 location 的映射,方便快速获取 location,避免 CPU 频繁与 GPU 交互,以提高渲染性能。
shader_program.h
#pragma once #include <map> #include "core_lib.h" using namespace std; namespace glcore { /** * 着色器程序 * @author little fat sheep */ class ShaderProgram { public: static constexpr char* ATTRIBUTE_POSITION = "a_position"; // 着色器中位置属性名 static constexpr char* ATTRIBUTE_NORMAL = "a_normal"; // 着色器中位法线性名 static constexpr char* ATTRIBUTE_COLOR = "a_color"; // 着色器中颜色属性名 static constexpr char* ATTRIBUTE_TEXCOORD = "a_texCoord"; // 着色器中纹理坐标属性名 static constexpr char* ATTRIBUTE_TANGENT = "a_tangent"; // 着色器中切线属性名 static constexpr char* ATTRIBUTE_BINORMAL = "a_binormal"; // 着色器中副切线属性名 static constexpr char* UNIFORM_TEXTURE = "u_texture"; // 着色器中纹理名 static constexpr char* UNIFORM_VP = "u_projectionViewMatrix"; // 着色器中VP名 private: GLuint m_program; map<const char*, int> m_attributes; map<const char*, int> m_uniforms; public: ShaderProgram(const char* vertexCode, const char* fragmentCode); ~ShaderProgram(); void bind(); GLuint getHandle() { return m_program; } // 操作attribute属性 void enableVertexAttribArray(const char* name); void enableVertexAttribArray(int location); void setVertexAttribPointer(const char* name, int size, int type, bool normalize, int stride, int offset); void setVertexAttribPointer(int location, int size, int type, bool normalize, int stride, int offset); void disableVertexAttribArray(const char* name); void disableVertexAttribArray(int location); // 操作uniform属性 void setUniformi(const char* name, int value); void setUniformi(int location, int value); void setUniformi(const char* name, int value1, int value2); void setUniformi(int location, int value1, int value2); void setUniformi(const char* name, int value1, int value2, int value3); void setUniformi(int location, int value1, int value2, int value3); void setUniformi(const char* name, int value1, int value2, int value3, int value4); void setUniformi(int location, int value1, int value2, int value3, int value4); void setUniformf(const char* name, float value); void setUniformf(int location, float value); void setUniformf(const char* name, float value1, float value2); void setUniformf(int location, float value1, float value2); void setUniformf(const char* name, float value1, float value2, int value3); void setUniformf(int location, float value1, float value2, int value3); void setUniformf(const char* name, float value1, float value2, int value3, int value4); void setUniformf(int location, float value1, float value2, int value3, int value4); void setUniform1fv(const char* name, int length, const float values[]); void setUniform1fv(int location, int count, float const values[]); void setUniform2fv(const char* name, int count, const float values[]); void setUniform2fv(int location, int count, const float values[]); void setUniform3fv(const char* name, int count, const float values[]); void setUniform3fv(int location, int count, const float values[]); void setUniform4fv(const char* name, int count, const float values[]); void setUniform4fv(int location, int count, const float values[]); void setUniformMatrix2fv(const char* name, int count, bool transpose, const float *value); void setUniformMatrix2fv(int location, int count, bool transpose, const float *value); void setUniformMatrix3fv(const char* name, int count, bool transpose, const float *value); void setUniformMatrix3fv(int location, int count, bool transpose, const float *value); void setUniformMatrix4fv(const char* name, int count, bool transpose, const float *value); void setUniformMatrix4fv(int location, int count, bool transpose, const float *value); int fetchAttributeLocation(const char* name); int fetchUniformLocation(const char* name); private: void compileShaders(const char* vertexCode, const char* fragmentCode); GLuint loadShader(GLenum type, const char* source); GLuint linkProgram(GLuint vertexShader, GLuint fragmentShader); }; } // namespace glcore
shader_program.cpp
#include <android/log.h> #include "glcore/gl_inspector.h" #include "glcore/shader_program.h" #define LOG_TAG "Native: ShaderProgram" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) namespace glcore { ShaderProgram::ShaderProgram(const char* vertexCode, const char* fragmentCode) { compileShaders(vertexCode, fragmentCode); } ShaderProgram::~ShaderProgram() { if (m_program) { GL_CALL(glUseProgram(0)); GL_CALL(glDeleteProgram(m_program)); m_program = 0; } m_attributes.clear(); m_uniforms.clear(); } void ShaderProgram::bind() { GL_CALL(glUseProgram(m_program)); } void ShaderProgram::enableVertexAttribArray(const char* name) { int location = fetchAttributeLocation(name); enableVertexAttribArray(location); } void ShaderProgram::enableVertexAttribArray(int location) { GL_CALL(glEnableVertexAttribArray(location)); } void ShaderProgram::setVertexAttribPointer(const char *name, int size, int type, bool normalize, int stride, int offset) { int location = fetchAttributeLocation(name); setVertexAttribPointer(location, size, type, normalize, stride, offset); } void ShaderProgram::setVertexAttribPointer(int location, int size, int type, bool normalize, int stride, int offset) { GL_CALL(glVertexAttribPointer(location, size, type, normalize, stride, (void*) offset)); } void ShaderProgram::disableVertexAttribArray(const char* name) { int location = fetchAttributeLocation(name); disableVertexAttribArray(location); } void ShaderProgram::disableVertexAttribArray(int location) { GL_CALL(glDisableVertexAttribArray(location)); } void ShaderProgram::setUniformi(const char* name, int value) { int location = fetchUniformLocation(name); GL_CALL(glUniform1i(location, value)); } void ShaderProgram::setUniformi(int location, int value) { GL_CALL(glUniform1i(location, value)); } void ShaderProgram::setUniformi(const char* name, int value1, int value2) { int location = fetchUniformLocation(name); GL_CALL(glUniform2i(location, value1, value2)); } void ShaderProgram::setUniformi(int location, int value1, int value2) { GL_CALL(glUniform2i(location, value1, value2)); } void ShaderProgram::setUniformi(const char* name, int value1, int value2, int value3) { int location = fetchUniformLocation(name); GL_CALL(glUniform3i(location, value1, value2, value3)); } void ShaderProgram::setUniformi(int location, int value1, int value2, int value3) { GL_CALL(glUniform3i(location, value1, value2, value3)); } void ShaderProgram::setUniformi(const char* name, int value1, int value2, int value3, int value4) { int location = fetchUniformLocation(name); GL_CALL(glUniform4i(location, value1, value2, value3, value4)); } void ShaderProgram::setUniformi(int location, int value1, int value2, int value3, int value4) { GL_CALL(glUniform4i(location, value1, value2, value3, value4)); } void ShaderProgram::setUniformf(const char* name, float value) { int location = fetchUniformLocation(name); GL_CALL(glUniform1f(location, value)); } void ShaderProgram::setUniformf(int location, float value) { GL_CALL(glUniform1f(location, value)); } void ShaderProgram::setUniformf(const char* name, float value1, float value2) { int location = fetchUniformLocation(name); GL_CALL(glUniform2f(location, value1, value2)); } void ShaderProgram::setUniformf(int location, float value1, float value2) { GL_CALL(glUniform2f(location, value1, value2)); } void ShaderProgram::setUniformf(const char* name, float value1, float value2, int value3) { int location = fetchUniformLocation(name); GL_CALL(glUniform3f(location, value1, value2, value3)); } void ShaderProgram::setUniformf(int location, float value1, float value2, int value3) { GL_CALL(glUniform3f(location, value1, value2, value3)); } void ShaderProgram::setUniformf(const char* name, float value1, float value2, int value3, int value4) { int location = fetchUniformLocation(name); GL_CALL(glUniform4f(location, value1, value2, value3, value4)); } void ShaderProgram::setUniformf(int location, float value1, float value2, int value3, int value4) { GL_CALL(glUniform4f(location, value1, value2, value3, value4)); } void ShaderProgram::setUniform1fv(const char* name, int count, const float values[]) { int location = fetchUniformLocation(name); GL_CALL(glUniform1fv(location, count, values)); } void ShaderProgram::setUniform1fv(int location, int count, const float values[]) { GL_CALL(glUniform1fv(location, count, values)); } void ShaderProgram::setUniform2fv(const char* name, int count, const float values[]) { int location = fetchUniformLocation(name); GL_CALL(glUniform2fv(location, count / 2, values)); } void ShaderProgram::setUniform2fv(int location, int count, const float values[]) { GL_CALL(glUniform2fv(location, count / 2, values)); } void ShaderProgram::setUniform3fv(const char* name, int count, const float values[]) { int location = fetchUniformLocation(name); GL_CALL(glUniform3fv(location, count / 3, values)); } void ShaderProgram::setUniform3fv(int location, int count, const float values[]) { GL_CALL(glUniform3fv(location, count / 3, values)); } void ShaderProgram::setUniform4fv(const char* name, int count, const float values[]) { int location = fetchUniformLocation(name); GL_CALL(glUniform4fv(location, count / 4, values)); } void ShaderProgram::setUniform4fv(int location, int count, const float values[]) { GL_CALL(glUniform4fv(location, count / 4, values)); } void ShaderProgram::setUniformMatrix2fv(const char* name, int count, bool transpose, const float *value) { int location = fetchUniformLocation(name); GL_CALL(glUniformMatrix2fv(location, count, transpose, value)); } void ShaderProgram::setUniformMatrix2fv(int location, int count, bool transpose, const float *value) { GL_CALL(glUniformMatrix2fv(location, count, transpose, value)); } void ShaderProgram::setUniformMatrix3fv(const char* name, int count, bool transpose, const float *value) { int location = fetchUniformLocation(name); GL_CALL(glUniformMatrix3fv(location, count, transpose, value)); } void ShaderProgram::setUniformMatrix3fv(int location, int count, bool transpose, const float *value) { GL_CALL(glUniformMatrix3fv(location, count, transpose, value)); } void ShaderProgram::setUniformMatrix4fv(const char* name, int count, bool transpose, const float *value) { int location = fetchUniformLocation(name); GL_CALL(glUniformMatrix4fv(location, count, transpose, value)); } void ShaderProgram::setUniformMatrix4fv(int location, int count, bool transpose, const float *value) { GL_CALL(glUniformMatrix4fv(location, count, transpose, value)); } int ShaderProgram::fetchAttributeLocation(const char* name) { int location; auto it = m_attributes.find(name); if (it == m_attributes.end()) { GL_CALL(location = glGetAttribLocation(m_program, name)); if (location == -1) { LOGI("no attribute: %s", name); //GLInspector::printProgramInfoLog(m_program, "fetchAttributeLocation"); return -1; } m_attributes[name] = location; } else { location = it->second; } return location; } int ShaderProgram::fetchUniformLocation(const char* name) { int location; auto it = m_uniforms.find(name); if (it == m_uniforms.end()) { GL_CALL(location = glGetUniformLocation(m_program, name)); if (location == -1) { LOGI("no uniform: %s", name); //GLInspector::printProgramInfoLog(m_program, "fetchUniformLocation"); return -1; } m_uniforms[name] = location; } else { location = it->second; } return location; } void ShaderProgram::compileShaders(const char* vertexCode, const char* fragmentCode) { GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexCode); GLuint fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentCode); m_program = linkProgram(vertexShader, fragmentShader); } GLuint ShaderProgram::loadShader(GLenum type, const char* source) { GL_CALL(GLuint shader = glCreateShader(type)); GL_CALL(glShaderSource(shader, 1, &source, nullptr)); GL_CALL(glCompileShader(shader)); GLint success; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { GLInspector::printShaderInfoLog(shader, "loadShader"); return 0; } return shader; } GLuint ShaderProgram::linkProgram(GLuint vertexShader, GLuint fragmentShader) { GL_CALL(GLuint program = glCreateProgram()); GL_CALL(glAttachShader(program, vertexShader)); GL_CALL(glAttachShader(program, fragmentShader)); GL_CALL(glLinkProgram(program)); GLint success; glGetProgramiv(program, GL_LINK_STATUS, &success); if (!success) { GLInspector::printProgramInfoLog(m_program, "linkProgram"); } GL_CALL(glDeleteShader(vertexShader)); GL_CALL(glDeleteShader(fragmentShader)); return program; } } // namespace glcore
2.8 VBO
VBO 是 Vertex Buffer Object 的简称,即顶点缓冲对象,作用是缓存顶点数据到显存中,避免频繁调用 glVertexAttribPointer 传输顶点数据,减少 CPU 到 GPU 的数据传输,提高渲染效率。
顶点属性主要有位置、颜色、纹理坐标、法线、切线、副切线等,每个属性又有属性标识、维数、是否已标准化、数据类型、偏移、别名、纹理单元等。
由于 VBO 中有多个属性数据,每个属性有多个字段,笔者除了封装 VertexBufferObject 类,还封装了 VertexAttributes 和 VertexAttribute 两个类。VertexAttribute 是属性描述类,VertexAttributes 是属性描述集合。
vertex_buffer_object.h
#pragma once #include <initializer_list> #include <vector> #include "core_lib.h" #include "shader_program.h" #include "vertex_attributes.h" #include "vertex_attribute.h" #include "vertex_attributes.h" using namespace std; namespace glcore { /** * 顶点属性缓冲对象 (简称VBO) * @author little fat sheep */ class VertexBufferObject { protected: bool m_isBound = false; // 是否已绑定到VBO (或VAO) bool m_isDirty = false; // 是否有脏数据 (缓存的数据需要更新) private: GLuint m_vboHandle; // VBO句柄 VertexAttributes* m_attributes; // 顶点属性 GLuint m_usage; // GL_STATIC_DRAW 或 GL_DYNAMIC_DRAW const float* m_vertices; // 顶点属性数据 int m_vertexNum = 0; // 顶点个数 int m_bytes = 0; // 顶点属性字节数 public: VertexBufferObject(bool isStatic, initializer_list<VertexAttribute*> attributes); VertexBufferObject(bool isStatic, VertexAttributes* attributes); virtual ~VertexBufferObject(); void setVertices(float* vertices, int bytes); void bind(ShaderProgram* shader); virtual void bind(ShaderProgram* shader, int* locations); void unbind(ShaderProgram* shader); virtual void unbind(ShaderProgram* shader, int* locations); int getNumVertices() { return m_vertexNum; } private: void applyBufferData(); // 缓存数据 }; } // namespace glcore
vertex_buffer_object.cpp
#include <android/log.h> #include "glcore/gl_inspector.h" #include "glcore/vertex_buffer_object.h" #define LOG_TAG "Native: VertexBufferObject" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) namespace glcore { VertexBufferObject::VertexBufferObject(bool isStatic, initializer_list<VertexAttribute*> attributes): VertexBufferObject(isStatic, new VertexAttributes(attributes)) { } VertexBufferObject::VertexBufferObject(bool isStatic, VertexAttributes* attributes): m_attributes(attributes) { m_usage = isStatic ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW; GL_CALL(glGenBuffers(1, &m_vboHandle)); LOGI("init: %d", m_vboHandle); } VertexBufferObject::~VertexBufferObject() { LOGI("destroy"); GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0)); GL_CALL(glDeleteBuffers(1, &m_vboHandle)); m_vboHandle = 0; delete m_attributes; delete[] m_vertices; } void VertexBufferObject::setVertices(float* vertices, int bytes) { m_vertices = vertices; m_vertexNum = bytes / m_attributes->vertexSize; m_bytes = bytes; m_isDirty = true; if (m_isBound) { applyBufferData(); } } void VertexBufferObject::bind(ShaderProgram* shader) { bind(shader, nullptr); } void VertexBufferObject::bind(ShaderProgram* shader, int* locations) { GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_vboHandle)); if (m_isDirty) { applyBufferData(); } if (locations == nullptr) { for (int i = 0; i < m_attributes->size(); i++) { VertexAttribute* attribute = m_attributes->get(i); shader->enableVertexAttribArray(attribute->alias); shader->setVertexAttribPointer(attribute->alias, attribute->numComponents, attribute->type, attribute->normalized, m_attributes->vertexSize, attribute->offset); } } else { for (int i = 0; i < m_attributes->size(); i++) { VertexAttribute* attribute = m_attributes->get(i); shader->enableVertexAttribArray(locations[i]); shader->setVertexAttribPointer(locations[i], attribute->numComponents, attribute->type, attribute->normalized, m_attributes->vertexSize, attribute->offset); } } m_isBound = true; } void VertexBufferObject::unbind(ShaderProgram* shader) { unbind(shader, nullptr); } void VertexBufferObject::unbind(ShaderProgram* shader, int* locations) { if (locations == nullptr) { for (int i = 0; i < m_attributes->size(); i++) { shader->disableVertexAttribArray(m_attributes->get(i)->alias); } } else { for (int i = 0; i < m_attributes->size(); i++) { shader->disableVertexAttribArray(locations[i]); } } m_isBound = false; } void VertexBufferObject::applyBufferData() { GL_CALL(glBufferData(GL_ARRAY_BUFFER, m_bytes, m_vertices, m_usage)); //GLInspector::checkGLError("vbo: applyBufferData"); m_isDirty = false; } } // namespace glcore
vertex_attributes.h
#pragma once #include <initializer_list> #include <vector> #include "vertex_attribute.h" using namespace std; namespace glcore { /** * 顶点属性集(位置、颜色、纹理坐标、法线、切线、副切线等中的一部分) * 每个顶点属性可以看作一个通道, 这个通道可能是多维的, 每个维度可能是多字节的 * @author little fat sheep */ class VertexAttributes { public: int vertexSize; // 所有顶点属性的字节数 private: vector<VertexAttribute*> m_attributes; // 顶点属性列表 public: VertexAttributes(initializer_list<VertexAttribute*> attributes); ~VertexAttributes(); VertexAttribute* get(int index); // 根据索引获取属性 int size(); // 获取属性个数 private: int calculateOffsets(); // 计算偏移 }; /** * 顶点属性标识 * @author little fat sheep */ class Usage { public: static const int Position = 1; static const int ColorUnpacked = 2; static const int ColorPacked = 4; static const int Normal = 8; static const int TextureCoordinates = 16; static const int Tangent = 32; static const int BiNormal = 64; }; } // namespace glcore
vertex_attributes.cpp
#include "glcore/vertex_attributes.h" namespace glcore { VertexAttributes::VertexAttributes(initializer_list<VertexAttribute*> attributes): m_attributes(attributes) { vertexSize = calculateOffsets(); } VertexAttributes::~VertexAttributes() { m_attributes.clear(); } VertexAttribute* VertexAttributes::get(int index) { if (index >= 0 && index < m_attributes.size()) { return m_attributes[index]; } return nullptr; } int VertexAttributes::size() { return m_attributes.size(); } int VertexAttributes::calculateOffsets() { int count = 0; for (VertexAttribute* attribute : m_attributes) { attribute->offset = count; count += attribute->getSizeInBytes(); } return count; } } // namespace glcore
vertex_attribute.h
#pragma once namespace glcore { /** * 单个顶点属性(位置、颜色、纹理坐标、法线、切线、副切线等中的一个) * 每个顶点属性可以看作一个通道, 这个通道可能是多维的, 每个维度可能是多字节的 * @author little fat sheep */ class VertexAttribute { public: int usage; // 顶点属性标识 int numComponents; // 顶点属性维数 (如顶点坐标属性是3维的, 纹理坐标是2维的) bool normalized; // 顶点属性是否已经标准化 (有符号: -1~1, 无符号: 0~1) int type; // 顶点属性的变量类型 (GL_FLOAT、GL_UNSIGNED_BYTE等) int offset; // 顶点属性在字节上的偏移 const char* alias; // 顶点属性别名 (着色器中变量名) int unit; // 纹理单元 (可能有多个纹理, 可选) public: VertexAttribute(int usage, int numComponents, const char* alias); VertexAttribute(int usage, int numComponents, const char* alias, int unit); VertexAttribute(int usage, int numComponents, int type, bool normalized, const char* alias); VertexAttribute(int usage, int numComponents, int type, bool normalized, const char* alias, int unit); ~VertexAttribute(); static VertexAttribute* Position(); // 位置参数信息 static VertexAttribute* TexCoords(int unit); // 纹理坐标参数信息 static VertexAttribute* Normal(); // 法线参数信息 static VertexAttribute* ColorPacked(); // 颜色参数信息 static VertexAttribute* ColorUnpacked(); // 颜色参数信息 static VertexAttribute* Tangent(); // 切线参数信息 static VertexAttribute* Binormal(); // 副切线参数信息 int getSizeInBytes(); // 属性对应的字节数 private: void create(int usage, int numComponents, int type, bool normalized, const char* alias, int unit); }; } // namespace glcore
vertex_attribute.cpp
#include <android/log.h> #include <string> #include "glcore/shader_program.h" #include "glcore/vertex_attribute.h" #include "glcore/vertex_attributes.h" #define LOG_TAG "Native: VertexAttribute" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) using namespace std; namespace glcore { VertexAttribute::VertexAttribute(int usage, int numComponents, const char* alias): VertexAttribute(usage, numComponents, alias, 0) { } VertexAttribute::VertexAttribute(int usage, int numComponents, const char* alias, int unit) { int type = usage == Usage::ColorPacked ? GL_UNSIGNED_BYTE : GL_FLOAT; bool normalized = usage == Usage::ColorPacked; create(usage, numComponents, type, normalized, alias, unit); } VertexAttribute::VertexAttribute(int usage, int numComponents, int type, bool normalized, const char* alias) { create(usage, numComponents, type, normalized, alias, 0); } VertexAttribute::VertexAttribute(int usage, int numComponents, int type, bool normalized, const char* alias, int unit) { create(usage, numComponents, type, normalized, alias, unit); } VertexAttribute::~VertexAttribute() { free((void*)alias); } VertexAttribute* VertexAttribute::Position() { return new VertexAttribute(Usage::Position, 3, ShaderProgram::ATTRIBUTE_POSITION); } VertexAttribute* VertexAttribute::TexCoords(int unit) { string str = string(ShaderProgram::ATTRIBUTE_TEXCOORD) + to_string(unit); // 复制字符串, 避免str被回收导致悬垂指针问题, 通过free((void*)alias)释放内存 const char* combined = strdup(str.c_str()); return new VertexAttribute(Usage::TextureCoordinates, 2, combined, unit); } VertexAttribute* VertexAttribute::Normal() { return new VertexAttribute(Usage::Normal, 3, ShaderProgram::ATTRIBUTE_NORMAL); } VertexAttribute* VertexAttribute::ColorPacked() { return new VertexAttribute(Usage::ColorPacked, 4, GL_UNSIGNED_BYTE, true, ShaderProgram::ATTRIBUTE_COLOR); } VertexAttribute* VertexAttribute::ColorUnpacked() { return new VertexAttribute(Usage::ColorUnpacked, 4, GL_FLOAT, false, ShaderProgram::ATTRIBUTE_COLOR); } VertexAttribute* VertexAttribute::Tangent() { return new VertexAttribute(Usage::Tangent, 3, ShaderProgram::ATTRIBUTE_TANGENT); } VertexAttribute* VertexAttribute::Binormal() { return new VertexAttribute(Usage::BiNormal, 3, ShaderProgram::ATTRIBUTE_BINORMAL); } int VertexAttribute::getSizeInBytes() { switch (type) { case GL_FLOAT: case GL_FIXED: return 4 * numComponents; case GL_UNSIGNED_BYTE: case GL_BYTE: return numComponents; case GL_UNSIGNED_SHORT: case GL_SHORT: return 2 * numComponents; } return 0; } void VertexAttribute::create(int usage, int numComponents, int type, bool normalized, const char* alias, int unit) { this->usage = usage; this->numComponents = numComponents; this->type = type; this->normalized = normalized; this->alias = alias; this->unit = unit; LOGI("create, alias: %s", alias); } } // namespace glcore
2.9 VAO
VAO 是 Vertex Array Object 的简称,即顶点数组对象,作用是缓存顶点属性的指针和描述(或格式)信息,简化顶点属性设置的流程,避免频繁调用 glVertexAttribPointer 设置属性描述(或格式)信息,减少 CPU 与 GPU 的交互,提高渲染效率。
vertex_buffer_object_with_vao.h
#pragma once #include <initializer_list> #include "core_lib.h" #include "vertex_buffer_object.h" namespace glcore { /** * 携带VAO的顶点属性缓冲对象 * @author little fat sheep */ class VertexBufferObjectWithVAO : public VertexBufferObject { private: GLuint m_vaoHandle; // VAO句柄 public: VertexBufferObjectWithVAO(bool isStatic, initializer_list<VertexAttribute*> attributes); VertexBufferObjectWithVAO(bool isStatic, VertexAttributes* attributes); ~VertexBufferObjectWithVAO() override; void bind(ShaderProgram* shader, int* locations) override; void unbind(ShaderProgram* shader, int* locations) override; }; } // namespace glcore
vertex_buffer_object_with_vao.cpp
#include <android/log.h> #include "glcore/gl_inspector.h" #include "glcore/vertex_buffer_object_with_vao.h" #define LOG_TAG "Native: VertexBufferObjectWithVAO" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) namespace glcore { VertexBufferObjectWithVAO::VertexBufferObjectWithVAO(bool isStatic, initializer_list<VertexAttribute*> attributes): VertexBufferObjectWithVAO(isStatic, new VertexAttributes(attributes)) { } VertexBufferObjectWithVAO::VertexBufferObjectWithVAO(bool isStatic, VertexAttributes* attributes): VertexBufferObject(isStatic, attributes) { GL_CALL(glGenVertexArrays(1, &m_vaoHandle)); LOGI("init: %d", m_vaoHandle); } VertexBufferObjectWithVAO::~VertexBufferObjectWithVAO() { LOGI("destroy"); GL_CALL(glDeleteVertexArrays(1, &m_vaoHandle)); } void VertexBufferObjectWithVAO::bind(ShaderProgram* shader, int* locations) { GL_CALL(glBindVertexArray(m_vaoHandle)); if (m_isDirty) { VertexBufferObject::bind(shader, locations); } m_isBound = true; } void VertexBufferObjectWithVAO::unbind(ShaderProgram* shader, int* locations) { GL_CALL(glBindVertexArray(0)); m_isBound = false; } } // namespace glcore
2.10 IBO
IBO 是 Index Buffer Object 的简称,即索引缓冲对象,作用是缓存顶点索引到显存中,避免频繁调用 glDrawElements 传输顶点索引,减少 CPU 到 GPU 的数据传输,提高渲染效率。由于 IBO 绑定的是 OpenGL ES 状态机的 GL_ELEMENT_ARRAY_BUFFER “插槽”,并且对应的绘制指令又是 glDrawElements (都有 Element),因此 IBO 也被称为 EBO。
index_buffer_object.h
#pragma once #include "core_lib.h" namespace glcore { /** * 顶点索引缓冲对象 (简称IBO) * @author little fat sheep */ class IndexBufferObject { private: GLuint m_iboHandle; // IBO句柄 GLuint m_usage; // GL_STATIC_DRAW 或 GL_DYNAMIC_DRAW GLenum m_type = GL_UNSIGNED_SHORT; // 索引数据类型 (GL_UNSIGNED_SHORT 或 GL_UNSIGNED_INT) const void* m_indices; // 顶点索引数据(short*或int*类型) int m_indexNum = 0; // 索引个数 int m_bytes = 0; // 顶点索引字节数 bool m_isDirty = false; // 是否有脏数据 (缓存的数据需要更新) bool m_isBound = false; // 是否已绑定到IBO public: IndexBufferObject(bool isStatic); IndexBufferObject(bool isStatic, GLenum type); ~IndexBufferObject(); void setIndices (void* indices, int bytes); void setIndices (void* indices, int bytes, GLenum type); void bind(); void unbind(); int getNumIndices() { return m_indexNum; } GLenum getType() { return m_type; } private: void applyBufferData(); // 缓存数据 int getTypeSize(); // 获取type对应的字节数 }; } // namespace glcore
index_buffer_object.cpp
#include <android/log.h> #include "glcore/gl_inspector.h" #include "glcore/index_buffer_object.h" #define LOG_TAG "Native: IndexBufferObject" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) namespace glcore { IndexBufferObject::IndexBufferObject(bool isStatic): IndexBufferObject(isStatic, GL_UNSIGNED_SHORT) { } IndexBufferObject::IndexBufferObject(bool isStatic, GLenum type) { m_usage = isStatic ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW; m_type = type; GL_CALL(glGenBuffers(1, &m_iboHandle)); LOGI("init: %d", m_iboHandle); } IndexBufferObject::~IndexBufferObject() { LOGI("destroy"); GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); GL_CALL(glDeleteBuffers(1, &m_iboHandle)); m_iboHandle = 0; delete[] m_indices; } void IndexBufferObject::setIndices(void* indices, int bytes) { setIndices(indices, bytes, m_type); } void IndexBufferObject::setIndices(void* indices, int bytes, GLenum type) { m_indices = indices; m_type = type; m_indexNum = bytes > 0 ? bytes / getTypeSize() : 0; m_bytes = bytes; m_isDirty = true; if (m_isBound) { applyBufferData(); } } void IndexBufferObject::bind() { GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_iboHandle)); if (m_isDirty) { applyBufferData(); } m_isBound = true; } void IndexBufferObject::unbind() { GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); m_isBound = false; } void IndexBufferObject::applyBufferData() { GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_bytes, m_indices, m_usage)); //GLInspector::checkGLError("ibo: applyBufferData"); m_isDirty = false; } int IndexBufferObject::getTypeSize() { switch (m_type) { case GL_UNSIGNED_SHORT: return 2; case GL_UNSIGNED_INT: return 4; } return 2; } } // namespace glcore
2.11 Mesh
Mesh 是网格类,用于管理顶点数据、索引、描述(或格式)等信息,由于 VBO 管理了顶点数据、IBO 管理了顶点索引、VAO 管理了顶点描述(或格式),因此 Mesh 只需管理 VBO、IBO、VAO。另外 IBO 和 VAO 是可选的,Mesh 中需要根据用户的行为调整渲染指令。
为方便用户快速创建平面网格,笔者提供了 MeshUtils 类,用户也可以根据该类提供的模板创建自己的网格。
mesh.h
#pragma once #include <initializer_list> #include "core_lib.h" #include "index_buffer_object.h" #include "shader_program.h" #include "vertex_buffer_object.h" #include "vertex_attribute.h" #include "vertex_attributes.h" using namespace std; namespace glcore { /** * 网格 * @author little fat sheep */ class Mesh { private: VertexBufferObject* m_vbo; // 顶点属性缓冲对象 IndexBufferObject* m_ibo; // 顶点索引缓冲对象 GLenum m_mode = GL_TRIANGLES; // 渲染模式 (GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN等) public: Mesh(bool isStatic, initializer_list<VertexAttribute*> attributes); Mesh(bool isStatic, VertexAttributes* attributes); Mesh(bool useVao, bool isStatic, initializer_list<VertexAttribute*> attributes); Mesh(bool useVao, bool isStatic, VertexAttributes* attributes); ~Mesh(); void setVertices(float* vertices, int bytes); // 设置顶点属性 void setIndices(void* indices, int bytes); // 设置顶点索引 void setIndices(void* indices, int bytes, GLenum type); // 设置顶点索引 void setMode(GLenum mode); // 设置渲染模式 void render(ShaderProgram* shader); // 渲染 }; } // namespace glcore
mesh.cpp
#include "glcore/gl_inspector.h" #include "glcore/mesh.h" #include "glcore/vertex_buffer_object_with_vao.h" namespace glcore { Mesh::Mesh(bool isStatic, initializer_list<VertexAttribute*> attributes): Mesh(true, isStatic, new VertexAttributes(attributes)) { } Mesh::Mesh(bool isStatic, VertexAttributes* attributes): Mesh(true, isStatic, attributes) { } Mesh::Mesh(bool useVao, bool isStatic, initializer_list<VertexAttribute*> attributes): Mesh(useVao, isStatic, new VertexAttributes(attributes)) { } Mesh::Mesh(bool useVao, bool isStatic, VertexAttributes* attributes) { m_vbo = useVao ? new VertexBufferObjectWithVAO(isStatic, attributes) : new VertexBufferObject(isStatic, attributes); m_ibo = new IndexBufferObject(isStatic); } Mesh::~Mesh() { delete m_vbo; delete m_ibo; } void Mesh::setVertices(float* vertices, int bytes) { m_vbo->setVertices(vertices, bytes); } void Mesh::setIndices(void* indices, int bytes) { m_ibo->setIndices(indices, bytes); } void Mesh::setIndices(void* indices, int bytes, GLenum type) { m_ibo->setIndices(indices, bytes, type); } void Mesh::setMode(GLenum mode) { m_mode = mode; } void Mesh::render(ShaderProgram* shader) { m_vbo->bind(shader); if (m_ibo->getNumIndices() > 0) { m_ibo->bind(); GL_CALL(glDrawElements(m_mode, m_ibo->getNumIndices(), m_ibo->getType(), nullptr)); m_ibo->unbind(); } else { GL_CALL(glDrawArrays(m_mode, 0, m_vbo->getNumVertices())); } m_vbo->unbind(shader); } } // namespace glcore
mesh_utils.h
#pragma once #include "mesh.h" namespace glcore { /** * 网格工具类 * @author little fat sheep */ class MeshUtils { public: static Mesh* createRect(bool reverse); private: static float* getRectVertices(bool reverse); }; } // namespace glcore
mesh_utils.cpp
#include "glcore/mesh_utils.h" namespace glcore { Mesh* MeshUtils::createRect(bool reverse) { Mesh* mesh = new Mesh(true, { VertexAttribute::Position(), VertexAttribute::TexCoords(0) }); float* vertices = getRectVertices(reverse); mesh->setVertices(vertices, 4 * 5 * sizeof(float)); void* indices = new short[] { 0, 1, 2, 2, 3, 0 }; mesh->setIndices(indices, 6 * sizeof(short)); return mesh; } float* MeshUtils::getRectVertices(bool reverse) { if (reverse) { return new float[] { // 中间渲染(FBO)使用 -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // 左下 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // 右下 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // 右上 -1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上 }; } return new float[] { // 终端渲染使用 -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, // 左下 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, // 右下 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右上 -1.0f, 1.0f, 0.0f, 0.0f, 0.0f // 左上 }; } } // namespace glcore
2.12 GLTexture
封装 GLTexture 类是了方便用户进行纹理贴图。为了方便管理多渲染目标图层,定义了 TextureAction 接口,并提供 bind 函数,GLTexture、FBO 都继承了 TextureAction,用户自定义的渲染器或特效类也可以继承 TextureAction,将它们统一视为纹理活动(可绑定),这在特效叠加(或后处理)中非常有用,易于扩展。
texture_action.h
#pragma once #include "core_lib.h" #include "shader_program.h" namespace glcore { /** * 纹理活动 (纹理绑定、FBO绑定) * @author little fat sheep */ class TextureAction { public: virtual ~TextureAction() = default; virtual void setTexParameter(GLint filter, GLint wrap) {} virtual void setBindParameter(char* alias, GLenum unit) {} virtual void bind(ShaderProgram* shader) = 0; }; } // namespace glcore
gl_texture.h
#pragma once #include "core_lib.h" #include "shader_program.h" #include "texture_action.h" namespace glcore { /** * 纹理贴图 * @author little fat sheep */ class GLTexture: public TextureAction { private: GLuint m_textureHandle = 0; // 纹理句柄 int m_width = 0; // 纹理宽度 int m_height = 0; // 纹理高度 GLint m_filter = GL_LINEAR; // 滤波方式 GLint m_wrap = GL_CLAMP_TO_EDGE; // 环绕方式 const char* m_alias = ShaderProgram::UNIFORM_TEXTURE; // 纹理别名(着色器中变量名) GLenum m_unit = 0; // 纹理单元 (可能有多个纹理) bool m_isDirty = false; // 是否有脏数据 (纹理参数需要更新) public: GLTexture(int width, int height); GLTexture(void *buffer, int width, int height); ~GLTexture() override; void setTexture(const void *buffer); void setTexParameter(GLint filter, GLint wrap) override; void setBindParameter(char* alias, GLenum unit) override; void bind(ShaderProgram* shader) override; int getWidth() { return m_width; } int getHeight() { return m_height; } private: void applyTexParameter(); }; } // namespace glcore
gl_texture.cpp
#include "glcore/gl_inspector.h" #include "glcore/gl_texture.h" namespace glcore { GLTexture::GLTexture(int width, int height): m_width(width), m_height(height) { } GLTexture::GLTexture(void *buffer, int width, int height): GLTexture(width, height) { setTexture(buffer); } GLTexture::~GLTexture() { GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); if (m_textureHandle != 0) { GL_CALL(glDeleteTextures(1, &m_textureHandle)); m_textureHandle = 0; } } /** * buffer 可以通过以下两种方式得到 * 1) bitmap.copyPixelsToBuffer(bytebuffer); * void* buffer = env->GetDirectBufferAddress(bytebuffer); * 2) AndroidBitmap_lockPixels(env, bitmap, &buffer) */ void GLTexture::setTexture(const void *buffer) { GL_CALL(glGenTextures(1, &m_textureHandle)); GL_CALL(glBindTexture(GL_TEXTURE_2D, m_textureHandle)); applyTexParameter(); GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer)); GL_CALL(glGenerateMipmap(GL_TEXTURE_2D)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); //GLInspector::checkGLError("setTexture"); } void GLTexture::setTexParameter(GLint filter, GLint wrap) { m_filter = filter; m_wrap = wrap; m_isDirty = true; } void GLTexture::setBindParameter(char *alias, GLenum unit) { m_alias = alias; m_unit = unit; } void GLTexture::bind(ShaderProgram *shader) { shader->setUniformi(m_alias, m_unit); GL_CALL(glActiveTexture(GL_TEXTURE0 + m_unit)); GL_CALL(glBindTexture(GL_TEXTURE_2D, m_textureHandle)); if (m_isDirty) { applyTexParameter(); } } void GLTexture::applyTexParameter() { GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_wrap)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_wrap)); m_isDirty = false; } } // namespace glcore
2.13 FBO
FBO 是 Frame Buffer Object 的简称,即帧缓冲对象,主要用于离屏渲染、特效叠加,
frame_buffer_object.h
#pragma once #include "core_lib.h" #include "format.h" #include "shader_program.h" #include "texture_action.h" namespace glcore { /** * 帧缓冲对象 (简称FBO, 用于离屏渲染) * @author little fat sheep */ class FrameBufferObject: public TextureAction { private: Format* m_format; // 颜色格式 int m_width; // 缓冲区宽度 int m_height; // 缓冲区高度 bool m_hasDepth; // 是否有深度缓冲区 bool m_hasStencil; // 是否有模板缓冲区 GLuint m_frameBufferHandle; // 帧缓冲区句柄 GLuint m_depthBufferHandle; // 深度缓冲区句柄 GLuint m_stencilBufferHandle; // 模板缓冲区句柄 GLuint m_colorTextureHandle; // 颜色缓冲区句柄 GLint m_preFramebufferHandle; // 前一个帧缓冲区句柄 int m_preFramebufferViewPort[4]; // 前一个帧缓冲区视口 GLint m_filter = GL_LINEAR; // 滤波方式 GLint m_wrap = GL_CLAMP_TO_EDGE; // 环绕方式 const char* m_alias = ShaderProgram::UNIFORM_TEXTURE; // 纹理别名(着色器中变量名) GLenum m_unit = 0; // 纹理单元 (可能有多个纹理) bool m_isDirty = true; // 是否有脏数据 (纹理参数需要更新) public: FrameBufferObject(Format* format, int width, int height, bool hasDepth, bool hasStencil); ~FrameBufferObject() override; void setTexParameter(GLint filter, GLint wrap) override; void setBindParameter(char* alias, GLenum unit) override; void begin(); void end(); void bind(ShaderProgram* shader) override; private: void applyTexParameter(); }; } // namespace glcore
frame_buffer_object.cpp
#include "glcore/frame_buffer_object.h" #include "glcore/gl_inspector.h" namespace glcore { FrameBufferObject::FrameBufferObject(Format* format, int width, int height, bool hasDepth, bool hasStencil) { m_format = format; m_width = width; m_height = height; m_hasDepth = hasDepth; m_hasStencil = hasStencil; GL_CALL(glGenFramebuffers(1, &m_frameBufferHandle)); begin(); if (m_hasDepth) { GL_CALL(glGenRenderbuffers(1, &m_depthBufferHandle)); GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_depthBufferHandle)); GL_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height)); GL_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferHandle)); GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); } if (m_hasStencil) { GL_CALL(glGenRenderbuffers(1, &m_stencilBufferHandle)); GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_stencilBufferHandle)); GL_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height)); GL_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_stencilBufferHandle)); GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); } GL_CALL(glGenTextures(1, &m_colorTextureHandle)); GL_CALL(glBindTexture(GL_TEXTURE_2D, m_colorTextureHandle)); GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, m_format->getFormat(), m_width, m_height, 0, m_format->getFormat(), m_format->getType(), nullptr)); applyTexParameter(); GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorTextureHandle, 0)); end(); } FrameBufferObject::~FrameBufferObject() { GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); GL_CALL(glDeleteTextures(1, &m_colorTextureHandle)); if (m_hasDepth) { GL_CALL(glDeleteRenderbuffers(1, &m_depthBufferHandle)); } if (m_hasStencil) { GL_CALL(glDeleteRenderbuffers(1, &m_stencilBufferHandle)); } GL_CALL(glDeleteFramebuffers(1, &m_frameBufferHandle)); } void FrameBufferObject::setTexParameter(GLint filter, GLint wrap) { m_filter = filter; m_wrap = wrap; m_isDirty = true; } void FrameBufferObject::setBindParameter(char* alias, GLenum unit) { m_alias = alias; m_unit = unit; } void FrameBufferObject::begin() { GL_CALL(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_preFramebufferHandle)); GL_CALL(glGetIntegerv(GL_VIEWPORT, m_preFramebufferViewPort)); GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferHandle)); GL_CALL(glViewport(0, 0, m_width, m_height)); } void FrameBufferObject::end() { GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_preFramebufferHandle)); GL_CALL(glViewport(m_preFramebufferViewPort[0], m_preFramebufferViewPort[1], m_preFramebufferViewPort[2], m_preFramebufferViewPort[3])); } void FrameBufferObject::bind(ShaderProgram* shader) { shader->setUniformi(m_alias, m_unit); GL_CALL(glActiveTexture(GL_TEXTURE0 + m_unit)); GL_CALL(glBindTexture(GL_TEXTURE_2D, m_colorTextureHandle)); if (m_isDirty) { applyTexParameter(); } } void FrameBufferObject::applyTexParameter() { GL_CALL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter)); GL_CALL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter)); GL_CALL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_wrap)); GL_CALL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_wrap)); m_isDirty = false; } } // namespace glcore
format.h
#pragma once #include "core_lib.h" namespace glcore { /** * 纹理格式 * @author little fat sheep */ class Format { private: GLint format; GLenum type; public: Format(GLint format, GLenum type); GLint getFormat() { return format; } GLenum getType() { return type; } static Format* Alpha(); static Format* LuminanceAlpha(); static Format* RGB565(); static Format* RGBA4444(); static Format* RGB888(); static Format* RGBA8888(); }; } // namespace glcore
format.cpp
#include "glcore/format.h" namespace glcore { Format::Format(GLint format, GLenum type): format(format), type(type) { } Format *Format::Alpha() { return new Format(GL_ALPHA, GL_UNSIGNED_BYTE); } Format *Format::LuminanceAlpha() { return new Format(GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE); } Format *Format::RGB565() { return new Format(GL_RGB, GL_UNSIGNED_SHORT_5_6_5); } Format *Format::RGBA4444() { return new Format(GL_RGB, GL_UNSIGNED_SHORT_4_4_4_4); } Format *Format::RGB888() { return new Format(GL_RGB, GL_UNSIGNED_BYTE); } Format *Format::RGBA8888() { return new Format(GL_RGBA, GL_UNSIGNED_BYTE); } } // namespace glcore
3 JNI 相关
本节主要介绍 glcore 框架在初始化过程中所依附的工具类,如 View 载体、字符串加载工具、图片加载工具等,它们与 JNI 密切相关,不便于进行跨平台迁移,因此不能将它们归入 glcore 框架中。
如果读者对 JNI 不太熟悉,推荐阅读 → JNI环境搭建、JNI基础语法。
3.1 EGLSurfaceView
Android 中渲染内容需要 View 容器承载,有以下常用方案,详见 → 【OpenGL ES】不用GLSurfaceView,如何渲染图像。
- SurfaceView + SurfaceHolder.Callback
- TextureView + TextureView.SurfaceTextureListener
本框架采用 TextureView + TextureView.SurfaceTextureListener 方案,因为它在退后台后不会销毁 Surface,避免反复销毁和创建 Surface,稳定性更好。
Java 和 Native 中都有 EGLSurfaceView,它们是相互绑定的,前者为后者提供了 Surface、宽高、Renderer、Context 等属性,并管理了其生命周期。
EGLSurfaceView.java
package com.zhyan8.egldemo; import android.content.Context; import android.graphics.SurfaceTexture; import android.util.Log; import android.view.Choreographer; import android.view.Surface; import android.view.TextureView; import androidx.annotation.NonNull; /** * @author little fat sheep * 承载EGL环境的View, 类比GLSurfaceView */ public class EGLSurfaceView extends TextureView implements TextureView.SurfaceTextureListener { private static final String TAG = "EGLSurfaceView"; private long mNativeHandle; protected Surface mSurface; private Choreographer mChoreographer = Choreographer.getInstance(); static { System.loadLibrary("egl-native"); } public EGLSurfaceView(Context context) { super(context); setSurfaceTextureListener(this); mNativeHandle = nativeCreate(); } public void setRenderer(long handle) { Log.i(TAG, "setRenderer"); nativeSetRenderer(mNativeHandle, handle); } public void startRender() { Log.i(TAG, "startRender"); mChoreographer.removeFrameCallback(mFrameCallback); mChoreographer.postFrameCallback(mFrameCallback); } public void stopRender() { Log.i(TAG, "stopRender"); mChoreographer.removeFrameCallback(mFrameCallback); } public void requestRender() { mFrameCallback.doFrame(System.nanoTime()); } @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { Log.i(TAG, "onSurfaceTextureAvailable"); mSurface = new Surface(surface); nativeSurfaceCreated(mNativeHandle, mSurface); nativeSurfaceChanged(mNativeHandle, width, height); } @Override public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { Log.i(TAG, "onSurfaceTextureSizeChanged, width=" + width + ", height=" + height); nativeSurfaceChanged(mNativeHandle, width, height); } @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { Log.i(TAG, "onSurfaceTextureDestroyed"); nativeSurfaceDestroyed(mNativeHandle); return false; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Log.i(TAG, "onDetachedFromWindow"); stopRender(); setSurfaceTextureListener(null); mSurface.release(); nativeDestroy(mNativeHandle); mNativeHandle = 0; } private Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { mChoreographer.postFrameCallback(this); nativeDrawFrame(mNativeHandle); } }; private native long nativeCreate(); private native void nativeSetRenderer(long viewHandle, long rendererHandle); private native void nativeSurfaceCreated(long handle, Object surface); private native void nativeSurfaceChanged(long handle, int width, int height); private native void nativeDrawFrame(long handle); private native void nativeSurfaceDestroyed(long handle); private native void nativeDestroy(long handle); }
jin_egl_surface_view.cpp
#include <android/log.h> #include <android/native_window.h> #include <android/native_window_jni.h> #include <jni.h> #include "glcore/core.h" #define LOG_TAG "JNIBrige_EGLSurfaceView" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) using namespace glcore; static jlong nativeCreate(JNIEnv *env, jobject thiz) { LOGI("nativeCreate"); EGLSurfaceView* view = new EGLSurfaceView(); return reinterpret_cast<jlong>(view); } static void nativeSetRenderer(JNIEnv *env, jobject thiz, jlong viewHandle, jlong rendererHandle) { LOGI("nativeSetRenderer"); EGLSurfaceView* view = reinterpret_cast<EGLSurfaceView*>(viewHandle); EGLSurfaceView::Renderer* renderer = reinterpret_cast<EGLSurfaceView::Renderer*>(rendererHandle); view->setRenderer(renderer); } static void nativeSurfaceCreated(JNIEnv* env, jobject thiz, jlong handle, jobject surface) { LOGI("nativeSurfaceCreated"); EGLSurfaceView* view = reinterpret_cast<EGLSurfaceView*>(handle); ANativeWindow* window = ANativeWindow_fromSurface(env, surface); app->setWindow(window); view->surfaceCreated(); } static void nativeSurfaceChanged(JNIEnv* env, jobject thiz, jlong handle, jint width, jint height) { LOGI("nativeSurfaceChanged"); EGLSurfaceView* view = reinterpret_cast<EGLSurfaceView*>(handle); view->surfaceChanged(width, height); } static void nativeDrawFrame(JNIEnv* env, jobject thiz, jlong handle) { EGLSurfaceView* view = reinterpret_cast<EGLSurfaceView*>(handle); view->drawFrame(); } static void nativeSurfaceDestroyed(JNIEnv* env, jobject thiz, jlong handle) { LOGI("nativeSurfaceDestroyed"); EGLSurfaceView* view = reinterpret_cast<EGLSurfaceView*>(handle); view->surfaceDestroy(); } static void nativeDestroy(JNIEnv* env, jobject thiz, jlong handle) { LOGI("nativeDestroy"); EGLSurfaceView* view = reinterpret_cast<EGLSurfaceView*>(handle); delete view; } static JNINativeMethod methods[] = { { "nativeCreate", "()J", (void*) nativeCreate }, { "nativeSetRenderer", "(JJ)V", (void*) nativeSetRenderer }, { "nativeSurfaceCreated", "(JLjava/lang/Object;)V", (void*) nativeSurfaceCreated }, { "nativeSurfaceChanged", "(JII)V", (void*) nativeSurfaceChanged }, { "nativeDrawFrame", "(J)V", (void*) nativeDrawFrame }, { "nativeSurfaceDestroyed", "(J)V", (void*) nativeSurfaceDestroyed }, { "nativeDestroy", "(J)V", (void*) nativeDestroy }, }; static int registerNativeMethods(JNIEnv* env) { int result = -1; jclass clazz = env->FindClass("com/zhyan8/egldemo/EGLSurfaceView"); if (clazz != NULL) { jint len = sizeof(methods) / sizeof(methods[0]); if (env->RegisterNatives(clazz, methods, len) == JNI_OK) { result = 0; } } return result; } jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) == JNI_OK) { if (NULL != env && registerNativeMethods(env) == 0) { result = JNI_VERSION_1_6; } } return result; }
3.2 StringUtils
StringUtils 用于加载顶点和片元着色器资源为字符串。
StringUtils.java
package com.zhyan8.egldemo; import android.content.Context; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; /** * 字符串工具类 * @author little fat sheep */ public class StringUtils { private static final String TAG = "BitmapUtils"; /** * 根据资源路径读取字符串 * @param assetPath 资源路径, 如: "jelly_vert.glsl" */ public static String loadStringFromAsset(Context context, String assetPath) { String str = ""; try (InputStream inputStream = context.getAssets().open(assetPath)) { str = loadString(inputStream); } catch (IOException e) { Log.w(TAG, "loadString error, message=" + e.getMessage()); } return str; } /** * 根据资源id读取字符串 * @param rawId 资源id, 如: "R.raw.vertex_shader" */ public static String loadStringFromRaw(Context context, String rawId) { if (rawId.startsWith("R.raw.")) { rawId = rawId.substring(6); // Remove "R.raw." } int id = context.getResources().getIdentifier(rawId, "raw", context.getPackageName()); if (id == 0) { Log.e(TAG, "loadBitmapFromRaw, resource is not found, rawId=" + rawId); return null; } return loadStringFromRaw(context, id); } /** * 根据资源id读取字符串 * @param rawId 资源id, 如: R.raw.vertex_shader */ public static String loadStringFromRaw(Context context, int rawId) { String str = ""; try (InputStream inputStream = context.getResources().openRawResource(rawId)) { str = loadString(inputStream); } catch (IOException e) { Log.w(TAG, "loadString error, message=" + e.getMessage()); } return str; } private static String loadString(InputStream inputStream) { StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { String line; while ((line = br.readLine()) != null) { sb.append(line).append("n"); } } catch (IOException e) { Log.w(TAG, "loadString error, message=" + e.getMessage()); } return sb.toString(); } }
string_utils.h
#pragma once /** * String工具类 * @author little fat sheep */ class StringUtils { public: /** * 根据资源路径读取字符串 * @param asset 资源路径, 如: "vertex_shader.glsl" */ static const char* loadStringFromAsset(const char* asset); /** * 根据资源id读取字符串 * @param rawId 资源id, 如: "R.raw.vertex_shader" */ static const char* loadStringFromRaw(const char* rawId); };
string_utils.cpp
#include <jni.h> #include "glcore/core.h" #include "jni/jni_refs.h" #include "jni/string_utils.h" using namespace glcore; const char* StringUtils::loadStringFromAsset(const char* asset) { JNIEnv* env = app->jniEnv; jobject context = app->context; jstring assetStr = env->NewStringUTF(asset); jclass clazz = env->FindClass("com/zhyan8/egldemo/StringUtils"); jmethodID method = LoadStringFromAssetMethodId(env); jstring jstr = (jstring) env->CallStaticObjectMethod(clazz, method, context, assetStr); const char* str = env->GetStringUTFChars(jstr, nullptr); return str; } const char* StringUtils::loadStringFromRaw(const char* rawId) { JNIEnv* env = app->jniEnv; jobject context = app->context; jstring rawIdStr = env->NewStringUTF(rawId); jclass clazz = env->FindClass("com/zhyan8/egldemo/StringUtils"); jmethodID method = LoadStringFromRawMethodId(env); jstring jstr = (jstring) env->CallStaticObjectMethod(clazz, method, context, rawIdStr); const char* str = env->GetStringUTFChars(jstr, nullptr); return str; }
3.3 BitmapUtils
BitmapUtils 用于加载图片资源为位图。
BitmapUtils.java
package com.zhyan8.egldemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; import java.io.IOException; import java.io.InputStream; /** * Bitmap工具类 * @author little fat sheep */ public class BitmapUtils { private static final String TAG = "BitmapUtils"; /** * 根据资源路径读取bitmap * @param assetPath 资源路径, 如: "textures/xxx.jpg" */ public static Bitmap loadBitmapFromAsset(Context context, String assetPath) { Bitmap bitmap = null; try (InputStream inputStream = context.getAssets().open(assetPath)) { BitmapFactory.Options options = getOptions(); bitmap = BitmapFactory.decodeStream(inputStream, null, options); } catch (IOException e) { Log.e(TAG, "loadBitmapFromAsset error, message=" + e.getMessage()); } return bitmap; } /** * 根据资源id读取bitmap * @param rawId 资源id, 如: "R.raw.xxx" */ public static Bitmap loadBitmapFromRaw(Context context, String rawId) { if (rawId.startsWith("R.raw.")) { rawId = rawId.substring(6); // Remove "R.raw." } int id = context.getResources().getIdentifier(rawId, "raw", context.getPackageName()); if (id == 0) { Log.e(TAG, "loadBitmapFromRaw, resource is not found, rawId=" + rawId); return null; } return loadBitmapFromRaw(context, id); } /** * 根据资源id读取bitmap * @param rawId 资源id, 如: R.raw.xxx */ public static Bitmap loadBitmapFromRaw(Context context, int rawId) { Bitmap bitmap = null; try (InputStream inputStream = context.getResources().openRawResource(rawId)) { BitmapFactory.Options options = getOptions(); bitmap = BitmapFactory.decodeStream(inputStream, null, options); } catch (IOException e) { Log.e(TAG, "loadBitmapFromRaw error, message=" + e.getMessage()); } return bitmap; } private static BitmapFactory.Options getOptions() { BitmapFactory.Options options = new BitmapFactory.Options(); options.inScaled = false; return options; } }
bitmap_utils.h
#pragma once #include <jni.h> struct BitmapData { void* buffer; int width; int height; }; /** * Bitmap工具类 * @author little fat sheep */ class BitmapUtils { public: /** * 根据资源路径读取bitmap * @param asset 资源路径, 如: "textures/xxx.jpg" */ static BitmapData* loadBitmapDataFromAsset(const char* asset); /** * 根据资源id读取bitmap * @param rawId 资源id, 如: "R.raw.xxx" */ static BitmapData* loadBitmapDataFromRaw(const char* rawId); private: static jobject loadBitmapFromAsset(JNIEnv* env, jobject context, const char* asset); static jobject loadBitmapFromRaw(JNIEnv* env, jobject context, const char* rawId); static BitmapData* getBitmapData(JNIEnv* env, jobject bitmap); };
bitmap_utils.cpp
#include <android/bitmap.h> #include <android/log.h> #include <string> #include "glcore/core.h" #include "jni/bitmap_utils.h" #include "jni/jni_refs.h" #define LOG_TAG "Native: BitmapUtils" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) using namespace glcore; BitmapData* BitmapUtils::loadBitmapDataFromAsset(const char* asset) { JNIEnv* env = app->jniEnv; jobject context = app->context; jobject bitmap = loadBitmapFromAsset(env, context, asset); if (!bitmap) { LOGI("loadBitmapDataFromAsset, bitmap is null: %s", asset); return nullptr; } return getBitmapData(env, bitmap); } BitmapData* BitmapUtils::loadBitmapDataFromRaw(const char* rawId) { JNIEnv* env = app->jniEnv; jobject context = app->context; jobject bitmap = loadBitmapFromRaw(env, context, rawId); if (!bitmap) { LOGI("loadBitmapDataFromRaw, bitmap is null: %s", rawId); return nullptr; } return getBitmapData(env, bitmap); } jobject BitmapUtils::loadBitmapFromAsset(JNIEnv* env, jobject context, const char* asset) { jstring assetStr = env->NewStringUTF(asset); jclass clazz = env->FindClass("com/zhyan8/egldemo/BitmapUtils"); jmethodID method = LoadBitmapFromAssetMethodId(env); jobject bitmap = env->CallStaticObjectMethod(clazz, method, context, assetStr); return bitmap; } jobject BitmapUtils::loadBitmapFromRaw(JNIEnv* env, jobject context, const char* rawId) { jstring rawIdStr = env->NewStringUTF(rawId); jclass clazz = env->FindClass("com/zhyan8/egldemo/BitmapUtils"); jmethodID method = LoadBitmapFromRawMethodId(env); jobject bitmap = env->CallStaticObjectMethod(clazz, method, context, rawIdStr); return bitmap; } BitmapData* BitmapUtils::getBitmapData(JNIEnv* env, jobject bitmap) { AndroidBitmapInfo info; if (AndroidBitmap_getInfo(env, bitmap, &info)) { LOGI("getBitmapData, failed to get bitmap info"); return nullptr; } void* buffer; if (AndroidBitmap_lockPixels(env, bitmap, &buffer)) { LOGI("getBitmapData, failed to lock bitmap pixels"); return nullptr; } BitmapData* data = new BitmapData(); data->buffer = buffer; data->width = info.width; data->height = info.height; return data; }
3.4 jin_ref
jni_ref 提供了 StringUtils 和 BitmaUtils 的类路径、函数名、函数签名等信息。
jni_ref.h
#pragma once #include <jni.h> jmethodID LoadBitmapFromAssetMethodId(JNIEnv* env); jmethodID LoadBitmapFromRawMethodId(JNIEnv* env); jmethodID LoadStringFromAssetMethodId(JNIEnv* env); jmethodID LoadStringFromRawMethodId(JNIEnv* env); jmethodID GetMethodId(JNIEnv* env, const char* method[]); jmethodID GetStaticMethodId(JNIEnv* env, const char* method[]);
jni_ref.c
#include "jni/jni_refs.h" const char* loadBitmapFromAssetTab[] = { "com/zhyan8/egldemo/BitmapUtils", "loadBitmapFromAsset", "(Landroid/content/Context;Ljava/lang/String;)Landroid/graphics/Bitmap;" }; const char* loadBitmapFromRawTab[] = { "com/zhyan8/egldemo/BitmapUtils", "loadBitmapFromRaw", "(Landroid/content/Context;Ljava/lang/String;)Landroid/graphics/Bitmap;" }; const char* loadStringFromAssetTab[] = { "com/zhyan8/egldemo/StringUtils", "loadStringFromAsset", "(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;" }; const char* loadStringFromRawTab[] = { "com/zhyan8/egldemo/StringUtils", "loadStringFromRaw", "(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;" }; jmethodID LoadBitmapFromAssetMethodId(JNIEnv* env) { return GetStaticMethodId(env, loadBitmapFromAssetTab); } jmethodID LoadBitmapFromRawMethodId(JNIEnv* env) { return GetStaticMethodId(env, loadBitmapFromRawTab); } jmethodID LoadStringFromAssetMethodId(JNIEnv* env) { return GetStaticMethodId(env, loadStringFromAssetTab); } jmethodID LoadStringFromRawMethodId(JNIEnv* env) { return GetStaticMethodId(env, loadStringFromRawTab); } jmethodID GetMethodId(JNIEnv* env, const char* method[]) { jclass clazz = env->FindClass(method[0]); return env->GetMethodID(clazz, method[1], method[2]); } jmethodID GetStaticMethodId(JNIEnv* env, const char* method[]) { jclass clazz = env->FindClass(method[0]); return env->GetStaticMethodID(clazz, method[1], method[2]); }
4 应用
本节将基于 glcore 框架写一个色散特效叠加果冻特效的 Demo,体验一下 glcore 的便捷之处。
4.1 MyRenderer
my_renderer.h
#pragma once #include "glcore/core.h" #include "dispersion_effect.h" #include "jelly_effect.h" using namespace glcore; /** * 自定义渲染器 * @author little fat sheep */ class MyRenderer : public EGLSurfaceView::Renderer { private: DispersionEffect* m_dispersionEffect; JellyEffect* m_jellyEffect; long m_startTime = 0; float m_runTime = 0.0f; public: MyRenderer(); ~MyRenderer() override; void onSurfaceCreated() override; void onSurfaceChanged(int width, int height) override; void onDrawFrame() override; private: long getTimestamp(); };
my_renderer.cpp
#include <android/log.h> #include <chrono> #include "custom/my_renderer.h" #define LOG_TAG "Native: MyRenderer" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) using namespace glcore; using namespace std::chrono; MyRenderer::MyRenderer() { LOGI("init"); m_dispersionEffect = new DispersionEffect(); m_jellyEffect = new JellyEffect(); m_jellyEffect->setTexAction(m_dispersionEffect); } MyRenderer::~MyRenderer() { LOGI("destroy"); delete m_dispersionEffect; delete m_jellyEffect; } void MyRenderer::onSurfaceCreated() { LOGI("onSurfaceCreated"); m_dispersionEffect->onCreate(); m_jellyEffect->onCreate(); GL_CALL(glClearColor(0.1f, 0.2f, 0.3f, 0.4f)); m_startTime = getTimestamp(); } void MyRenderer::onSurfaceChanged(int width, int height) { LOGI("onSurfaceChanged, width: %d, height: %d", width, height); GL_CALL(glViewport(0, 0, width, height)); m_dispersionEffect->onResize(width, height); m_jellyEffect->onResize(width, height); } void MyRenderer::onDrawFrame() { m_runTime = (getTimestamp() - m_startTime) / 1000.0f; GL_CALL(glClear(GL_COLOR_BUFFER_BIT)); m_dispersionEffect->onDraw(m_runTime); m_jellyEffect->onDraw(m_runTime); } long MyRenderer::getTimestamp() { auto now = std::chrono::system_clock::now(); // 获取当前时间 auto duration = now.time_since_epoch(); // 转换为自纪元以来的时间 return duration_cast<milliseconds>(duration).count(); }
4.2 DispersionEffect
DispersionEffect 是色散特效。
dispersion_effect.h
#pragma once #include "glcore/core.h" using namespace glcore; /** * 色散特效 * @author little fat sheep */ class DispersionEffect: public TextureAction { private: ShaderProgram* m_program; Mesh* m_mesh; GLTexture* m_glTexture; FrameBufferObject* m_fbo; public: DispersionEffect(); ~DispersionEffect() override; void onCreate(); void onResize(int width, int height); void onDraw(float runtime); void bind(ShaderProgram* shader) override; private: void createProgram(); void createTexture(); };
dispersion_effect.cpp
#include <android/log.h> #include "custom/dispersion_effect.h" #include "jni/bitmap_utils.h" #include "jni/string_utils.h" #define LOG_TAG "Native: DispersionEffect" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) using namespace glcore; DispersionEffect::DispersionEffect() { LOGI("init"); } DispersionEffect::~DispersionEffect() { LOGI("destroy"); delete m_program; delete m_mesh; delete m_glTexture; delete m_fbo; } void DispersionEffect::onCreate() { LOGI("onCreate"); createProgram(); createTexture(); m_mesh = MeshUtils::createRect(true); m_fbo = new FrameBufferObject(Format::RGBA8888(), app->width, app->height, false, false); } void DispersionEffect::onResize(int width, int height) { LOGI("onResize, width: %d, height: %d", width, height); } void DispersionEffect::onDraw(float runtime) { m_fbo->begin(); m_program->bind(); m_program->setUniformf("u_time", runtime); m_program->setUniformf("u_aspect", app->aspect); m_glTexture->bind(m_program); m_mesh->render(m_program); m_fbo->end(); } void DispersionEffect::bind(ShaderProgram* shader) { m_fbo->bind(shader); } void DispersionEffect::createProgram() { LOGI("createProgram"); const char* vertexCode = StringUtils::loadStringFromAsset("dispersion_vert.glsl"); const char* fragmentCode = StringUtils::loadStringFromAsset("dispersion_frag.glsl"); m_program = new ShaderProgram(vertexCode, fragmentCode); } void DispersionEffect::createTexture() { LOGI("createTexture"); BitmapData* data = BitmapUtils::loadBitmapDataFromAsset("girl.jpg"); m_glTexture = new GLTexture(data->buffer, data->width, data->height); }
dispersion_vert.glsl
attribute vec4 a_position; attribute vec2 a_texCoord0; varying vec2 v_texCoord; void main() { gl_Position = a_position; v_texCoord = a_texCoord0; }
dispersion_frag.glsl
precision highp float; uniform float u_aspect; uniform float u_time; uniform sampler2D u_texture; varying vec2 v_texCoord; vec2 getOffset() { // 偏移函数 float time = u_time * 1.5; vec2 dire = vec2(sin(time), cos(time)); float strength = sin(u_time * 2.0) * 0.004; return dire * strength * vec2(1.0, 1.0 / u_aspect); } void main() { vec2 offset = getOffset(); vec4 color = texture2D(u_texture, v_texCoord); color.r = texture2D(u_texture, v_texCoord + offset).r; color.b = texture2D(u_texture, v_texCoord - offset).b; gl_FragColor = color; }
4.3 JellyEffect
JellyEffect 是果冻特效。
jelly_effect.h
#pragma once #include "glcore/core.h" using namespace glcore; /** * 果冻特效 * @author little fat sheep */ class JellyEffect { private: ShaderProgram* m_program; Mesh* m_mesh; TextureAction* m_texAction; public: JellyEffect(); ~JellyEffect(); void setTexAction(TextureAction* texAction); void onCreate(); void onResize(int width, int height); void onDraw(float runtime); private: void createProgram(); };
jelly_effect.cpp
#include <android/log.h> #include "custom/jelly_effect.h" #include "jni/bitmap_utils.h" #include "jni/string_utils.h" #define LOG_TAG "Native: JellyEffect" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) using namespace glcore; JellyEffect::JellyEffect() { LOGI("init"); } JellyEffect::~JellyEffect() { LOGI("destroy"); delete m_program; delete m_mesh; } void JellyEffect::setTexAction(TextureAction* texAction) { m_texAction = texAction; } void JellyEffect::onCreate() { LOGI("onCreate"); createProgram(); m_mesh = MeshUtils::createRect(false); } void JellyEffect::onResize(int width, int height) { LOGI("onResize, width: %d, height: %d", width, height); } void JellyEffect::onDraw(float runtime) { m_program->bind(); m_program->setUniformf("u_time", runtime); m_program->setUniformf("u_aspect", app->aspect); m_texAction->bind(m_program); m_mesh->render(m_program); } void JellyEffect::createProgram() { LOGI("createProgram"); const char* vertexCode = StringUtils::loadStringFromAsset("jelly_vert.glsl"); const char* fragmentCode = StringUtils::loadStringFromAsset("jelly_frag.glsl"); m_program = new ShaderProgram(vertexCode, fragmentCode); }
jelly_vert.glsl
attribute vec4 a_position; attribute vec2 a_texCoord0; varying vec2 v_texCoord; void main() { gl_Position = a_position; v_texCoord = a_texCoord0; }
jelly_frag.glsl
precision highp float; uniform float u_aspect; uniform float u_time; uniform sampler2D u_texture; varying vec2 v_texCoord; vec2 fun(vec2 center, vec2 uv) { // 畸变函数 vec2 dire = normalize(uv - center); float dist = distance(uv, center); vec2 uv1 = uv + sin(dist * 2.2 + u_time * 3.5) * 0.025; return uv1; } void main() { vec2 uv = vec2(v_texCoord.x, v_texCoord.y / u_aspect); vec2 center = vec2(0.5, 0.5 / u_aspect); vec2 uv1 = fun(center, uv); uv1.y *= u_aspect; gl_FragColor = texture2D(u_texture, uv1); }
4.4 运行效果
运行效果如下,可以看到叠加了色散和果冻特效。

声明:本文转自【OpenGL ES】在Android上手撕一个mini版的渲染框架。