news 2026/4/15 17:08:01

HarmonyOS 6 自定义人脸识别模型3:OH_NativeXComponent基于OpenGL绘制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS 6 自定义人脸识别模型3:OH_NativeXComponent基于OpenGL绘制

前面文章《HarmonyOS 6 自定义人脸识别模型2:OH_NativeXComponent方式绘制》介绍了如何将ArkTS层的XComponent与C++层的OH_NativeXComponent进行关联与映射,文本接着介绍如何在C++中通过OpenGL在OH_NativeXComponent中进行绘制等操作。

OpenGL介绍

OpenGL (Open Graphics Library) 是一个跨编程语言、跨平台的编程图形接口,用于渲染2D、3D矢量图形。在移动设备开发中,我们通常使用的是OpenGL ES (OpenGL for Embedded Systems),它是 OpenGL 的子集,去除了冗余功能,专门为嵌入式系统设计。

在 HarmonyOS 中,我们使用 OpenGL ES 来进行高性能的图形渲染。而要让 OpenGL ES 工作,还需要EGL (Embedded-System Graphics Library)。EGL 是 Khronos 渲染 API(如 OpenGL ES)与底层原生窗口系统之间的接口。它负责:

  • 管理图形渲染管线。
  • 创建渲染表面(Surface)。
  • 管理渲染上下文(Context)。
    简单来说,EGL 是 OpenGL ES 与屏幕(Window)之间的“胶水”
HarmonyOS 中OpenGL操作流程

在 HarmonyOS NDK 开发中,使用 OpenGL 进行绘制通常遵循以下标准流程:

  1. 获取原生窗口句柄:通过OH_NativeXComponent获取底层的NativeWindow
  2. 创建 EGL Display:建立与本地窗口系统的连接。
  3. 初始化 EGL:设置 EGL 的版本信息。
  4. 选择 EGL Config:配置渲染参数(如颜色位宽、采样率等)。
  5. 创建 EGL Surface:将NativeWindow绑定到 EGL。
  6. 创建 EGL Context:创建渲染状态机。
  7. 绑定上下文(Make Current):将当前线程与 EGL 上下文绑定。
  8. 执行 OpenGL 渲染指令:使用渲染程序(Shader Program)进行绘图。
  9. 交换缓冲区(Swap Buffers):将渲染内容显示到屏幕上。
OpenGL基于OH_NativeXComponent绘制

接下来详细介绍如何按照上面步骤实现具体的绘制流程,这里我们把主要逻辑封装在EGLCorePluginRender类中。

1. 初始化 EGL 环境

OnSurfaceCreatedCB回调中,我们获取到NativeWindow并触发EglContextInit

// egl_core.cppboolEGLCore::EglContextInit(void*window,intwidth,intheight){UpdateSize(width,height);eglWindow_=reinterpret_cast<EGLNativeWindowType>(window);// 1. 初始化 displayeglDisplay_=eglGetDisplay(EGL_DEFAULT_DISPLAY);// 2. 初始化 EGLEGLint majorVersion;EGLint minorVersion;if(!eglInitialize(eglDisplay_,&majorVersion,&minorVersion)){returnfalse;}// 3. 选择配置constEGLint maxConfigSize=1;EGLint numConfigs;if(!eglChooseConfig(eglDisplay_,ATTRIB_LIST,&eglConfig_,maxConfigSize,&numConfigs)){returnfalse;}// 4. 创建环境(Surface 和 Context)returnCreateEnvironment();}boolEGLCore::CreateEnvironment(){// 创建 SurfaceeglSurface_=eglCreateWindowSurface(eglDisplay_,eglConfig_,eglWindow_,NULL);// 创建 ContexteglContext_=eglCreateContext(eglDisplay_,eglConfig_,EGL_NO_CONTEXT,CONTEXT_ATTRIBS);// 绑定当前线程if(!eglMakeCurrent(eglDisplay_,eglSurface_,eglSurface_,eglContext_)){returnfalse;}// 创建着色器程序 (Program)program_=CreateProgram(VERTEX_SHADER,FRAGMENT_SHADER);returntrue;}
2. 执行渲染逻辑

