图片上,那几根管子本来应该出现在背面,应该是看不到的。但却出现在了前部。用xr-frame打开模型倒是没事,但用visionkit却不行。
import {
createScopedThreejs
} from 'threejs-miniprogram'
import {
registerGLTFLoader
} from '../loaders/gltf-loader'
import cloneGltf from '../loaders/gltf-clone'
const info = wx.getSystemInfoSync()
export default function getBehavior() {
return Behavior({
data: {
width: 375,
height: 600,
fps: 0,
memory: 0,
cpu: 0,
},
methods: {
onReady() {
// 使用组件作用域的选择器查询
this.createSelectorQuery()
.select('#webgl')
.node()
.exec(res => {
console.log('选择器查询结果:', res)
// 检查是否找到了元素
if (!res || !res[0] || !res[0].node) {
console.error('未找到webgl canvas元素')
this.setData({
loading: false,
loadingText: '初始化失败:未找到canvas元素'
})
return
}
this.canvas = res[0].node
const info = wx.getSystemInfoSync()
const pixelRatio = info.pixelRatio
const calcSize = (width, height) => {
console.log(`canvas size: width = ${width} , height = ${height}`)
this.canvas.width = width * pixelRatio
this.canvas.height = height * pixelRatio
this.setData({
width,
height,
})
}
// 设置初始尺寸
const initialWidth = info.windowWidth
const initialHeight = info.windowHeight * 0.8
calcSize(initialWidth, initialHeight)
this.initVK()
})
},
onUnload() {
if (this._texture) {
this._texture.dispose()
this._texture = null
}
if (this.renderer) {
this.renderer.dispose()
this.renderer = null
}
if (this.scene) {
this.scene.dispose()
this.scene = null
}
if (this.camera) this.camera = null
if (this.model) this.model = null
if (this._insertModel) this._insertModel = null
if (this._insertModels) this._insertModels = null
if (this.planeBox) this.planeBox = null
if (this.mixers) {
this.mixers.forEach(mixer => mixer.uncacheRoot(mixer.getRoot()))
this.mixers = null
}
if (this.clock) this.clock = null
if (this.THREE) this.THREE = null
if (this._tempTexture && this._tempTexture.gl) {
this._tempTexture.gl.deleteTexture(this._tempTexture)
this._tempTexture = null
}
if (this._fb && this._fb.gl) {
this._fb.gl.deleteFramebuffer(this._fb)
this._fb = null
}
if (this._program && this._program.gl) {
this._program.gl.deleteProgram(this._program)
this._program = null
}
if (this.canvas) this.canvas = null
if (this.gl) this.gl = null
if (this.session) this.session = null
if (this.anchor2DList) this.anchor2DList = []
},
initVK() {
// 首先检查相机权限
this.checkCameraPermission().then(() => {
// 权限获取成功后再初始化AR
this.initARSession()
}).catch(err => {
console.error('相机权限检查失败:', err)
this.setData({
loading: false,
loadingText: '相机权限获取失败,请在设置中开启相机权限'
})
})
},
/**
* 检查相机权限
*/
checkCameraPermission() {
return new Promise((resolve, reject) => {
wx.getSetting({
success: (res) => {
console.log('当前权限状态:', res.authSetting)
if (res.authSetting['scope.camera'] === true) {
// 已授权
resolve()
} else if (res.authSetting['scope.camera'] === false) {
// 已拒绝,需要引导用户手动开启
this.showCameraPermissionDialog()
reject(new Error('相机权限被拒绝'))
} else {
// 未询问过,尝试授权
wx.authorize({
scope: 'scope.camera',
success: () => {
console.log('相机权限获取成功')
resolve()
},
fail: (error) => {
console.error('相机权限获取失败:', error)
this.showCameraPermissionDialog()
reject(error)
}
})
}
},
fail: (error) => {
console.error('获取权限状态失败:', error)
reject(error)
}
})
})
},
/**
* 显示相机权限对话框
*/
showCameraPermissionDialog() {
wx.showModal({
title: '需要相机权限',
content: 'AR功能需要使用相机权限,请在设置中开启相机权限后重试',
confirmText: '去设置',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
wx.openSetting({
success: (settingRes) => {
if (settingRes.authSetting['scope.camera']) {
// 用户开启了权限,重新初始化
this.initARSession()
} else {
// 用户仍未开启权限
this.setData({
loading: false,
loadingText: '需要相机权限才能使用AR功能'
})
}
}
})
} else {
// 用户选择取消
this.setData({
loading: false,
loadingText: '需要相机权限才能使用AR功能'
})
}
}
})
},
/**
* 初始化AR会话
*/
initARSession() {
this.setData({
loading: true,
loadingText: '正在初始化AR系统...'
})
// 初始化 threejs
this.initTHREE()
const THREE = this.THREE
// 自定义初始化
if (this.init) this.init()
console.log('this.gl', this.gl)
const session = this.session = wx.createVKSession({
track: {
plane: {
mode: 1
},
},
version: 'v1',
gl: this.gl
})
session.start(err => {
if (err) {
console.error('VK error: ', err)
this.setData({
loading: false,
loadingText: 'AR系统启动失败,请检查设备是否支持AR功能'
})
return
}
console.log('@@@@@@@@ VKSession.version', session.version)
// AR启动成功,关闭加载状态
this.setData({
loading: false,
loadingText: ''
})
const canvas = this.canvas
const calcSize = (width, height, pixelRatio) => {
console.log(`canvas size: width = ${width} , height = ${height}`)
this.canvas.width = width * pixelRatio
this.canvas.height = height * pixelRatio
this.setData({
width,
height,
})
}
session.on('resize', () => {
const info = wx.getSystemInfoSync()
calcSize(info.windowWidth, info.windowHeight * 0.8, info.pixelRatio)
})
const loader = new THREE.GLTFLoader()
// 使用 loadModelById 获取模型URL并加载模型
if (this.data.modelId) {
this.loadModelById(this.data.modelId).then(modelUrl => {
console.log('开始加载模型:', modelUrl)
loader.load(modelUrl, async (gltf) => {
console.log('模型加载成功:', gltf)
// 先分析材质信息
this.analyzeMaterials(gltf.scene)
// 使用异步处理材质,避免卡顿
await this.processMaterialsAsync(gltf.scene)
this.model = {
scene: gltf.scene,
animations: gltf.animations,
}
this.setData({ loading: false })
}, undefined, (error) => {
console.error('模型加载失败:', error)
this.setData({
loading: false,
loadingText: '模型加载失败'
})
})
}).catch(error => {
console.error('获取模型URL失败:', error)
this.setData({
loading: false,
loadingText: '获取模型信息失败'
})
})
} else {
// 如果没有 modelId,使用默认的机器人模型
loader.load('https://dldir1.qq.com/weixin/miniprogram/RobotExpressive_aa2603d917384b68bb4a086f32dabe83.glb', gltf => {
console.log('默认机器人模型加载成功:', gltf)
// 先分析材质信息(在设置光源之前)
this.analyzeMaterials(gltf.scene)
// 然后处理材质,解决黑色显示问题
this.processMaterials(gltf.scene)
this.model = {
scene: gltf.scene,
animations: gltf.animations,
}
}, undefined, (error) => {
console.error('默认机器人模型加载失败:', error)
})
}
this.clock = new THREE.Clock()
loader.load('https://dldir1.qq.com/weixin/miniprogram/reticle_4b6cc19698ca4a08b31fd3c95ce412ec.glb', gltf => {
const reticle = this.reticle = gltf.scene
reticle.visible = false
this.scene.add(reticle)
})
// 限制调用帧率
const fps = 30
const fpsInterval = 1000 / fps
let last = Date.now()
// 逐帧渲染
const onFrame = timestamp => {
const now = Date.now()
const mill = now - last
// 经过了足够的时间
if (mill > fpsInterval) {
last = now - (mill % fpsInterval) // 校正当前时间
const frame = session.getVKFrame(canvas.width, canvas.height)
if (frame) {
this.render(frame)
}
}
session.requestAnimationFrame(onFrame)
}
session.requestAnimationFrame(onFrame)
})
},
initTHREE() {
const THREE = this.THREE = createScopedThreejs(this.canvas)
registerGLTFLoader(THREE)
// 相机
this.camera = new THREE.Camera()
// 场景
const scene = this.scene = new THREE.Scene()
// 根据参数设置光照
// 1. 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4) // 强度 40.0 对应 0.4
scene.add(ambientLight)
// 2. 三个方向光
const light0 = new THREE.DirectionalLight(0xffffff, 1.0) // 强度 100.0 对应 1.0
light0.position.set(1, 1, 1)
scene.add(light0)
const light1 = new THREE.DirectionalLight(0xffffff, 1.0)
light1.position.set(-1, 1, 1)
scene.add(light1)
const light2 = new THREE.DirectionalLight(0xffffff, 1.0)
light2.position.set(0, 1, -1)
scene.add(light2)
// 渲染层
const renderer = this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
renderer.gammaOutput = true
renderer.gammaFactor = 2.2
},
updateAnimation() {
const dt = this.clock.getDelta()
if (this.mixers) this.mixers.forEach(mixer => mixer.update(dt))
},
/**
* 根据模型ID加载模型
*/
async loadModelById(modelId) {
try {
console.log('开始加载模型:', modelId)
this.setData({
loading: true,
loadingText: '正在获取模型信息...'
})
const result = await wx.cloud.callFunction({
name: 'modelManager',
data: {
action: 'detail',
data: {
modelId
}
}
})
if (!result.result || !result.result.success) {
throw new Error('获取模型信息失败')
}
const model = result.result.data
console.log('模型信息:', model)
if (!model.fileInfo || !model.fileInfo.downloadUrl) {
throw new Error('模型文件信息不完整')
}
// 检查文件格式
const fileType = model.fileInfo.fileType?.toLowerCase() || ''
const fileName = model.fileInfo.downloadUrl.split('/').pop().toLowerCase()
// 检查是否是已知的可能不兼容的格式
const needsConversion = (
fileType.includes('fbx') ||
fileName.endsWith('.fbx') ||
fileType.includes('obj') ||
fileName.endsWith('.obj')
)
if (needsConversion) {
this.setData({
loadingText: '当前模型格式可能不兼容,建议转换为glTF/GLB格式'
})
await new Promise(resolve => setTimeout(resolve, 2000))
}
this.setData({
loadingText: '正在下载模型文件...'
})
// 返回模型的下载URL
const url = model.fileInfo.downloadUrl
return url
} catch (error) {
console.error('加载模型失败:', error)
this.setData({
loading: false,
loadingText: error.message || '加载失败'
})
throw error
}
},
copyRobot() {
const THREE = this.THREE
const {
scene,
animations
} = cloneGltf(this.model, THREE)
scene.scale.set(0.5, 0.5, 0.5)
// 动画混合器
const mixer = new THREE.AnimationMixer(scene)
// 播放所有可用动画
animations.forEach(clip => {
const action = mixer.clipAction(clip);
action.play();
})
// 如果没有动画,添加提示日志
if (animations.length === 0) {
console.warn('模型没有找到任何动画数据');
}
this.mixers = this.mixers || []
this.mixers.push(mixer)
scene._mixer = mixer
return scene
},
getRobot() {
const THREE = this.THREE
const model = new THREE.Object3D()
model.add(this.copyRobot())
this._insertModels = this._insertModels || []
this._insertModels.push(model)
if (this._insertModels.length > 5) {
const needRemove = this._insertModels.splice(0, this._insertModels.length - 5)
needRemove.forEach(item => {
if (item._mixer) {
const mixer = item._mixer
this.mixers.splice(this.mixers.indexOf(mixer), 1)
mixer.uncacheRoot(mixer.getRoot())
}
if (item.parent) item.parent.remove(item)
})
}
return model
},
onTouchEnd(evt) {
if (this.scene && this.model && this.reticle) {
const model = this.getRobot()
model.position.copy(this.reticle.position)
model.rotation.copy(this.reticle.rotation)
this.scene.add(model)
}
},
/**
* 分析模型材质类型和光照信息
*/
analyzeMaterials(scene) {
const THREE = this.THREE
console.log('=== 开始分析模型材质信息 ===')
let materialCount = 0
let meshCount = 0
const materialTypes = new Map()
scene.traverse((child) => {
if (child.isMesh) {
meshCount++
console.log(`--- Mesh ${meshCount}: ${child.name || 'unnamed'} ---`)
if (child.material) {
const materials = Array.isArray(child.material) ? child.material : [child.material]
materials.forEach((mat, index) => {
materialCount++
console.log(` 材质 ${index + 1}:`)
console.log(` 类型: ${mat.constructor.name}`)
console.log(` UUID: ${mat.uuid}`)
console.log(` 名称: ${mat.name || '未命名'}`)
// 统计材质类型
const typeName = mat.constructor.name
materialTypes.set(typeName, (materialTypes.get(typeName) || 0) + 1)
// 基础属性
console.log(` 透明度: ${mat.opacity}`)
console.log(` 透明: ${mat.transparent}`)
console.log(` 可见: ${mat.visible}`)
console.log(` 双面: ${mat.side === THREE.DoubleSide ? '双面' : '单面'}`)
// 颜色信息
if (mat.color) {
console.log(` 颜色: rgb(${Math.round(mat.color.r * 255)}, ${Math.round(mat.color.g * 255)}, ${Math.round(mat.color.b * 255)})`)
console.log(` 颜色十六进制: #${mat.color.getHexString()}`)
}
// 自发光信息
if (mat.emissive) {
console.log(` 自发光颜色: rgb(${Math.round(mat.emissive.r * 255)}, ${Math.round(mat.emissive.g * 255)}, ${Math.round(mat.emissive.b * 255)})`)
console.log(` 自发光强度: ${mat.emissiveIntensity || 'N/A'}`)
}
// PBR材质特有属性
if (mat.isMeshStandardMaterial || mat.isMeshPhysicalMaterial) {
console.log(` 金属度: ${mat.metalness}`)
console.log(` 粗糙度: ${mat.roughness}`)
console.log(` 环境光遮蔽强度: ${mat.aoMapIntensity || 'N/A'}`)
console.log(` 法线贴图强度: ${mat.normalScale ? `(${mat.normalScale.x}, ${mat.normalScale.y})` : 'N/A'}`)
}
// 纹理信息
console.log(` 纹理信息:`)
if (mat.map) console.log(` - 基础纹理: ${mat.map.constructor.name}`)
if (mat.normalMap) console.log(` - 法线贴图: ${mat.normalMap.constructor.name}`)
if (mat.emissiveMap) console.log(` - 自发光贴图: ${mat.emissiveMap.constructor.name}`)
if (mat.metalnessMap) console.log(` - 金属贴图: ${mat.metalnessMap.constructor.name}`)
if (mat.roughnessMap) console.log(` - 粗糙度贴图: ${mat.roughnessMap.constructor.name}`)
if (mat.aoMap) console.log(` - 环境光遮蔽贴图: ${mat.aoMap.constructor.name}`)
if (mat.envMap) console.log(` - 环境贴图: ${mat.envMap.constructor.name}`)
// 检查是否是黑色材质
const isBlack = mat.color && mat.color.getHex() === 0x000000
const isDark = mat.color && (mat.color.r + mat.color.g + mat.color.b) < 0.3
// 检查自发光属性
const emissiveExists = mat.emissive !== undefined
const hasEmissiveColor = emissiveExists && mat.emissive.getHex() !== 0x000000
console.log(` 材质分析:`)
console.log(` - 是否为黑色: ${isBlack}`)
console.log(` - 是否偏暗: ${isDark}`)
console.log(` - 自发光属性存在: ${emissiveExists}`)
console.log(` - 是否有自发光颜色: ${hasEmissiveColor}`)
console.log(` - 是否有基础纹理: ${!!mat.map}`)
// 关键检查:如果emissive是undefined,这就是黑色的主要原因
if (mat.emissive === undefined) {
console.log(` - ⚠️ 发现问题: mat.emissive 是 undefined,这是导致黑色显示的主要原因`)
console.log(` - 建议处理: 需要创建自发光属性`)
} else if (!hasEmissiveColor) {
console.log(` - ⚠️ 发现问题: 自发光颜色为黑色,可能导致显示过暗`)
}
console.log(` - 建议处理: ${mat.emissive === undefined ? '需要创建自发光属性' : '材质正常'}`)
console.log(` ----------`)
})
}
}
})
// 输出总结信息
console.log(`=== 材质分析总结 ===`)
console.log(`网格数量: ${meshCount}`)
console.log(`材质总数: ${materialCount}`)
console.log(`材质类型分布:`)
materialTypes.forEach((count, type) => {
console.log(` ${type}: ${count} 个`)
})
// 分析光照需求
console.log(`=== 光照需求分析 ===`)
const hasBasicMaterial = materialTypes.has('MeshBasicMaterial')
const hasLambertMaterial = materialTypes.has('MeshLambertMaterial')
const hasPhongMaterial = materialTypes.has('MeshPhongMaterial')
const hasStandardMaterial = materialTypes.has('MeshStandardMaterial')
const hasPhysicalMaterial = materialTypes.has('MeshPhysicalMaterial')
console.log(`基础材质(不需要光照): ${hasBasicMaterial ? '是' : '否'}`)
console.log(`Lambert材质(需要环境光): ${hasLambertMaterial ? '是' : '否'}`)
console.log(`Phong材质(需要方向光): ${hasPhongMaterial ? '是' : '否'}`)
console.log(`Standard材质(需要复杂光照): ${hasStandardMaterial ? '是' : '否'}`)
console.log(`Physical材质(需要物理光照): ${hasPhysicalMaterial ? '是' : '否'}`)
const needsComplexLighting = hasStandardMaterial || hasPhysicalMaterial
console.log(`是否需要复杂光照系统: ${needsComplexLighting ? '是' : '否'}`)
if (needsComplexLighting) {
console.log(`推荐光照配置:`)
console.log(` - 环境光强度: 0.6-0.8`)
console.log(` - 半球光强度: 0.4-0.6`)
console.log(` - 主方向光强度: 0.8-1.0`)
console.log(` - 补充光强度: 0.3-0.5`)
}
console.log(`=== 材质分析完成 ===`)
},
/**
* 安全的材质处理方法,防止卡顿
*/
processMaterials(scene) {
const THREE = this.THREE
console.log('=== 开始安全处理材质 ===')
let processedCount = 0
let fixedEmissiveCount = 0
let errorCount = 0
const maxMaterials = 50 // 限制最大处理材质数量
try {
scene.traverse((child) => {
if (child.isMesh && child.material && processedCount < maxMaterials) {
const material = child.material
// 处理材质数组
const materials = Array.isArray(material) ? material : [material]
materials.forEach((mat, index) => {
if (processedCount >= maxMaterials) return
try {
processedCount++
// // 关键检查:如果emissive是undefined,立即修复
// if (mat.emissive === undefined) {
// mat.emissive = new THREE.Color(0x000000) // 先创建黑色自发光
// fixedEmissiveCount++
// console.log(`修复材质 ${processedCount}: 创建自发光属性`)
// }
// // 确保材质能够接收光照
// if (mat.isMeshStandardMaterial || mat.isMeshPhysicalMaterial) {
// // 处理颜色问题
// if (!mat.color || mat.color.getHex() === 0x000000) {
// mat.color = new THREE.Color(0x888888)
// }
// // 安全设置自发光颜色
// if (mat.emissive && mat.color) {
// mat.emissive.copy(mat.color).multiplyScalar(0.3)
// }
// // 处理自发光贴图(只在必要时)
// if (mat.map && !mat.emissiveMap) {
// mat.emissiveMap = mat.map
// }
// // 设置自发光强度
// if (mat.emissiveIntensity !== undefined) {
// mat.emissiveIntensity = Math.max(0.1, mat.emissiveIntensity || 0)
// }
// } else {
// // 对于非PBR材质
// if (!mat.color || mat.color.getHex() === 0x000000) {
// mat.color = new THREE.Color(0x888888)
// }
// }
// // 强制更新材质
// mat.needsUpdate = true
} catch (error) {
errorCount++
console.error(`处理材质 ${processedCount} 时出错:`, error)
}
})
}
})
console.log(`=== 材质处理完成 ===`)
console.log(`处理材质: ${processedCount} 个`)
console.log(`修复undefined自发光: ${fixedEmissiveCount} 个`)
console.log(`错误数量: ${errorCount} 个`)
if (processedCount >= maxMaterials) {
console.warn(`⚠️ 达到最大处理限制 ${maxMaterials},可能还有未处理的材质`)
}
} catch (error) {
console.error('材质处理过程中发生严重错误:', error)
}
},
/**
* 异步材质处理方法,分批处理避免卡顿
*/
async processMaterialsAsync(scene) {
const THREE = this.THREE
console.log('=== 开始异步处理材质 ===')
let processedCount = 0
let fixedEmissiveCount = 0
const batchSize = 5 // 每批处理5个材质
const allMaterials = []
// 收集所有材质
scene.traverse((child) => {
if (child.isMesh && child.material) {
const materials = Array.isArray(child.material) ? child.material : [child.material]
materials.forEach(mat => allMaterials.push(mat))
}
})
console.log(`发现 ${allMaterials.length} 个材质,开始分批处理`)
// 分批处理材质
for (let i = 0; i < allMaterials.length; i += batchSize) {
const batch = allMaterials.slice(i, i + batchSize)
batch.forEach((mat, index) => {
try {
processedCount++
// 关键修复:undefined自发光
// if (mat.emissive === undefined) {
// mat.emissive = new THREE.Color(0x000000)
// fixedEmissiveCount++
// }
// 处理PBR材质
if (mat.isMeshStandardMaterial || mat.isMeshPhysicalMaterial) {
if (!mat.color || mat.color.getHex() === 0x000000) {
mat.color = new THREE.Color(0x888888)
}
if (mat.emissive && mat.color) {
mat.emissive.copy(mat.color)
}
if (mat.map && !mat.emissiveMap) {
mat.emissiveMap = mat.map
}
}
mat.needsUpdate = true
} catch (error) {
console.error(`处理材质时出错:`, error)
}
})
// 每批处理后暂停一下,避免阻塞
if (i + batchSize < allMaterials.length) {
await new Promise(resolve => setTimeout(resolve, 10))
}
}
console.log(`=== 异步材质处理完成 ===`)
console.log(`处理材质: ${processedCount} 个`)
console.log(`修复undefined自发光: ${fixedEmissiveCount} 个`)
}
},
})
}