After loading the model file, it occasionally fails to rotate. As shown in the picture, my component is divided into 2 Tabs, one is the normal map and the other is the texture map. The normal map is displayed by default. Occasionally, it cannot be rotated with the mouse. At this time, switch to the texture mode, and then switch back to the mouse to control the rotation. What is the problem and how to solve it?
“@babylonjs/core”: “^7.44.0”,
“@babylonjs/loaders”: “^7.44.0”,
The following is the core code of vue3:
<template>
<div class="d3-render-kit">
<div class="chnange-type">
<span :class="['hitems-baimo', curType === 'faxian' ? 'active' : '']" @click="curType = 'faxian'"
showTitle="texture off"></span>
<span :class="['hitems-faxian', curType === 'baimo' ? 'active' : '']" @click="curType = 'baimo'"
showTitle="texture on"></span>
</div>
<span v-if="isShowDownload" class="hitems-download2" @click="download"></span>
<span class="tag" v-if="isShowTag">Image to 3D</span>
<canvas ref="canvasRef" class="d3-canvas"></canvas>
<PlaceHolder v-if="loading" class="d3-place-holder" text="loading model file..." />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch, watchEffect } from 'vue';
import * as BABYLON from '@babylonjs/core';
import { OBJFileLoader } from "@babylonjs/loaders/OBJ/objFileLoader";
import PlaceHolder from '@/components/PlaceHolder/index.vue';
import ToastService from '@/components/AppToast/toast';
import { MaterialManager } from './MaterialManager';
import { registerBuiltInLoaders } from "@babylonjs/loaders/dynamic";
registerBuiltInLoaders();
type RenderType = "baimo" | "faxian";
defineOptions({ name: 'D3RenderKit' })
const { modelUrl, isShowDownload, isShowTag } = defineProps({
modelUrl: {
type: String,
required: true
},
isShowDownload: {
type: Boolean,
default: true
},
isShowTag: {
type: Boolean,
default: false
}
})
const curType = ref<RenderType>('faxian')
const canvasRef = ref<HTMLCanvasElement | null>(null)
const modelScene = ref<BABYLON.Scene | null>(null);
let scene: BABYLON.Scene | null = null;
let engine: BABYLON.Engine | null = null;
const loading = ref(true)
let materialManager: MaterialManager
const fileType = ref("glb")
let skylight1: BABYLON.HemisphericLight;
let pointLight: BABYLON.PointLight;
let pipeline: BABYLON.DefaultRenderingPipeline;
watchEffect(() => {
fileType.value = modelUrl.toLowerCase().endsWith('.glb') ? 'glb' : 'obj';
})
watch([modelScene, curType, fileType], () => {
if (modelScene.value) {
if (fileType.value === "obj") {
const material = curType.value === 'baimo' ? getWhiteMaterial() : getFaxianMaterial();
materialManager.applyNewMaterial(material);
if (curType.value === 'baimo') {
skylight1.intensity = 1.2;
pointLight.intensity = 1.2;
}
} else if (fileType.value === "glb") {
if (curType.value === 'baimo') {
materialManager.restoreOriginal();
pipeline.imageProcessingEnabled = true;
} else {
materialManager.applyNewMaterial(getFaxianMaterial());
}
}
if (curType.value === 'faxian') {
// 禁用图像处理
pipeline.imageProcessingEnabled = false;
}
}
}, { immediate: true });
const startRender = async () => {
if (!engine) return;
scene = new BABYLON.Scene(engine)
scene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
// Environment Texture
const envTexture = BABYLON.CubeTexture.CreateFromPrefilteredData("studio_small_08_1k.env", scene);
envTexture.level = 0.8;
// 相机
const camera = new BABYLON.ArcRotateCamera('camera', Math.PI / 2, Math.PI / 2, 3.5, BABYLON.Vector3.Zero(), scene);
// 限制物体最大缩放
camera.lowerRadiusLimit = 1.2
// 限制物体最小缩放
camera.upperRadiusLimit = 5
camera.minZ = 0.1;
camera.maxZ = 100;
// 自动旋转
camera.useAutoRotationBehavior = true;
camera.attachControl(canvasRef.value, true);
// 天光
skylight1 = new BABYLON.HemisphericLight('skylight', new BABYLON.Vector3(0, 1, 0), scene);
skylight1.intensity = 1;
skylight1.groundColor = new BABYLON.Color3(0.2, 0.2, 0.2);
skylight1.diffuse = new BABYLON.Color3(0.6, 0.6, 0.6);
//点光
pointLight = new BABYLON.PointLight('pointLight', new BABYLON.Vector3(2, 0, 2), scene);
pointLight.intensity = 5
pointLight.diffuse = new BABYLON.Color3(0.92, 0.577, 0.28);
// 导入模型
modelScene.value = await BABYLON.SceneLoader.AppendAsync(modelUrl, '', scene, (event: BABYLON.ISceneLoaderProgressEvent) => {
if (event.loaded === event.total) {
loading.value = false
}
});
// 本地测试代码
// modelScene.value = await BABYLON.SceneLoader.AppendAsync("/models/img2.glb", undefined, scene, (event: BABYLON.ISceneLoaderProgressEvent) => {
// if (event.loaded === event.total) {
// loading.value = false
// }
// });
modelScene.value.meshes.forEach((mesh: any) => {
if (!mesh.material) return;
if (!mesh.material.albedoTexture) {
mesh.material.albedoColor = new BABYLON.Color3(0.95, 0.95, 0.95); // 设置基础颜色
}
if (!mesh.material.metallicTexture) {
mesh.material.metallic = 0.3; // 设置金属度
if (mesh.material.useRoughnessFromMetallicTextureGreen || mesh.material.materialuseRoughnessFromMetallicTextureAlpha) {
mesh.material.roughness = 0.5; // 设置粗糙度
}
}
mesh.material.reflectionTexture = envTexture;
})
// 初始化材质管理器, 注意这里必须在重新设置材质后面
materialManager = new MaterialManager(modelScene.value.meshes as BABYLON.AbstractMesh[]);
// 3. 添加后处理效果
pipeline = new BABYLON.DefaultRenderingPipeline(
"defaultPipeline",
true,
scene,
[camera]
);
pipeline.bloomEnabled = false;
pipeline.chromaticAberrationEnabled = false;
pipeline.depthOfFieldEnabled = false;
pipeline.fxaaEnabled = false;
pipeline.grainEnabled = false;
pipeline.sharpenEnabled = false;
pipeline.imageProcessingEnabled = true;
pipeline.imageProcessing.contrast = 1.8;
pipeline.imageProcessing.colorCurvesEnabled = false;
pipeline.imageProcessing.colorGradingEnabled = false;
pipeline.imageProcessing.toneMappingEnabled = false;
pipeline.imageProcessing.ditheringEnabled = false;
pipeline.imageProcessing.vignetteEnabled = false;
pipeline.samples = 4;
}
const getWhiteMaterial = () => {
const pbrMaterial = new BABYLON.PBRMaterial("pbrMaterial", scene!);
pbrMaterial.albedoColor = new BABYLON.Color3(1, 1, 1); // 设置基础颜色
pbrMaterial.metallic = 0.0; // 设置金属度
pbrMaterial.roughness = 1.0; // 设置粗糙度
return pbrMaterial;
}
const getFaxianMaterial = () => {
BABYLON.Effect.ShadersStore["customVertexShader"] =
"\r\n" +
"precision highp float;\r\n" +
"// Attributes\r\n" +
"attribute vec3 position;\r\n" +
"attribute vec3 normal;\r\n" +
"// Uniforms\r\n" +
"uniform mat4 worldViewProjection;\r\n" +
"uniform mat4 worldView;\r\n" +
"// Varying\r\n" +
"varying vec3 vNormal;\r\n" +
"void main(void) {\r\n" +
" gl_Position = worldViewProjection * vec4(position, 1.0);\r\n" +
" vNormal = (worldView * vec4(normal, 0.0)).xyz;\r\n" +
"}\r\n";
BABYLON.Effect.ShadersStore["customFragmentShader"] =
"\r\n"
+ "precision highp float;\r\n"
+ "varying vec3 vNormal;\r\n"
+ "void main(void) {\r\n"
+ " gl_FragColor = vec4(vNormal*vec3(0.5,0.5,-0.5) + vec3(0.5), 1.0);\r\n"
+ "}\r\n";
const shaderMaterial = new BABYLON.ShaderMaterial(
"shader",
scene!,
{
vertex: "custom",
fragment: "custom",
},
{
attributes: ["position", "normal"],
uniforms: ["world", "worldView", "worldViewProjection", "view", "projection"],
},
);
return shaderMaterial;
}
const initEngine = () => {
if (!canvasRef.value) return
// 针对缺失法线的模型文件,自动计算法线
OBJFileLoader.COMPUTE_NORMALS = true;
engine = new BABYLON.Engine(canvasRef.value, true)
// 隐藏加载Loading
engine.loadingScreen = {
displayLoadingUI: function () { },
hideLoadingUI: function () { },
loadingUIBackgroundColor: "",
loadingUIText: ""
};
engine.runRenderLoop(() => {
scene?.render();
});
window.addEventListener('resize', () => {
engine?.resize();
});
}
onMounted(() => {
initEngine()
startRender();
})
watchEffect(() => {
if (!modelUrl) return;
startRender()
})
const download = () => {
ToastService({
message: 'Downloading, Please waited',
type: 'success',
})
const link = document.createElement('a');
link.href = modelUrl;
link.download = 'model.obj';
link.click();
}
</script>
<style lang="stylus" scoped>
@import './index.styl';
</style>