当前位置: 首页 > news >正文

【open harmony/harmonyos】ArkTS 实现 3D 透视投影:让普通组件拥有空间感

【open harmony/harmonyos】ArkTS 实现 3D 透视投影:让普通组件拥有空间感

前言 🌌

在 HarmonyOS / OpenHarmony 应用中,如果想做 3D 效果,很多人第一反应可能是使用 3D 引擎。

但如果需求只是“让节点有空间感”,其实不一定要上复杂引擎。对于轻量级知识图谱、星图、关系网络这类场景,可以用 ArkTS 数学计算 + ArkUI 普通组件实现一个简化版 3D 透视效果。

这篇文章会结合我的项目星图 Xingtu,分享如何用 ArkTS 实现 3D 坐标旋转、透视投影、节点缩放、深度排序,让普通 ArkUI 组件呈现出空间层次。✨

一、目标效果

项目中希望实现这样一个星图界面:

  • 节点分布在 3D 空间里
  • 拖动时节点会围绕视角旋转
  • 近处节点更大、更亮
  • 远处节点更小、更淡
  • 近处节点覆盖远处节点
  • 整体看起来像一个可探索的数据空间

重点是:这些节点本质上仍然是 ArkUI 组件,不是复杂 3D 模型。

二、定义 3D 坐标和投影结果

首先定义一个三维坐标类型:

exportinterfaceVec3 {x:number;y:number;z:number; }

节点保存在真实的 3D 坐标中:

exportinterfaceXingtuNode{ id:string; title:string; note:string; tags:string[]; position: Vec3; }

经过投影后,节点会变成屏幕上的二维位置:

exportinterfaceProjectedNode {id:string;title:string;note:string;tags:string[];screenX:number;screenY:number;scale:number;opacity:number;depth:number; }

这里的ProjectedNode是 UI 真正需要的数据:

  • screenX:屏幕横坐标
  • screenY:屏幕纵坐标
  • scale:节点大小比例
  • opacity:节点透明度
  • depth:节点深度

三、相机状态设计 📷

为了让用户可以旋转和缩放星图,需要一个相机状态。

exportinterfaceCameraState { yaw:number; pitch:number; distance:number;scale:number; }

项目中的默认相机如下:

exportfunctiondefaultCamera(): CameraState {return{yaw: -18, pitch: -10, distance:620, scale:1}; }

每个字段的含义:

  • yaw:水平旋转角度
  • pitch:垂直旋转角度
  • distance:相机和节点空间的距离
  • scale:整体缩放比例

有了相机状态,就可以通过改变相机,而不是直接改变所有节点位置,来实现视角变化。

四、坐标旋转计算

3D 投影的第一步,是根据相机角度旋转节点。

constDEGREE = Math.PI /180;exportfunctionrotatePoint(point: Vec3, yaw: number, pitch: number): Vec3 {constyawRad: number = yaw * DEGREE;constpitchRad: number = pitch * DEGREE;constyawX: number = point.x * Math.cos(yawRad) + point.z * Math.sin(yawRad);constyawZ: number = -point.x * Math.sin(yawRad) + point.z * Math.cos(yawRad);constpitchY: number = point.y * Math.cos(pitchRad) - yawZ * Math.sin(pitchRad);constpitchZ: number = point.y * Math.sin(pitchRad) + yawZ * Math.cos(pitchRad);return{ x: yawX, y: pitchY, z: pitchZ }; }

这段逻辑分两步:

  1. 根据yaw做水平旋转
  2. 根据pitch做上下旋转

用户拖动屏幕时,实际更新的是yawpitch,然后所有节点重新计算屏幕位置。

五、透视投影核心算法 🔵

坐标旋转后,需要把 3D 坐标投影到 2D 屏幕。

