Meine Seite dauert lange, bis die Modelle geladen werden, sobald die Seite geladen wird, wenn die Seite geladen wird. Fast 0! minimal reproduzierbares Beispiel < /p>
Code: Select all
default loading
Web Worker loading
{
"imports": {
"three": "https://esm.sh/[email protected]/build/three.module.js",
"three/addons/": "https://esm.sh/[email protected]/examples/jsm/"
}
}
// base libs
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
// dependencies
let _glbLoader = new GLTFLoader();
// state
let _isSceneInstantiated = false;
// models
let _itemsContainer = undefined;
let _baseSkeleton = undefined;
// animation
let _clock = new THREE.Clock();
let _skeletonMixer = undefined;
// parts
let _scene = undefined;
let _camera = undefined;
let _renderer = undefined;
let InstantiateScene = (hasToLoadWithWebWorker = true) =>
{
if (!_isSceneInstantiated)
{
_isSceneInstantiated = true;
let canvas = document.getElementById('canvas-container');
canvas.classList.remove('hidden');
let rect = canvas.getBoundingClientRect();
_scene = new THREE.Scene();
_camera = new THREE.PerspectiveCamera(30, rect.width / rect.height, 0.1, 1000);
_renderer = new THREE.WebGLRenderer({ canvas: canvas, });
_renderer.setSize(rect.width, rect.height);
_renderer.setAnimationLoop(animate);
const geometry = new THREE.BoxGeometry(0.4, 0.4, 0.4);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
_scene.add(cube);
_camera.position.z = 5;
function animate()
{
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
let deltaTime = (60.0 / 1000.0);
if (_clock)
deltaTime = _clock.getDelta();
if (_skeletonMixer)
_skeletonMixer.update(deltaTime);
if (_renderer)
_renderer.render(_scene, _camera);
}
_scene.background = new THREE.Color("rgb(10, 80, 10)");
SetupInitialState(hasToLoadWithWebWorker);
}
}
let DestroyScene = () =>
{
if (_isSceneInstantiated)
{
_isSceneInstantiated = false;
_scene = undefined;
_camera = undefined;
_renderer = undefined;
let canvas = document.getElementById('canvas-container');
canvas.classList.add('hidden');
}
}
let SetupInitialState = (hasToLoadWithWebWorker = true) =>
{
_itemsContainer = new THREE.Group();
_scene.add(_itemsContainer);
_itemsContainer.position.set(0, -1, 0);
InstantiateLights();
let onFinishCallback = () =>
{
let blueMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff });
let redMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
InstantiateDynamicBodyPart('default-male-body-01-model-01.glb', blueMaterial, hasToLoadWithWebWorker);
InstantiateDynamicBodyPart('default-male-shirt-01-model-01.glb', redMaterial, hasToLoadWithWebWorker);
}
InstantiateBaseSkeleton(onFinishCallback, hasToLoadWithWebWorker)
}
let InstantiateLights = () =>
{
const mainUpLight = new THREE.DirectionalLight(0xffffff, 2);
const secondaryUpLight = new THREE.DirectionalLight(0xffffff, 1);
mainUpLight.position.set(20, 30, 10);
mainUpLight.target.position.set(0, 0, 0);
mainUpLight.castShadow = true;
secondaryUpLight.position.set(-20, 30, -10);
secondaryUpLight.target.position.set(0, 0, 0);
secondaryUpLight.castShadow = true;
const reverseLight1 = new THREE.DirectionalLight(0xffffff, 1);
const reverseLight2 = new THREE.DirectionalLight(0xffffff, 1);
reverseLight1.position.set(20, -30, -10);
reverseLight1.target.position.set(0, 0, 0);
reverseLight1.castShadow = false;
reverseLight2.position.set(-20, -30, 10);
reverseLight2.target.position.set(0, 0, 0);
reverseLight2.castShadow = false;
const ambientLight = new THREE.AmbientLight(0xaaaaaa); // soft white light
_scene.add(ambientLight);
_scene.add(mainUpLight);
}
let InstantiateBaseSkeleton = async (onFinishCallback, hasToLoadWithWebWorker = true) =>
{
let onFinishCallback2 = (baseModel) =>
{
if (baseModel)
{
_itemsContainer.add(baseModel);
let skeletonHelper = new THREE.SkeletonHelper(_scene);
_scene.add(skeletonHelper)
let baseBones = [];
baseModel.traverse(
(object) =>
{
switch (object.type)
{
case 'Bone':
baseBones.push(object);
break;
}
});
_baseSkeleton = new THREE.Skeleton(baseBones);
let charAnimationGroup = new THREE.AnimationObjectGroup(baseModel);
_skeletonMixer = new THREE.AnimationMixer(charAnimationGroup);
if (baseModel.animations.length > 0)
{
let currentAnimation = _skeletonMixer.clipAction(baseModel.animations[1]);
currentAnimation.play();
}
let finalScale = 1;
let finalPositionX = 0;
let finalPositionY = 0;
baseModel.scale.set(finalScale, finalScale, finalScale);
baseModel.position.set(finalPositionX, 0, finalPositionY);
}
onFinishCallback();
};
let baseModel = await LoadObject3D('default-skeleton-01.glb', onFinishCallback2, hasToLoadWithWebWorker);
}
let InstantiateDynamicBodyPart = (
modelFileName,
baseMaterial,
hasToLoadWithWebWorker = true) =>
{
let onFinishCallback = (baseModel) =>
{
if (baseModel)
{
let skinnedMeshesList = [];
baseModel.traverse(
(object) =>
{
switch (object.type)
{
case 'SkinnedMesh':
skinnedMeshesList.push(object);
break;
}
});
InjectMaterial(baseModel, baseMaterial);
skinnedMeshesList.map(
(skinnedMesh) =>
{
_itemsContainer.add(skinnedMesh);
skinnedMesh.skeleton = _baseSkeleton;
});
}
}
LoadObject3D(modelFileName, onFinishCallback, hasToLoadWithWebWorker);
}
let InjectMaterial = (baseModel, material) =>
{
if (baseModel)
{
baseModel.traverse(
(object) =>
{
if (object.isMesh)
object.material = material;
});
}
}
let GetRandomId = (length) =>
{
let result = '';
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let counter = 0;
while (counter < length)
{
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}
let LoadObject3D = (assetSubPath, onFinishCallback, hasToLoadWithWebWorker = true) =>
{
let value = undefined;
let assetsRootPath = 'https://y87vj9.csb.app/public/';
let assetPath = assetsRootPath + assetSubPath;
if (!hasToLoadWithWebWorker)
{
if (!value)
{
console.log('[default loading] > loading model... | assetPath =', assetPath);
let onSuccessCallback =
(modelGLTF) =>
{
console.log('[default loading] >>> model loaded! | assetPath =', assetPath);
let moodelObject3D = SkeletonUtils.clone(modelGLTF.scene.clone());
moodelObject3D.animations = modelGLTF.animations;
onFinishCallback(moodelObject3D);
};
_glbLoader.load(assetPath, onSuccessCallback);
};
}
else
{
if (typeof Worker !== 'undefined')
{
let requestId = GetRandomId(16);
_webworkersCallbacksById[requestId] = onFinishCallback;
let webworkerRequest =
{
RequestId: requestId,
ProcessName: 'LoadModel',
Params: [assetPath],
}
_worker.postMessage(JSON.stringify(webworkerRequest));
}
else
{
// Web workers are not supported in this environment.
// You should add a fallback so that your program still executes correctly.
console.error('$$q> AssetsService.GetAsset | Web workers are not supported in this environment!');
}
}
}
// region Worker
// state
let _webworkersCallbacksById = {};
let HandleWebworkerMessage = (data) =>
{
// let webworkerResponse = JSON.parse(data);
let webworkerResponse = JSON.parse(data.data);
let requestId = webworkerResponse.RequestId;
let baseModel = undefined;
if (webworkerResponse.RawResponse)
{
let object3DJSON = webworkerResponse.RawResponse;
const loader = new THREE.ObjectLoader();
baseModel = loader.parse(object3DJSON);
}
else
console.error('$> webworkerResponse.RawResponse is undefined! ');
let isCallbackRegistered = requestId in _webworkersCallbacksById;
if (isCallbackRegistered)
{
let callback = _webworkersCallbacksById[requestId];
callback(baseModel);
delete _webworkersCallbacksById[requestId];
}
else
console.error('$> Unexpected requestId! requestId = ', requestId);
}
const _worker = new Worker("/model-loading-worker.js", { type: 'module' });
_worker.onmessage = HandleWebworkerMessage;
//end region Worker
let DefaultIntantiatingButton = () =>
{
DestroyScene();
InstantiateScene(false);
}
let WebWorkerIntantiatingButton = () =>
{
DestroyScene();
InstantiateScene(true);
}
let SetupButtons = () =>
{
let defaultIntantiatingButton = document.querySelector('#default-intantiating-button');
let webWorkerIntantiatingButton = document.querySelector('#web-worker-intantiating-button');
defaultIntantiatingButton.onclick = DefaultIntantiatingButton;
webWorkerIntantiatingButton.onclick = WebWorkerIntantiatingButton;
}
SetupButtons();
DefaultIntantiatingButton();
< /code>
model-loading-worker.js
import { GLTFLoader } from '
https://esm.sh/[email protected]/examples/j ... FLoader.js';
// dependencies
let _glbLoader = new GLTFLoader();
let HandleMessage = async (request) =>
{
let response = {};
response.RequestId = request.RequestId;
switch (request.ProcessName)
{
case 'LoadModel':
{
let assetPath = request.Params[0];
console.log('[web worker] > loading model... | assetPath =', assetPath);
let onSuccessCallback =
(modelGLTF) =>
{
console.log('[web worker] >>> model loaded! | assetPath =', assetPath);
let finalAssetValue = modelGLTF.scene;
finalAssetValue.animations = modelGLTF.animations;
let finalAssetValueObject3DJSON = finalAssetValue.toJSON()
response.RawResponse = finalAssetValueObject3DJSON;
}
await _glbLoader.loadAsync(assetPath).then(onSuccessCallback);
break
}
default:
{
console.error('Unexpected request.ProcessName! | request.ProcessName =', request.ProcessName);
break;
}
}
return response;
}
addEventListener(
'message',
async ({ data }) =>
{
let webworkerRequest = JSON.parse(data, function (key, value)
{
return value;
});
let response = await HandleMessage(webworkerRequest);
data = JSON.stringify(response);
postMessage(data);
});
< /code>
I'll leave a link below to a practical example on codesandbox with reproduction (change between loading modes by changing the state of the checkbox in the lower right corner and then press "Instatiate Scene". The buttons have a yellow hover to test with the mouse if the page is really stuck during loading, as well as the cube animation)
https://codesandbox.io/p/sandbox/y87vj9
Any help or guidance will be very welcome, I've been trying to find a way to improve the optimization of this page for about 2 weeks, and this was the only thing that had a good result in performance, however, it's not working.
Meine Seite dauert lange, bis die Modelle geladen werden, sobald die Seite geladen wird, wenn die Seite geladen wird. Fast 0! minimal reproduzierbares Beispiel < /p>
[code]index.html[/code]
[code]
default loading
Web Worker loading
{
"imports": {
"three": "https://esm.sh/
[email protected]/build/three.module.js",
"three/addons/": "https://esm.sh/
[email protected]/examples/jsm/"
}
}
// base libs
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
// dependencies
let _glbLoader = new GLTFLoader();
// state
let _isSceneInstantiated = false;
// models
let _itemsContainer = undefined;
let _baseSkeleton = undefined;
// animation
let _clock = new THREE.Clock();
let _skeletonMixer = undefined;
// parts
let _scene = undefined;
let _camera = undefined;
let _renderer = undefined;
let InstantiateScene = (hasToLoadWithWebWorker = true) =>
{
if (!_isSceneInstantiated)
{
_isSceneInstantiated = true;
let canvas = document.getElementById('canvas-container');
canvas.classList.remove('hidden');
let rect = canvas.getBoundingClientRect();
_scene = new THREE.Scene();
_camera = new THREE.PerspectiveCamera(30, rect.width / rect.height, 0.1, 1000);
_renderer = new THREE.WebGLRenderer({ canvas: canvas, });
_renderer.setSize(rect.width, rect.height);
_renderer.setAnimationLoop(animate);
const geometry = new THREE.BoxGeometry(0.4, 0.4, 0.4);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
_scene.add(cube);
_camera.position.z = 5;
function animate()
{
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
let deltaTime = (60.0 / 1000.0);
if (_clock)
deltaTime = _clock.getDelta();
if (_skeletonMixer)
_skeletonMixer.update(deltaTime);
if (_renderer)
_renderer.render(_scene, _camera);
}
_scene.background = new THREE.Color("rgb(10, 80, 10)");
SetupInitialState(hasToLoadWithWebWorker);
}
}
let DestroyScene = () =>
{
if (_isSceneInstantiated)
{
_isSceneInstantiated = false;
_scene = undefined;
_camera = undefined;
_renderer = undefined;
let canvas = document.getElementById('canvas-container');
canvas.classList.add('hidden');
}
}
let SetupInitialState = (hasToLoadWithWebWorker = true) =>
{
_itemsContainer = new THREE.Group();
_scene.add(_itemsContainer);
_itemsContainer.position.set(0, -1, 0);
InstantiateLights();
let onFinishCallback = () =>
{
let blueMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff });
let redMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
InstantiateDynamicBodyPart('default-male-body-01-model-01.glb', blueMaterial, hasToLoadWithWebWorker);
InstantiateDynamicBodyPart('default-male-shirt-01-model-01.glb', redMaterial, hasToLoadWithWebWorker);
}
InstantiateBaseSkeleton(onFinishCallback, hasToLoadWithWebWorker)
}
let InstantiateLights = () =>
{
const mainUpLight = new THREE.DirectionalLight(0xffffff, 2);
const secondaryUpLight = new THREE.DirectionalLight(0xffffff, 1);
mainUpLight.position.set(20, 30, 10);
mainUpLight.target.position.set(0, 0, 0);
mainUpLight.castShadow = true;
secondaryUpLight.position.set(-20, 30, -10);
secondaryUpLight.target.position.set(0, 0, 0);
secondaryUpLight.castShadow = true;
const reverseLight1 = new THREE.DirectionalLight(0xffffff, 1);
const reverseLight2 = new THREE.DirectionalLight(0xffffff, 1);
reverseLight1.position.set(20, -30, -10);
reverseLight1.target.position.set(0, 0, 0);
reverseLight1.castShadow = false;
reverseLight2.position.set(-20, -30, 10);
reverseLight2.target.position.set(0, 0, 0);
reverseLight2.castShadow = false;
const ambientLight = new THREE.AmbientLight(0xaaaaaa); // soft white light
_scene.add(ambientLight);
_scene.add(mainUpLight);
}
let InstantiateBaseSkeleton = async (onFinishCallback, hasToLoadWithWebWorker = true) =>
{
let onFinishCallback2 = (baseModel) =>
{
if (baseModel)
{
_itemsContainer.add(baseModel);
let skeletonHelper = new THREE.SkeletonHelper(_scene);
_scene.add(skeletonHelper)
let baseBones = [];
baseModel.traverse(
(object) =>
{
switch (object.type)
{
case 'Bone':
baseBones.push(object);
break;
}
});
_baseSkeleton = new THREE.Skeleton(baseBones);
let charAnimationGroup = new THREE.AnimationObjectGroup(baseModel);
_skeletonMixer = new THREE.AnimationMixer(charAnimationGroup);
if (baseModel.animations.length > 0)
{
let currentAnimation = _skeletonMixer.clipAction(baseModel.animations[1]);
currentAnimation.play();
}
let finalScale = 1;
let finalPositionX = 0;
let finalPositionY = 0;
baseModel.scale.set(finalScale, finalScale, finalScale);
baseModel.position.set(finalPositionX, 0, finalPositionY);
}
onFinishCallback();
};
let baseModel = await LoadObject3D('default-skeleton-01.glb', onFinishCallback2, hasToLoadWithWebWorker);
}
let InstantiateDynamicBodyPart = (
modelFileName,
baseMaterial,
hasToLoadWithWebWorker = true) =>
{
let onFinishCallback = (baseModel) =>
{
if (baseModel)
{
let skinnedMeshesList = [];
baseModel.traverse(
(object) =>
{
switch (object.type)
{
case 'SkinnedMesh':
skinnedMeshesList.push(object);
break;
}
});
InjectMaterial(baseModel, baseMaterial);
skinnedMeshesList.map(
(skinnedMesh) =>
{
_itemsContainer.add(skinnedMesh);
skinnedMesh.skeleton = _baseSkeleton;
});
}
}
LoadObject3D(modelFileName, onFinishCallback, hasToLoadWithWebWorker);
}
let InjectMaterial = (baseModel, material) =>
{
if (baseModel)
{
baseModel.traverse(
(object) =>
{
if (object.isMesh)
object.material = material;
});
}
}
let GetRandomId = (length) =>
{
let result = '';
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let counter = 0;
while (counter < length)
{
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}
let LoadObject3D = (assetSubPath, onFinishCallback, hasToLoadWithWebWorker = true) =>
{
let value = undefined;
let assetsRootPath = 'https://y87vj9.csb.app/public/';
let assetPath = assetsRootPath + assetSubPath;
if (!hasToLoadWithWebWorker)
{
if (!value)
{
console.log('[default loading] > loading model... | assetPath =', assetPath);
let onSuccessCallback =
(modelGLTF) =>
{
console.log('[default loading] >>> model loaded! | assetPath =', assetPath);
let moodelObject3D = SkeletonUtils.clone(modelGLTF.scene.clone());
moodelObject3D.animations = modelGLTF.animations;
onFinishCallback(moodelObject3D);
};
_glbLoader.load(assetPath, onSuccessCallback);
};
}
else
{
if (typeof Worker !== 'undefined')
{
let requestId = GetRandomId(16);
_webworkersCallbacksById[requestId] = onFinishCallback;
let webworkerRequest =
{
RequestId: requestId,
ProcessName: 'LoadModel',
Params: [assetPath],
}
_worker.postMessage(JSON.stringify(webworkerRequest));
}
else
{
// Web workers are not supported in this environment.
// You should add a fallback so that your program still executes correctly.
console.error('$$q> AssetsService.GetAsset | Web workers are not supported in this environment!');
}
}
}
// region Worker
// state
let _webworkersCallbacksById = {};
let HandleWebworkerMessage = (data) =>
{
// let webworkerResponse = JSON.parse(data);
let webworkerResponse = JSON.parse(data.data);
let requestId = webworkerResponse.RequestId;
let baseModel = undefined;
if (webworkerResponse.RawResponse)
{
let object3DJSON = webworkerResponse.RawResponse;
const loader = new THREE.ObjectLoader();
baseModel = loader.parse(object3DJSON);
}
else
console.error('$> webworkerResponse.RawResponse is undefined! ');
let isCallbackRegistered = requestId in _webworkersCallbacksById;
if (isCallbackRegistered)
{
let callback = _webworkersCallbacksById[requestId];
callback(baseModel);
delete _webworkersCallbacksById[requestId];
}
else
console.error('$> Unexpected requestId! requestId = ', requestId);
}
const _worker = new Worker("/model-loading-worker.js", { type: 'module' });
_worker.onmessage = HandleWebworkerMessage;
//end region Worker
let DefaultIntantiatingButton = () =>
{
DestroyScene();
InstantiateScene(false);
}
let WebWorkerIntantiatingButton = () =>
{
DestroyScene();
InstantiateScene(true);
}
let SetupButtons = () =>
{
let defaultIntantiatingButton = document.querySelector('#default-intantiating-button');
let webWorkerIntantiatingButton = document.querySelector('#web-worker-intantiating-button');
defaultIntantiatingButton.onclick = DefaultIntantiatingButton;
webWorkerIntantiatingButton.onclick = WebWorkerIntantiatingButton;
}
SetupButtons();
DefaultIntantiatingButton();
< /code>
model-loading-worker.js[/code]
import { GLTFLoader } from 'https://esm.sh/
[email protected]/examples/jsm/loaders/GLTFLoader.js';
// dependencies
let _glbLoader = new GLTFLoader();
let HandleMessage = async (request) =>
{
let response = {};
response.RequestId = request.RequestId;
switch (request.ProcessName)
{
case 'LoadModel':
{
let assetPath = request.Params[0];
console.log('[web worker] > loading model... | assetPath =', assetPath);
let onSuccessCallback =
(modelGLTF) =>
{
console.log('[web worker] >>> model loaded! | assetPath =', assetPath);
let finalAssetValue = modelGLTF.scene;
finalAssetValue.animations = modelGLTF.animations;
let finalAssetValueObject3DJSON = finalAssetValue.toJSON()
response.RawResponse = finalAssetValueObject3DJSON;
}
await _glbLoader.loadAsync(assetPath).then(onSuccessCallback);
break
}
default:
{
console.error('Unexpected request.ProcessName! | request.ProcessName =', request.ProcessName);
break;
}
}
return response;
}
addEventListener(
'message',
async ({ data }) =>
{
let webworkerRequest = JSON.parse(data, function (key, value)
{
return value;
});
let response = await HandleMessage(webworkerRequest);
data = JSON.stringify(response);
postMessage(data);
});
< /code>
I'll leave a link below to a practical example on codesandbox with reproduction (change between loading modes by changing the state of the checkbox in the lower right corner and then press "Instatiate Scene". The buttons have a yellow hover to test with the mouse if the page is really stuck during loading, as well as the cube animation)
https://codesandbox.io/p/sandbox/y87vj9
Any help or guidance will be very welcome, I've been trying to find a way to improve the optimization of this page for about 2 weeks, and this was the only thing that had a good result in performance, however, it's not working.