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

纹渊 HarmonyOS 7 工程实战(14):AI 生成图回填到 3D 材质的异步链路

一、网络 URL 不能直接等同于三维纹理

AI 服务返回的通常是远程图片 URL,三维资源工厂需要的却是可读取、生命周期明确的图像资源。若直接把远程字符串传进材质,网络波动、URL 过期和组件销毁都会变成难以定位的渲染问题。稳定链路应分为五步:确认当前结果仍有效、下载二进制、写入应用沙箱、创建三维图像资源、把材质绑定到所有目标子网格。

下面的运行结果展示了纹样已经贴合到陶瓷杯表面。它证明的不只是“生成了一张图片”,还包括纹样选择、载体选择、预览渲染和导出区域已经消费同一份结果。

阶段输入输出失败时保留什么
AI 生成prompt、授权、接口配置图片 URL纹样与 prompt
下载HTTPS URLArrayBuffer旧业务选择,不保留假图片
沙箱落盘字节数组稳定文件路径可重试下载
资源创建沙箱路径G3DImage原模型材质
材质绑定图像与场景节点已贴图模型二维或原材质预览

二、请求结果先经过业务有效性检查

异步请求返回时,用户可能已经切换纹样、载体或退出页面。只检查 HTTP 200 不够,还要验证结果属于当前请求。可以用递增令牌标记最新任务;旧请求即使成功,也不能覆盖新选择。

@State aiImageUrl: string = '' @State aiBusy: boolean = false private requestToken: number = 0 private async generateImage(prompt: string): Promise<void> { const token = ++this.requestToken this.aiBusy = true this.aiImageUrl = '' try { const result = await requestAiImage( this.apiKey, prompt, this.imageApiUrl ) if (token !== this.requestToken || result.imageUrl.length === 0) { return } this.aiImageUrl = result.imageUrl this.syncContinuationSnapshot() } finally { if (token === this.requestToken) { this.aiBusy = false } } }

清空旧 URL 放在请求开始处,避免新请求失败时页面继续显示旧图。令牌不等于真正取消网络连接,但能阻止过期回调修改当前状态;若底层请求支持取消,还应在页面退出时同时关闭连接。

三、下载与落盘要形成原子边界

下载代码必须区分响应码、结果类型和文件写入是否完成。直接用最终文件名写入时,组件可能在写到一半读取到损坏图片。更稳妥的做法是先写临时文件,完成后再替换目标文件。

async function downloadTexture(url: string, finalPath: string): Promise<string> { const request = http.createHttp() const tempPath = `${finalPath}.part` try { const response = await request.request(url, { method: http.RequestMethod.GET, expectDataType: http.HttpDataType.ARRAY_BUFFER, connectTimeout: 15000, readTimeout: 30000 }) if (response.responseCode !== 200 || !(response.result instanceof ArrayBuffer)) { return '' } const file = fs.openSync(tempPath, fs.OpenMode.CREATE | fs.OpenMode.TRUNC | fs.OpenMode.WRITE_ONLY) fs.writeSync(file.fd, response.result) fs.closeSync(file) fs.renameSync(tempPath, finalPath) return finalPath } finally { request.destroy() } }

真实工程还应限制 Content-Type 和最大字节数,避免错误页或超大文件进入纹理创建阶段。目标文件名至少包含纹样 ID 与结果版本,不能让并发任务写入同一路径。

四、沙箱缓存要可失效、可回收

缓存的价值是把网络不确定性隔离在资源创建之前,但缓存不能无限增长。可用patternId + 内容摘要作为键;新的 AI 结果出现时生成新键,旧文件由定期清理策略回收。内置纹样与 AI 结果也应走统一的“准备沙箱纹理”接口。