渲染时,我们需要通过glUseProgram激活程序,并向顶点着色器传递顶点坐标和颜色数据。

// egl_core.cppvoidEGLCore::Draw(int&hasDraw){GLint position=PrepareDraw();// 调用 glUseProgram, glViewport 等// 绘制背景颜色ExecuteDraw(position,BACKGROUND_COLOR,BACKGROUND_RECTANGLE_VERTICES,sizeof(BACKGROUND_RECTANGLE_VERTICES));// 计算五角星顶点并绘制// ... (具体顶点计算逻辑详见源代码)ExecuteDrawStar(position,DRAW_COLOR,shapeVertices,sizeof(shapeVertices));// 结束绘制并刷新缓冲区FinishDraw();hasDraw=1;}boolEGLCore::FinishDraw(){glFlush();glFinish();// 将绘制内容“展示”到屏幕上returneglSwapBuffers(eglDisplay_,eglSurface_);}
3. 关联 NativeXComponent

上面EGL相关流程和系统系统很类似,具体到Window中的绑定这里重点介绍下,在获取到NativeXcomponent后给NativeXComponent注册各种回调,包括渲染回调:

void PluginRender::RegisterCallback(OH_NativeXComponent *nativeXComponent) { memset(&renderCallback_, 0, sizeof(OH_NativeXComponent_Callback)); renderCallback_.OnSurfaceCreated = OnSurfaceCreatedCB; renderCallback_.OnSurfaceChanged = OnSurfaceChangedCB; renderCallback_.OnSurfaceDestroyed = OnSurfaceDestroyedCB; renderCallback_.DispatchTouchEvent = DispatchTouchEventCB; OH_NativeXComponent_RegisterCallback(nativeXComponent, &renderCallback_); }

其中OnSurfaceCreatedCB回调中将这些 OpenGL 操作与OH_NativeXComponent的生命周期结合起来。当 Surface 创建、大小改变或通过 NAPI 被触发时,调用EGLCore相应的方法。OnSurfaceCreatedCB回调中包括了一个window参数,window 通过eglWindow_ = reinterpret_cast<EGLNativeWindowType>(window);转换为EGLNativeWindowType可以用来初始化EGL上下文:

// plugin_render.cppvoidOnSurfaceCreatedCB(OH_NativeXComponent*component,void*window){// ... 获取 id 和 size ...if(render->eglCore_->EglContextInit(window,width,height)){render->eglCore_->Background();// 初始背景色渲染}}// 供 ArkTS 层调用的绘制方法napi_valuePluginRender::NapiDrawPattern(napi_env env,napi_callback_info info){// ... 获取 PluginRender 实例 ...render->eglCore_->Draw(hasDraw_);returnnullptr;}

OnSurfaceChangedCB回调触发画布大小更新,用来更新EGL大小:

void PluginRender::OnSurfaceChanged(OH_NativeXComponent *component, void *window) { char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceChanged: Unable to get XComponent id"); return; } std::string id(idStr); PluginRender *render = PluginRender::GetInstance(id); uint64_t width; uint64_t height; OH_NativeXComponent_GetXComponentSize(component, window, &width, &height); if (render != nullptr) { render->eglCore_->UpdateSize(width, height); } }

此外还有OnSurfaceDestroyed画布销毁以及DispatchTouchEvent事件触发等回调。

OpenGL绘制五角星

本文以官方五角星绘制为例,五角星的绘制采用了一种更具技巧性的几何分解方法,而不是直接定义十个顶点:

(1)几何分解

我们将五角星分解为5 个完全相同的四边形(筝形)

每个四边形由以下四个关键点组成:

  • 中心点 (Center):五角星的几何中心。
  • 外顶点 (Tip):五角星的五个尖角之一。
  • 两个内顶点 (Shoulders):连接外顶点与中心点的凹角处。

这种分解方式的优势在于,我们只需要关注其中一个“角”的几何构造,其余部分完全可以通过数学变换(旋转)得到。

(2)数学旋转与坐标变换

为了简化计算,我们首先计算出其中一个四边形的 4 个顶点坐标。然后利用旋转矩阵,将其绕中心点旋转 72 度(即2π/52\pi/52π/5弧度),重复 4 次,即可得到完整的五角星。

相关的二维旋转逻辑封装在Rotate2d函数中:

voidEGLCore::Rotate2d(GLfloat centerX,GLfloat centerY,GLfloat*rotateX,GLfloat*rotateY,GLfloat theta){GLfloat tempX=cos(theta)*(*rotateX-centerX)-sin(theta)*(*rotateY-centerY);GLfloat tempY=sin(theta)*(*rotateX-centerX)+cos(theta)*(*rotateY-centerY);*rotateX=tempX+centerX;*rotateY=tempY+centerY;}
(3)OpenGL 渲染基础与函数深度解析

理解项目中出现的每一个 OpenGL 函数及其参数,是掌握高性能图形渲染的核心:

  • glViewport(x, y, width, height):
    • 作用: 设置视口(Viewport),即 OpenGL 最终将渲染内容映射到屏幕上的矩形区域。
    • 参数:(x, y)是视口左下角的起始位置,(width, height)是视口的像素大小。它完成了从规范化设备坐标(-1 到 1)到屏幕像素坐标的转换。
  • glClearColor(r, g, b, a)&glClear(mask):
    • 作用: 前者设置用于清除颜色的“底漆”;后者执行实际的清除动作。
    • 参数:glClearmask通常为GL_COLOR_BUFFER_BIT,表示清除颜色缓冲区。
  • glUseProgram(program):
    • 作用: 激活指定的着色器程序对象。
    • 参数:program是通过glCreateProgram链接生成的 ID。设置后,后续的顶点关联和绘制指令都在该程序下进行。
  • glGetAttribLocation(program, name):
    • 作用: 获取顶点着色器中attribute变量(如a_position)的槽位索引(Location)。
  • glVertexAttribPointer(index, size, type, normalized, stride, pointer):
    • 作用: 描述顶点数据的内存布局,将 C++ 数组与着色器变量关联。
    • 参数:
      • index: 变量索引。
      • size: 每个顶点的分量数(如(x, y)取值为 2)。
      • type: 数据类型(如GL_FLOAT)。
      • normalized: 是否对非浮点数据归一化。
      • stride: 步长,相邻顶点间的间隔字节数。
      • pointer: 指向内存中顶点数据的指针。
  • glEnableVertexAttribArray(index):
    • 作用: 启用指定索引的顶点属性。默认情况下所有属性是禁用的,必须手动开启才能在绘制时生效。
  • glVertexAttrib4fv(index, v):
    • 作用: 为指定的属性变量设置一个统一的(Uniform-like)常量值。在本项目中用于设置当前四边形的填充颜色。
  • glDrawArrays(mode, first, count):
    • 作用: 渲染图元的终极指令。
    • 参数:
      • mode: 渲染模式。
      • first: 起始索引。
      • count: 顶点数量。
    • 绘制模式 (mode) 详解:
      • GL_POINTS: 绘制独立的孤立点。
      • GL_LINES: 按对连接顶点,绘制独立线段。
      • GL_LINE_STRIP: 连接所有顶点,绘制一条连续线条。
      • GL_LINE_LOOP: 连成线段并闭合首尾。
      • GL_TRIANGLES: 每三个顶点构成一个独立三角形。
      • GL_TRIANGLE_FAN(本项目使用): 以第一个顶点为公共中心,连接后续所有顶点形成星扇形区域,非常适合绘制五角星这类凸多边形或复杂多边形的子集。
(4)绘制流程详细拆解:从 ExecuteDraw 到 GPU

核心绘制数据流向如下:

  1. 映射数据: 通过glVertexAttribPointer将 C++ 内存中的shapeVertices映射给着色器的position槽位。
  2. 触发绘制: 调用glDrawArrays,GPU 根据当前绑定的程序、顶点数据和绘制模式(扇形填充)进行光栅化渲染。
  3. 循环迭代: 主循环执行 5 次,每次旋转角度并调用ExecuteDraw
// 示例逻辑:五个部分依次绘制,每个部分使用调色盘中对应的颜色GLfloat rad=M_PI/180*72;// 72度for(inti=0;i<5;++i){Rotate2d(centerX,centerY,&rotateX,&rotateY,rad*i);// 旋转数学计算// ... 映射顶点、设置颜色并触发绘制 ...ExecuteDraw(position,DRAW_PALETTE[i],shapeVertices,sizeof(shapeVertices));}

完整绘制代码:

voidEGLCore::Draw(int&hasDraw){flag_=false;OH_LOG_Print(LOG_APP,LOG_INFO,LOG_PRINT_DOMAIN,"EGLCore","Draw");GLint position=PrepareDraw();if(position==POSITION_ERROR){OH_LOG_Print(LOG_APP,LOG_ERROR,LOG_PRINT_DOMAIN,"EGLCore","Draw get position failed");return;}// 绘制背景if(!ExecuteDraw(position,BACKGROUND_COLOR,BACKGROUND_RECTANGLE_VERTICES,sizeof(BACKGROUND_RECTANGLE_VERTICES))){OH_LOG_Print(LOG_APP,LOG_ERROR,LOG_PRINT_DOMAIN,"EGLCore","Draw execute draw background failed");return;}// 将五角星分为五个四边形,计算其中一个四边形的四个顶点GLfloat rotateX=0;GLfloat rotateY=FIFTY_PERCENT*height_;GLfloat centerX=0;// Convert DEG(54° & 18°) to RADGLfloat centerY=-rotateY*(M_PI/180*54)*(M_PI/180*18);// Convert DEG(18°) to RADGLfloat leftX=-rotateY*(M_PI/180*18);GLfloat leftY=0;// Convert DEG(18°) to RADGLfloat rightX=rotateY*(M_PI/180*18);GLfloat rightY=0;// 确定绘制四边形的顶点,使用绘制区域的百分比表示constGLfloat shapeVertices[]={centerX/width_,centerY/height_,leftX/width_,leftY/height_,rotateX/width_,rotateY/height_,rightX/width_,rightY/height_};// 绘制图形 (第一个部分)if(!ExecuteDraw(position,DRAW_PALETTE[0],shapeVertices,sizeof(shapeVertices))){OH_LOG_Print(LOG_APP,LOG_ERROR,LOG_PRINT_DOMAIN,"EGLCore","Draw execute draw shape failed");return;}// Convert DEG(72°) to RADGLfloat rad=M_PI/180*72;// Rotate four timesfor(inti=0;i<NUM_4;++i){// 旋转得其他四个四边形的顶点Rotate2d(centerX,centerY,&rotateX,&rotateY,rad);Rotate2d(centerX,centerY,&leftX,&leftY,rad);Rotate2d(centerX,centerY,&rightX,&rightY,rad);// 确定绘制四边形的顶点,使用绘制区域的百分比表示constGLfloat shapeVertices[]={centerX/width_,centerY/height_,leftX/width_,leftY/height_,rotateX/width_,rotateY/height_,rightX/width_,rightY/height_};// 绘制图形 (后续四个部分)if(!ExecuteDraw(position,DRAW_PALETTE[i+1],shapeVertices,sizeof(shapeVertices))){OH_LOG_Print(LOG_APP,LOG_ERROR,LOG_PRINT_DOMAIN,"EGLCore","Draw execute draw shape failed");return;}}// 结束绘制if(!FinishDraw()){OH_LOG_Print(LOG_APP,LOG_ERROR,LOG_PRINT_DOMAIN,"EGLCore","Draw FinishDraw failed");return;}hasDraw=1;flag_=true;}

绘制效果:

当点击切换颜色时修改颜色数组即可,修改颜色后效果:

多个XComponent验证

前面文章介绍过,当ArkTS层有多个XComponent时,C++中的Init函数会被调用多次,我们在Init函数中增加日志验证:

static napi_value Init(napi_env env, napi_value exports) { PluginManager::GetInstance()->Export(env, exports); return exports; } void PluginManager::Export(napi_env env, napi_value exports) { OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "Export start"); //... }

日志确实被打印了两次:
![[HarmonyOS 6 自定义人脸识别模型3:OH_NativeXComponent基于OpenGL绘制-3.png]]

总结

通过OH_NativeXComponent结合 OpenGL,我们可以完全掌管 UI 组件的像素级渲染。核心步骤在于:

  1. 环境配置:利用 EGL 准备好渲染所需的 Surface 和 Context。
  2. 数据交互:在 C++ 层根据具体业务逻辑(如本文中的星形图案绘制)计算顶点和颜色。
  3. 渲染呈现:通过 OpenGL ES 指令绘制并利用eglSwapBuffers同步到 native 窗口。

掌握了这一套 OpenGL 渲染流程,我们就具备了在 HarmonyOS 上开发高性能游戏、自定义视觉特效乃至复杂的人脸特征点可视化的基础能力。示例代码地址:https://github.com/qingkouwei/NativeXComponent

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/30 22:21:43

一键部署Cosmos-Reason1-7B:本地推理工具快速上手

一键部署Cosmos-Reason1-7B&#xff1a;本地推理工具快速上手 想找一个能帮你解决复杂逻辑题、数学计算或者编程问题的AI助手&#xff0c;但又担心数据隐私和网络依赖&#xff1f;今天介绍的这款工具&#xff0c;或许就是你的理想选择。Cosmos-Reason1-7B推理交互工具&#xf…

作者头像 李华
网站建设 2026/4/8 11:14:17

中文NLP新体验:REX-UniNLU语义分析系统完整使用指南

中文NLP新体验&#xff1a;REX-UniNLU语义分析系统完整使用指南 1. 引言&#xff1a;为什么你需要一个全能的中文语义分析工具&#xff1f; 如果你正在处理中文文本数据&#xff0c;无论是分析用户评论、挖掘新闻信息&#xff0c;还是构建智能客服系统&#xff0c;你可能会遇…

作者头像 李华
网站建设 2026/4/3 6:53:41

零代码体验Qwen3-ASR-1.7B:语音识别网页版演示

零代码体验Qwen3-ASR-1.7B&#xff1a;语音识别网页版演示 你是否曾经想过&#xff0c;不用写一行代码就能体验最先进的语音识别技术&#xff1f;现在&#xff0c;通过Qwen3-ASR-1.7B镜像&#xff0c;你可以在几分钟内搭建一个功能强大的语音识别系统&#xff0c;支持52种语言…

作者头像 李华
网站建设 2026/4/4 2:28:19

Java版本怎么选?JDK各版本特性对比与实战建议

Java 版本怎么选&#xff1f;JDK 各版本特性对比与实战建议&#xff08;2026 年 2 月最新&#xff09; 2026 年初&#xff0c;Java 生态已经非常清晰&#xff1a;LTS 版本才是生产主力&#xff0c;非 LTS 基本只用于尝鲜或实验。 当前 LTS 版本状态&#xff08;2026 年 2 月&…

作者头像 李华
网站建设 2026/4/8 20:20:53

实战分享:用Fish Speech 1.5制作多语言播客节目

实战分享&#xff1a;用Fish Speech 1.5制作多语言播客节目 你是否想过&#xff0c;一个人、一台电脑&#xff0c;就能制作一档覆盖全球听众的多语言播客&#xff1f;过去&#xff0c;这需要聘请不同语种的配音演员&#xff0c;投入高昂的制作成本。现在&#xff0c;借助Fish …

作者头像 李华
网站建设 2026/4/14 4:55:32

Qwen3-TTS语音合成:10种语言自由切换

Qwen3-TTS语音合成&#xff1a;10种语言自由切换 1. 引言 你有没有遇到过这样的场景&#xff1a;刚写完一段中文产品介绍&#xff0c;马上要录制成西班牙语发给海外团队&#xff1b;或者为日本客户准备的培训材料&#xff0c;需要同步生成日语配音&#xff1b;又或者想用德语…

作者头像 李华