Skip to content

Commit

Permalink
update document example code
Browse files Browse the repository at this point in the history
  • Loading branch information
noname0310 committed Sep 4, 2024
1 parent 42528e6 commit b2bd942
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 183 deletions.
17 changes: 9 additions & 8 deletions docs/docs/0-quick-start/1-load-mmd-model/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,23 @@ For load pmx model, we need to import side effects.
import "babylon-mmd/esm/Loader/pmxLoader";
```

Then, load the model using the `SceneLoader`.
Then, load the model using the `loadAssetContainerAsync`.

```typescript title="src/sceneBuilder.ts"
const mmdMesh = await SceneLoader.ImportMeshAsync("", "res/YYB Hatsune Miku_10th/", "YYB Hatsune Miku_10th_v1.02.pmx", scene)
.then((result) => result.meshes[0] as MmdMesh);
const mmdMesh = await loadAssetContainerAsync("res/YYB Hatsune Miku_10th/YYB Hatsune Miku_10th_v1.02.pmx", scene)
.then((result) => {
result.addAllToScene();
return result.meshes[0] as MmdMesh;
});
for (const mesh of mmdMesh.metadata.meshes) mesh.receiveShadows = true;
shadowGenerator.addShadowCaster(mmdMesh);
```

- `SceneLoader.ImportMeshAsync` - Load the model using the `SceneLoader` (All other loading methods are supported, but this example uses `ImportMeshAsync`).
- `""` - this parameter is not used in PMX loading.
- `"res/YYB Hatsune Miku_10th/"` - the path to the model file.
- `"YYB Hatsune Miku_10th_v1.02.pmx"` - If you pass a File object, you can load the model from the File object.
- `loadAssetContainerAsync` - Load the model (All other loading methods like `SceneLoader.ImportMeshAsync` are also supported).
- `"res/YYB Hatsune Miku_10th/YYB Hatsune Miku_10th_v1.02.pmx"` - the path to the model file. If you pass a File object, you can load the model from the File object.
- `scene` - the scene to load the model into.

- An importMeshAsync call in pmx file guarantees that result.meshes length is always greater than 0 and result.meshes[0] is always a root mesh which type is MmdMesh.
- An loadAssetContainerAsync call in pmx file guarantees that result.meshes length is always greater than 0 and result.meshes[0] is always a root mesh which type is MmdMesh.

- Below is the shadow setting I won't explain in detail.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ SdefInjector.OverrideEngineCreateEffect(engine); // Force all shaders to support
or

```typescript title="src/sceneBuilder.ts"
const pmxLoader = SceneLoader.GetPluginForExtension(".pmx") as PmxLoader;
pmxLoader.useSdef = false; // Disable SDEF
const assetContainer = await loadAssetContainerAsync("res/your_model.pmx", scene, {
pluginOptions: { // you can pass options to the loader using pluginOptions
mmdmodel: {
useSdef: false // Disable SDEF
}
}
});
```

Result:
Expand Down Expand Up @@ -54,25 +59,37 @@ The proper `transparencyMode` of the mesh is determined at load time by a specif
To fix this, you can disable the optimization settings. The code looks like this:

```typescript
const pmxLoader = SceneLoader.GetPluginForExtension(".pmx") as PmxLoader;
const materialBuilder = pmxLoader.materialBuilder as MmdStandardMaterialBuilder;
const materialBuilder = new MmdStandardMaterialBuilder();
materialBuilder.renderMethod = MmdStandardMaterialRenderMethod.DepthWriteAlphaBlending;

const assetContainer = await loadAssetContainerAsync("res/your_model.pmx", scene, {
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder // Override the material builder
}
}
});
```

The other approach is to choose the most appropriate `transparencyMode` possible without using `forceDepthWrite`. This approach should be compatible with most post-processing and shaders.

```typescript
const pmxLoader = SceneLoader.GetPluginForExtension(".pmx") as PmxLoader;
const materialBuilder = pmxLoader.materialBuilder as MmdStandardMaterialBuilder;
const materialBuilder = new MmdStandardMaterialBuilder();
materialBuilder.renderMethod = MmdStandardMaterialRenderMethod.AlphaEvaluation;