constCAMERA_FOCAL =560; export functionprojectNode( node: XingtuNode,camera: CameraState, viewport: ViewportSize ): ProjectedNode {constrotated: Vec3 =rotatePoint(node.position,camera.yaw,camera.pitch);constdepth: number =camera.distance- rotated.z;constperspective: number = CAMERA_FOCAL / Math.max(220, depth);consthalfWidth: number = viewport.width/2;consthalfHeight: number = viewport.height/2;return{ id: node.id, title: node.title, note: node.note, tags: node.tags,screenX: halfWidth + rotated.x*perspective*camera.scale,screenY: halfHeight + rotated.y*perspective*camera.scale,scale:perspective*camera.scale, opacity: Math.max(0.28, Math.min(1,0.2+perspective*0.35)), depth }; }

这里最关键的是perspective

constperspective: number = CAMERA_FOCAL / Math.max(220, depth);

depth越小,说明节点越靠近用户,perspective越大,节点显示就越大。

depth越大,说明节点越远,perspective越小,节点显示就越小。

这就是透视感的来源。

六、让远近影响透明度

除了大小,透明度也可以用来强化空间感。

opacity: Math.max(0.28, Math.min(1,0.2+ perspective *0.35))

这段代码限制了透明度范围:

  • 最低不低于0.28
  • 最高不超过1
  • 近处节点更亮
  • 远处节点更淡

如果只改变大小,不改变透明度,空间感会弱一些。大小 + 透明度一起变化,效果会更自然。

七、节点深度排序

在 3D 空间中,近处节点应该盖住远处节点。

项目中在 Store 里对投影节点做了排序:

projectedNodes(viewport: ViewportSize): ProjectedNode[] {returnthis.nodes .map((node: XingtuNode)=>projectNode(node, this.camera, viewport)) .sort((left: ProjectedNode, right: ProjectedNode)=>right.depth - left.depth); }

排序后,远处节点先渲染,近处节点后渲染。

Stack里,后面的组件会覆盖前面的组件,所以这样就能模拟基本的深度遮挡。

八、渲染节点组件

投影完成后,节点组件只关心自己的屏幕位置、大小和透明度。

privatenodeSize(): number {returnMath.max(30, Math.min(108,58*this.node.scale)); }privatenodePosX(): number {returnthis.node.screenX -this.nodeSize() /2; }privatenodePosY(): number {returnthis.node.screenY -this.nodeSize() /2; }

渲染时:

Stack() {} .width(this.nodeSize()) .height(this.nodeSize()) .borderRadius(this.nodeSize() /2) .backgroundColor(this.selected ? XingtuTheme.primaryAction : XingtuTheme.accent) .opacity(this.selected ?0.98:this.node.opacity *0.82) .shadow({ radius:this.selected ?30:12+this.node.scale *5, color:this.selected ? XingtuTheme.harmonyLightShadow :'#3493C5FD', offsetX:0, offsetY:this.selected ?0:4})

这里节点本质上就是一个带圆角和阴影的 ArkUI 组件,但因为它的位置、大小、透明度都来自 3D 投影,所以看起来就有了空间感。

九、监听视口尺寸变化

投影计算需要知道当前屏幕宽高。

项目中使用onAreaChange获取视口尺寸:

.onAreaChange((_,area)=> { this.viewportWidth =Number(area.width); this.viewportHeight =Number(area.height); })

然后在计算投影时传入:

this.store.projectedNodes({ width:this.viewportWidth, height:this.viewportHeight });

这样不同设备尺寸下,星图都可以以屏幕中心为基准进行布局。

十、拖动时更新相机

当用户单指拖动时,更新相机角度:

this.store.updateCamera(deltaX *0.42, deltaY *0.28);this.refreshScene();

Store 中的实现:

updateCamera(deltaYaw: number, deltaPitch: number): void {this.camera = { yaw:this.camera.yaw + deltaYaw, pitch: clampPitch(this.camera.pitch + deltaPitch), distance:this.camera.distance, scale:this.camera.scale }; }

垂直角度需要限制:

exportfunctionclampPitch(nextPitch:number):number{returnMath.max(-80,Math.min(80, nextPitch)); }