async function prepareTexture(input: TextureInput): Promise<string> { const dir = `${getContext().cacheDir}/pattern_tex` ensureDirectory(dir) if (input.aiImageUrl.length > 0) { const key = hashText(input.aiImageUrl).substring(0, 12) const output = `${dir}/ai_${input.patternId}_${key}.png` if (fs.accessSync(output)) { return output } return await downloadTexture(input.aiImageUrl, output) } const output = `${dir}/builtin_${input.patternId}.png` if (!fs.accessSync(output)) { await packMediaResourceToPng(input.patternResource, output) } return output }

“AI URL 为空”不代表失败,它表示使用内置纹样资源。只有准备函数返回空路径时,三维组件才进入可见错误或原材质回退。

五、创建图像资源后再遍历几何节点

沙箱文件可读后,资源工厂创建G3DImage,再为当前纹理构造 PBR 材质。模型可能包含多个 Geometry 和多个 SubMesh,只替换第一个材质会出现“杯身已贴图、杯把仍是旧色”的不完整结果。

private async applyPatternTexture(scene: Scene, factory: SceneResourceFactory, sandboxPath: string): Promise<boolean> { const image = await factory.createImage({ name: 'patternTexture', uri: sandboxPath }) const geometries = this.findGeometryNodes(scene) if (geometries.length === 0) { return await this.applyShaderFallback(factory, image) } const material = await factory.createMaterial( { name: 'patternPbrMaterial' }, MaterialType.METALLIC_ROUGHNESS ) as MetallicRoughnessMaterial material.baseColor = { image, factor: { x: 1, y: 1, z: 1, w: 1 } } material.cullMode = 0 geometries.forEach((geometry: Geometry) => { geometry.mesh?.subMeshes?.forEach((subMesh) => { subMesh.material = material }) }) return true }

遍历函数需要处理空根节点和非 Geometry 节点,不能假设 GLB 结构永远固定。材质绑定成功后再把textureApplied设为真,UI 上的“纹样已贴合”才具有真实含义。

六、回退顺序要从局部到整体

找不到 Geometry 时,可以尝试 Shader 材质输入;Shader 也不可用时,继续展示原始模型;模型本身加载失败时,再切到二维预览。每次只缩减一层能力,避免一个材质异常直接清空整个创作状态。

private async applyShaderFallback(factory: SceneResourceFactory, image: G3DImage): Promise<boolean> { try { const material = await factory.createMaterial( { name: 'patternShaderMaterial' }, MaterialType.SHADER ) const shader = (material as ShaderMaterial).colorShader if (shader === undefined || shader === null) { return false } shader.inputs['BASE_COLOR_Image'] = image return true } catch { return false } } private showPreview(result: TextureResult): void { if (result.textureApplied) { this.previewState = 'textured3d' } else if (result.modelLoaded) { this.previewState = 'plain3d' } else { this.previewState = 'canvas2d' } }

状态标签应真实反映结果:textured3d才能显示“纹样已贴合”;原模型只能显示“模型已加载”;二维回退则明确提示“当前设备使用二维预览”。

七、异常矩阵

异常检测点页面状态恢复动作
AI 返回空 URL生成结果解析失败,不展示旧图修改 prompt 后重试
图片 URL 过期下载响应码保留选择,纹理未应用重新生成
返回非图片数据Content-Type/结果类型拒绝落盘更换接口或重试
写文件失败临时文件写入清理.part文件检查空间后重试
页面已切换请求令牌丢弃旧回调使用新任务结果
Geometry 为空场景遍历尝试 Shader保留原模型
材质创建失败资源工厂原模型状态切二维预览
设备不支持 3D模型加载二维状态继续导出图片

把错误写入日志还不够,用户至少要知道当前显示的是 AI 结果、内置纹样、原模型还是二维回退,否则无法判断生成按钮是否真正生效。

八、验证步骤