const assetContainer = await loadAssetContainerAsync("res/your_model.pmx", scene, {
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder // Override the material builder
}
}
});
```

The `DepthWriteAlphaBlendingWithEvaluation` and `AlphaEvaluation` methods both add some delays to the execution of the algorithm. To improve this, you can force off the optimization that automatically determines the `transparencyMode` and set the `transparencyMode` manually.

```typescript
const pmxLoader = SceneLoader.GetPluginForExtension(".pmx") as PmxLoader;
pmxLoader.useSdef = false;
const materialBuilder = pmxLoader.materialBuilder as MmdStandardMaterialBuilder;
const materialBuilder = new MmdStandardMaterialBuilder();
materialBuilder.forceDisableAlphaEvaluation = true;
const alphaBlendMaterials = ["face02", "Facial02", "HL", "Hairshadow", "q302"];
const alphaTestMaterials = ["q301"];
Expand All @@ -84,6 +101,14 @@ materialBuilder.afterBuildSingleMaterial = (material): void => {
material.useAlphaFromDiffuseTexture = true;
material.diffuseTexture!.hasAlpha = true;
};

const assetContainer = await loadAssetContainerAsync("res/your_model.pmx", scene, {
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder // Override the material builder
}
}
});
```

- `forceDisableAlphaEvaluation` - If true, the optimization that automatically determines the `transparencyMode` is disabled.
Expand All @@ -96,10 +121,16 @@ Outline rendering might looks weird with some post-processes or shaders.
In this case, you should consider turning off outline rendering partially or disabling it in loaders.

```typescript title="src/sceneBuilder.ts"
const pmxLoader = SceneLoader.GetPluginForExtension(".pmx") as PmxLoader;
const materialBuilder = pmxLoader.materialBuilder as MmdStandardMaterialBuilder;

const materialBuilder = new MmdStandardMaterialBuilder();
materialBuilder.loadOutlineRenderingProperties = () => { /* do nothing */ };

const assetContainer = await loadAssetContainerAsync("res/your_model.pmx", scene, {
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder // Override the material builder
}
}
});
```

- `loadOutlineRenderingProperties` - This callback is called when loading the outline rendering properties. You can override this to customize the outline rendering properties.
Expand All @@ -114,21 +145,18 @@ So if you want to make any changes to the loaded asset, check the loader option

```typescript title="src/sceneBuilder.ts"
import type { Engine } from "@babylonjs/core";
import { Color3, DirectionalLight, HavokPlugin, HemisphericLight, Material, MeshBuilder, Scene, SceneLoader, ShadowGenerator, Vector3 } from "@babylonjs/core";
import { Color3, DirectionalLight, HavokPlugin, HemisphericLight, Material, MeshBuilder, Scene, ShadowGenerator, Vector3 } from "@babylonjs/core";
import HavokPhysics from "@babylonjs/havok";
import type { MmdStandardMaterialBuilder } from "babylon-mmd";
import { MmdCamera, MmdMesh, MmdPhysics, MmdPlayerControl, MmdRuntime, PmxLoader, SdefInjector, StreamAudioPlayer, VmdLoader } from "babylon-mmd";
import { MmdCamera, MmdMesh, MmdPhysics, MmdPlayerControl, MmdRuntime, MmdStandardMaterialBuilder, PmxLoader, SdefInjector, StreamAudioPlayer, VmdLoader } from "babylon-mmd";

import type { ISceneBuilder } from "./baseRuntime";