如果不限制pitch,用户可能把场景翻转到不舒服的角度。

十一、缩放时更新 scale

双指缩放最终修改的是camera.scale

updateScale(nextScale: number): void {this.camera = { yaw: this.camera.yaw, pitch: this.camera.pitch, distance: this.camera.distance, scale: Math.max(0.6, Math.min(2.2, nextScale)) }; }

这里限制缩放范围在0.62.2之间。

适当限制交互范围,可以避免用户把节点放大到失控,或者缩小到完全看不清。

十二、总结 🌟

这篇文章分享了如何在 HarmonyOS / OpenHarmony 中用 ArkTS 和 ArkUI 普通组件实现轻量级 3D 透视投影。

核心步骤是:

  • Vec3保存节点三维坐标
  • CameraState保存相机旋转和缩放
  • rotatePoint计算旋转后的坐标
  • projectNode把 3D 坐标转换成屏幕坐标
  • scaleopacity表现远近关系
  • depth排序模拟遮挡关系
  • 用 ArkUI 组件渲染节点

这种方案不适合重型 3D 游戏,但非常适合知识星图、关系网络、AI 概念图、灵感空间等轻量级场景。

不用复杂引擎,也能让普通组件拥有空间感。✨

http://www.jsqmd.com/news/1092866/

相关文章:

  • Hot 100 --- K 个一组翻转链表
  • 庚子夜半漏下三刻,众微机突发雪崩!余施大华胄日志天网,救大匠于九死一生
  • FPGA加速同态矩阵向量乘法的技术解析与实践
  • 别只会用Office!打工人必学的5个AI办公技巧
  • 程序员AI时代35岁出路指南
  • OPENCV——RV1126+OPENCV在视频中添加LOGO图像
  • AI 替代传统 GUI:基于 MCP 的 OBCloud 工作流(09)
  • 《北戴河之恋》:换一个角度重新听
  • 液冷板焊接的质量账:70%的失效根源在钎焊,激光焊接怎么把良率拉到99%
  • 2026论文双降终极榜单:10款降AIGC工具,智能改写快速定稿成文
  • 从零开始学Java:第31章 网络和 HTTP:让 Java 程序和外部服务通信
  • FFmpeg视频切片与AES-128加密完整实战指南
  • 从零构建 AI 客服系统:Next.js 14 + RAG + 向量检索实战
  • 【HarmonyOS/OpenHarmony】创新体验:从应用入口到页面加载理解全场景应用基础链路
  • 如何用AI写代码 ? AI编程提示词怎么写 ?AI写的代码如何调试
  • U校园自动答题工具:如何2分钟搞定网课必修题的终极指南
  • 从弗朗西斯·奇切斯特的环球航行看:技术、勇气与人类精神的现代启示
  • ClamAV病毒库自动更新与异常告警:Linux服务器安全运维实战
  • 全平台Chrome配置SSLKEYLOGFILE与Wireshark解密HTTPS流量实战指南
  • Steam成就自由掌控:告别无法完成的游戏挑战
  • 小白也能懂的备份防勒索实战(一):不懂技术也要做备份?我试了十几种方案,最终选了它
  • 基于 Ragas 与通义千问实现 RAG 系统答案正确性自动评估
  • 基于鸿蒙十二阶均衡体系:境外全域隐性渗透的安全风险与均衡治理路径——基于全域均衡数理模型推演(十三)
  • 2026在线去除本地视频水印工具推荐!免费无水印导出、安全无需下载电脑端
  • 每日更新!免费股票日k、分时k线数据,etf分钟数据,截至到2026-07月最新数据,含全沪京深7000+股票
  • YgoMaster终极指南:如何免费搭建游戏王大师决斗离线服务器
  • 新手也能上手!2026年实测靠谱的专业降AI率平台
  • 智能微博文案助手项目介绍
  • 从“方阵的行列式”说起:一次对数学严谨性的追问
  • 5分钟高效激活Windows与Office:实用智能激活完整指南