1. 生成一张新图,确认旧预览立即清空,加载结束后只出现新结果。 2. 切换纹样后立即再次生成,确认第一个请求即使晚返回也不会覆盖第二个结果。 3. 断网后触发下载,确认不会生成半文件,三维组件显示可恢复状态。 4. 检查缓存键,确认不同 AI URL 不会写入同一个目标文件。 5. 使用包含多个子网格的模型,确认所有可见表面材质一致更新。 6. 模拟 Geometry 为空,确认先走 Shader 回退;继续失败时仍显示原模型或二维预览。 7. 退出页面后等待旧请求完成,确认旧回调不会修改新页面状态。 8. 观察“纹样已贴合”标签,只在真实材质绑定成功后出现。

九、总结

AI 图片进入三维材质不是一次简单赋值,而是一条跨网络、文件系统和渲染资源的异步流水线。请求令牌阻止过期结果回写,临时文件保证落盘完整,沙箱缓存隔离远程 URL,PBR 材质遍历覆盖所有子网格,分层回退则让原模型和二维预览继续承接业务。每个阶段都有可观察状态,才能证明最终贴图属于当前纹样和当前载体。

网络请求的基础用法可参考HTTP 数据请求。

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

相关文章:

  • 北京百达翡丽官方售后服务中心|官方售后电话及地址权威公告(2026年7月最新) - 百达翡丽售后服务官网
  • 天气丹同源原料供应内幕:B端进货老板必看的工艺公差与验货底牌
  • 如何用超星学习通签到工具3步告别签到烦恼?
  • efaqa-corpus-zh vs 原始语料库:哪个更适合你的LLM微调项目?终极对比指南
  • 国产化落地避坑 · 干货向|Oracle 迁金仓 KES,我把外连接消除排在隐性陷阱第一位
  • 云南野生菌科普与销售系统
  • 深入解析USB控制器寄存器:从FIFO配置到端点控制实战
  • Java JDK版本选择策略与LTS长期支持解析
  • 鸿蒙 ArkTS 实战:Pet Boarding Booking 从宠物寄养预约到生活服务工具完整解析
  • 5分钟彻底解决重复文件困扰:Czkawka全能磁盘清理神器终极指南
  • 2026年7月甘井子区靠谱的财务公司避坑指南:大连中小企业从低价代账转向合规财税托管 - 资讯速览
  • 【小程序毕业设计】基于微信小程序的订餐配送一体化服务平台 外卖订单追踪与商家营收统计管理系统(源码+文档+远程调试,全bao定制等)
  • 一些杂七杂八的记录
  • 常州钻石回收店哪家好?2026资质甄选与出手时机攻略 - 奢侈品回收评测
  • OpenCV-Python实战(12)——一文详解AR增强现实
  • 渣汁分离原汁机三段精榨,出渣干爽不浪费每滴食材
  • 2026小提琴新手选购避坑指南!5款高性价比小提琴推荐不花冤枉钱
  • 如何在Windows、Linux和macOS上使用ipatool下载iOS应用包:完整指南
  • 终极Wand-Enhancer完整指南:三步解锁Wand专业版免费体验
  • 项目文档:基于MATLAB图像处理的肾结石超声影像辅助检测系统设计与实现
  • 怎样在一台电脑安装多个版本的JDK并切换使用?一篇文章教会你所有细节
  • 病区排班管理智能体开发文档
  • 2026临夏州甲醛检测怎么选:只做检测、不做治理的专业 CMA 资质实验室——中醛甲醛检测中心室内空气及环境检测 - 绿呼吸检测中心
  • 拆解合肥黄金回收价差核心逻辑:中间商差价、提纯损耗、门店租金影响报价 - 生活商业速报
  • ncclient源码解析:深入理解Python NETCONF客户端实现原理
  • Redis 管道 Pipeline:如何减少频繁交互,提升性能?
  • 零代码AI数据大屏生成工具推荐(拖拽设计+实时刷新)
  • Kubedog 故障排查指南:快速定位 Kubernetes 部署问题的 7 个技巧
  • Ingress2Gateway 项目概览:为什么它是 Kubernetes Ingress 迁移的最佳选择
  • 响应式图片的未来:grunt-responsive-images在现代前端工作流中的角色