export class SceneBuilder implements ISceneBuilder {
public async build(_canvas: HTMLCanvasElement, engine: Engine): Promise<Scene> {
SdefInjector.OverrideEngineCreateEffect(engine);
SceneLoader.RegisterPlugin(new PmxLoader());

// fix material alpha mode
const pmxLoader = SceneLoader.GetPluginForExtension(".pmx") as PmxLoader;
const materialBuilder = pmxLoader.materialBuilder as MmdStandardMaterialBuilder;
const materialBuilder = new MmdStandardMaterialBuilder();
materialBuilder.useAlphaEvaluation = false;
const alphaBlendMaterials = ["face02", "Facial02", "HL", "Hairshadow", "q302"];
const alphaTestMaterials = ["q301"];
Expand Down Expand Up @@ -165,8 +193,16 @@ export class SceneBuilder implements ISceneBuilder {
shadowGenerator.addShadowCaster(ground);

// load mmd model
const mmdMesh = await SceneLoader.ImportMeshAsync("", "res/YYB Hatsune Miku_10th/", "YYB Hatsune Miku_10th_v1.02.pmx", scene)
.then((result) => result.meshes[0] as MmdMesh);
const mmdMesh = await loadAssetContainerAsync("res/YYB Hatsune Miku_10th/YYB Hatsune Miku_10th_v1.02.pmx", scene, {
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder
}
}
}).then((result) => {
result.addAllToScene();
return result.meshes[0] as MmdMesh;
});
for (const mesh of mmdMesh.metadata.meshes) mesh.receiveShadows = true;
shadowGenerator.addShadowCaster(mmdMesh);

Expand Down
143 changes: 76 additions & 67 deletions docs/docs/1-deep-usage/2-load-bpmx-model/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,52 +21,51 @@ import "@babylonjs/core/Materials/Textures/Loaders/tgaTextureLoader";
```
:::

Then, load the model using the `SceneLoader`.
Then, load the model using the `loadAssetContainerAsync`.

```typescript title="src/sceneBuilder.ts"
const bpmxLoader = SceneLoader.GetPluginForExtension(".bpmx") as BpmxLoader;
bpmxLoader.loggingEnabled = true;
const materialBuilder = bpmxLoader.materialBuilder as MmdStandardMaterialBuilder;
const materialBuilder = new MmdStandardMaterialBuilder();
materialBuilder.loadOutlineRenderingProperties = (): void => { /* do nothing */ };

engine.displayLoadingUI();

bpmxLoader.boundingBoxMargin = 60;
const modelMesh = await SceneLoader.ImportMeshAsync(
undefined,
"res/",
"YYB Piano dress Miku.bpmx",
const modelMesh = await loadAssetContainerAsync(
"res/YYB Piano dress Miku.bpmx",
scene,
(event) => engine.loadingUIText = `Loading model... ${event.loaded}/${event.total} (${Math.floor(event.loaded * 100 / event.total)}%)`
).then((result) => result.meshes[0] as MmdMesh);
{
onProgress: (event) => engine.loadingUIText = `Loading model... ${event.loaded}/${event.total} (${Math.floor(event.loaded * 100 / event.total)}%)`,
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder,
boundingBoxMargin: 60,
loggingEnabled: true
}
}
}
).then((result) => {
result.addAllToScene();
return result.meshes[0] as MmdMesh;
});

modelMesh;

scene.onAfterRenderObservable.addOnce(() => engine.hideLoadingUI());
```

- `SceneLoader.GetPluginForExtension(".bpmx")` - Get the `BpmxLoader` from the `SceneLoader`.

- `bpmxLoader.loggingEnabled = true;` - Enable logging for better debugging.
- `materialBuilder.loadOutlineRenderingProperties = (): void => { /* do nothing */ };` - Override the method to disable outline rendering. (This is just a setting for the goal in this example.)

- `bpmxLoader.materialBuilder` - In this case we get the `MmdStandardMaterialBuilder` from the `BpmxLoader`. If you implement your own material builder, you can pass it to the loader.
- `materialBuilder: materialBuilder` - In this case we instantiate and pass the `MmdStandardMaterialBuilder`, but you can also implement your own material builder and pass it.

- `materialBuilder.loadOutlineRenderingProperties = (): void => { /* do nothing */ };` - Override the method to disable outline rendering. (This is just a setting for the goal in this example.)
- `boundingBoxMargin: 60` - Set the bounding box margin. (The default value is 10.)

- `bpmxLoader.boundingBoxMargin = 60;` - Set the bounding box margin. (The default value is 10.)
- `loggingEnabled: true` - Enable logging for better debugging. (The default value is false.)

:::info
Basically, dance motion can cause curring problem because it moves mesh a lot from rest pose.
Basically, dance motion can cause curring problem because it moves mesh far from the bounding box.

And the motion that we're going to use in this next example is especially problematic, so we need to make the bounding box bigger.
:::

:::tip
If you change the settings of the `BpmxLoader` before calling `ImportMeshAsync`, the settings will be applied to the loaded model.

The reason for using this method is that `ImportMeshAsync` does not have a parameter to pass the load options to the loader.
:::

![result1](image.png)

:::info
Expand All @@ -79,26 +78,34 @@ You'll see this error message. These errors are often displayed when the 3D mode

For models such as Stage, it is usually static. Importing these models into a static mesh can save performance.

If you don't need to get the mesh object, you can use `appendSceneAsync` instead of `loadAssetContainerAsync` which is more simple.

```typescript title="src/sceneBuilder.ts"
bpmxLoader.boundingBoxMargin = 0;
bpmxLoader.buildSkeleton = false;
bpmxLoader.buildMorph = false;
// promises.push(
await SceneLoader.ImportMeshAsync(
undefined,
"res/",
"ガラス片ドームB.bpmx",
await appendSceneAsync(
"res/ガラス片ドームB.bpmx",
scene,
(event) => engine.loadingUIText = `Loading stage... ${event.loaded}/${event.total} (${Math.floor(event.loaded * 100 / event.total)}%)`
{
onProgress: (event) => engine.loadingUIText = `Loading stage... ${event.loaded}/${event.total} (${Math.floor(event.loaded * 100 / event.total)}%)`,
pluginOptions: {
mmdmodel: {
buildSkeleton: false,
buildMorph: false,
boundingBoxMargin: 0,
loggingEnabled: true
}
}
}
);
```

- `bpmxLoader.boundingBoxMargin = 0;` - Set the bounding box margin to 0. (because static mesh doesn't move)
- `bpmxLoader.buildSkeleton = false;` - Disable building skeleton.
- `bpmxLoader.buildMorph = false;` - Disable building morph.
- `buildSkeleton: false` - Disable building skeleton.

- `buildMorph: false` - Disable building morph.

- `boundingBoxMargin: 0` - Set the bounding box margin to 0. (because static mesh doesn't move)

:::info
Naturally, mmd models loaded with static mesh cannot be controlled by MMD Runtime.
Naturally, mmd models loaded as static mesh cannot be controlled by MMD Runtime.
:::

![result2](image-1.png)
Expand All @@ -108,9 +115,6 @@ Naturally, mmd models loaded with static mesh cannot be controlled by MMD Runtim
Now, we are awaiting the model and stage to load one by one, but we can load them simultaneously by using `Promise.all`.

```typescript title="src/sceneBuilder.ts"
const bpmxLoader = SceneLoader.GetPluginForExtension(".bpmx") as BpmxLoader;
bpmxLoader.loggingEnabled = true;

engine.displayLoadingUI();

let loadingTexts: string[] = [];
Expand All @@ -119,42 +123,47 @@ const updateLoadingText = (updateIndex: number, text: string): void => {
engine.loadingUIText = "<br/><br/><br/><br/>" + loadingTexts.join("<br/><br/>");
};

const promises: Promise<any>[] = [];
const [modelMesh, ] = await Promise.all([
SceneLoader.ImportMeshAsync(
"res/YYB Piano dress Miku.bpmx",
scene,
{
onProgress: (event) => updateLoadingText(0, `Loading model... ${event.loaded}/${event.total} (${Math.floor(event.loaded * 100 / event.total)}%)`),
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder,
boundingBoxMargin: 60,
loggingEnabled: true
}
}
}
).then((result) => result.meshes[0] as MmdMesh),
SceneLoader.ImportMeshAsync(
"res/ガラス片ドームB.bpmx",
scene,
{
onProgress: (event) => updateLoadingText(1, `Loading stage... ${event.loaded}/${event.total} (${Math.floor(event.loaded * 100 / event.total)}%)`),
pluginOptions: {
mmdmodel: {
materialBuilder: materialBuilder,
buildSkeleton: false,
buildMorph: false,
boundingBoxMargin: 0,
loggingEnabled: true
}
}
}
)
]);

bpmxLoader.boundingBoxMargin = 60;
promises.push(SceneLoader.ImportMeshAsync(
undefined,
"res/",
"YYB Piano dress Miku.bpmx",
scene,
(event) => updateLoadingText(0, `Loading model... ${event.loaded}/${event.total} (${Math.floor(event.loaded * 100 / event.total)}%)`)
).then((result) => result.meshes[0]));

bpmxLoader.boundingBoxMargin = 0;
bpmxLoader.buildSkeleton = false;
bpmxLoader.buildMorph = false;
promises.push(SceneLoader.ImportMeshAsync(
undefined,
"res/",
"ガラス片ドームB.bpmx",
scene,
(event) => updateLoadingText(1, `Loading stage... ${event.loaded}/${event.total} (${Math.floor(event.loaded * 100 / event.total)}%)`)
));

loadingTexts = new Array(promises.length).fill("");

const loadResults = await Promise.all(promises);
scene.onAfterRenderObservable.addOnce(() => engine.hideLoadingUI());

loadResults;
```

- Simply added `Promise.all` and the loading screen.

## Add Shadow to Model

```typescript title="src/sceneBuilder.ts"
const modelMesh = loadResults[0] as MmdMesh;
for (const mesh of modelMesh.metadata.meshes) mesh.receiveShadows = true;
shadowGenerator.addShadowCaster(modelMesh);
```
Expand Down
Loading

0 comments on commit b2bd942

Please sign in to comment.