Skip to content

Commit

Permalink
Merge pull request virtual-cast#110 from horyu/fix-outline
Browse files Browse the repository at this point in the history
fix: use original method of rendering outlines
  • Loading branch information
il-masaru-yamagishi authored Sep 21, 2022
2 parents 4b50382 + 49566a6 commit 0ed9757
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 78 deletions.
85 changes: 10 additions & 75 deletions src/mtoon-outline-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { SceneComponentConstants } from '@babylonjs/core/sceneComponent';
import type { Nullable } from '@babylonjs/core/types';
import type { Matrix } from '@babylonjs/core/Maths/math';
import type { MToonMaterial } from './mtoon-material';
import { Constants } from '@babylonjs/core/Engines/constants';
import type { Material } from '@babylonjs/core/Materials/material';

// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -18,11 +17,6 @@ const BASE_NAME = 'MToonOutline';
* @see OutlineRenderer
*/
export class MToonOutlineRenderer implements ISceneComponent {
/**
* Stencil value used to avoid outline being seen within the mesh when the mesh is transparent
*/
private static _StencilReference = 0x04;

// eslint-disable-next-line @typescript-eslint/naming-convention
public static rendererId = 0;

Expand All @@ -31,18 +25,7 @@ export class MToonOutlineRenderer implements ISceneComponent {
*/
public readonly name: string;

/**
* Defines a zOffset default Factor to prevent zFighting between the overlay and the mesh.
*/
public zOffset = 1;

/**
* Defines a zOffset default Unit to prevent zFighting between the overlay and the mesh.
*/
public zOffsetUnits = 4; // 4 to account for projection a bit by default

private _engine: Engine;
private _savedDepthWrite = false;
private _passIdForDrawWrapper: number[];

/**
Expand All @@ -54,7 +37,7 @@ export class MToonOutlineRenderer implements ISceneComponent {
this.scene._addComponent(this);
this._engine = this.scene.getEngine();
this._passIdForDrawWrapper = [];
for (let i = 0; i < 4; ++i) {
for (let i = 0; i < 1; ++i) {
this._passIdForDrawWrapper[i] = this._engine.createRenderPassId(`Outline Renderer (${i})`);
}
}
Expand All @@ -64,7 +47,6 @@ export class MToonOutlineRenderer implements ISceneComponent {
* シーン描画前後にレンダリング処理を登録する
*/
public register(): void {
this.scene._beforeRenderingMeshStage.registerStep(SceneComponentConstants.STEP_BEFORERENDERINGMESH_OUTLINE, this, this._beforeRenderingMesh);
this.scene._afterRenderingMeshStage.registerStep(SceneComponentConstants.STEP_AFTERRENDERINGMESH_OUTLINE, this, this._afterRenderingMesh);
}

Expand All @@ -88,11 +70,10 @@ export class MToonOutlineRenderer implements ISceneComponent {
* Renders the outline in the canvas.
* @param subMesh Defines the sumesh to render
* @param batch Defines the batch of meshes in case of instances
* @param useOverlay Defines if the rendering is for the overlay or the outline
* @param renderPassId Render pass id to use to render the mesh
*/
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars
private render(subMesh: SubMesh, batch: _InstancesBatch, useOverlay: boolean = false, renderPassId?: number): void {
private render(subMesh: SubMesh, batch: _InstancesBatch, renderPassId?: number): void {
renderPassId = renderPassId ?? this._passIdForDrawWrapper[0];
const scene = this.scene;
const effect = subMesh.effect;
Expand All @@ -119,13 +100,13 @@ export class MToonOutlineRenderer implements ISceneComponent {
}

this.material.applyOutlineCullMode();
this.material.enableOutlineRender();
this._engine.enableEffect(drawWrapper);
if (!this.isHardwareInstancedRendering(subMesh, batch)) {
renderingMesh._bind(subMesh, effect, this.material.fillMode);
}

this._engine.setZOffset(-this.zOffset);
this._engine.setZOffsetUnits(-this.zOffsetUnits);
this.material._preBind(effect);

renderingMesh._processRendering(
effectiveMesh,
Expand All @@ -136,57 +117,14 @@ export class MToonOutlineRenderer implements ISceneComponent {
this.isHardwareInstancedRendering(subMesh, batch),
(isInstance: boolean, world: Matrix, effectiveMaterial?: Material) => {
if (effectiveMaterial) {
const m = effectiveMaterial as MToonMaterial;
m.enableOutlineRender();
m.bindForSubMesh(world, effectiveMesh as Mesh, subMesh);
m.disaableOutlineRender();
effectiveMaterial.bindForSubMesh(world, effectiveMesh as Mesh, subMesh);
}
},
this.material
);

this._engine.setZOffset(0);
this._engine.setZOffsetUnits(0);
this.material.restoreOutlineCullMode();
}

/**
* このメッシュを描画する前に実行されるコールバック
*/
private _beforeRenderingMesh(mesh: Mesh, subMesh: SubMesh, batch: _InstancesBatch): void {
this._savedDepthWrite = this._engine.getDepthWrite();

if (!this.willRender(subMesh)) {
return;
}
const material = subMesh.getMaterial() as MToonMaterial;
if (material.needAlphaBlendingForMesh(mesh)) {
this._engine.cacheStencilState();
// Draw only to stencil buffer for the original mesh
// The resulting stencil buffer will be used so the outline is not visible inside the mesh when the mesh is transparent
this._engine.setDepthWrite(false);
this._engine.setColorWrite(false);
this._engine.setStencilBuffer(true);
this._engine.setStencilOperationPass(Constants.REPLACE);
this._engine.setStencilFunction(Constants.ALWAYS);
this._engine.setStencilMask(MToonOutlineRenderer._StencilReference);
this._engine.setStencilFunctionReference(MToonOutlineRenderer._StencilReference);
this._engine.stencilStateComposer.useStencilGlobalOnly = true;
this.render(subMesh, batch, /* This sets offset to 0 */ true, this._passIdForDrawWrapper[1]);

this._engine.setColorWrite(true);
this._engine.setStencilFunction(Constants.NOTEQUAL);
}

// Draw the outline using the above stencil if needed to avoid drawing within the mesh
this._engine.setDepthWrite(false);
this.render(subMesh, batch, false, this._passIdForDrawWrapper[0]);
this._engine.setDepthWrite(this._savedDepthWrite);

if (material && material.needAlphaBlendingForMesh(mesh)) {
this._engine.stencilStateComposer.useStencilGlobalOnly = false;
this._engine.restoreStencilState();
}
this.material.disaableOutlineRender();
}

/**
Expand All @@ -197,13 +135,10 @@ export class MToonOutlineRenderer implements ISceneComponent {
return;
}

if (this._savedDepthWrite) {
// 深度アリで再度書き込む
this._engine.setDepthWrite(true);
this._engine.setColorWrite(false);
this.render(subMesh, batch, false, this._passIdForDrawWrapper[2]);
this._engine.setColorWrite(true);
}
const cullBackFaces = this._engine.cullBackFaces;
this._engine.cullBackFaces = false;
this.render(subMesh, batch, this._passIdForDrawWrapper[0]);
this._engine.cullBackFaces = cullBackFaces;
}

/**
Expand Down
3 changes: 0 additions & 3 deletions src/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { DirectionalLight } from '@babylonjs/core/Lights/directionalLight';
import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight';
import { PointLight } from '@babylonjs/core/Lights/pointLight';
import { ShadowGenerator } from '@babylonjs/core/Lights/Shadows/shadowGenerator';
import { Material } from '@babylonjs/core/Materials/material';
import { Texture } from '@babylonjs/core/Materials/Textures/texture';
import { Color3, Vector3 } from '@babylonjs/core/Maths/math';
import { CreateSphere } from '@babylonjs/core/Meshes/Builders/sphereBuilder';
Expand Down Expand Up @@ -178,9 +177,7 @@ async function main() {

mtoonMaterials.forEach((mat, index) => {
// MToonMaterial は glTF(右手座標) を考慮しているため、 CullMode をデフォルトから反転させる
mat.sideOrientation = Material.CounterClockWiseSideOrientation;
mat.cullMode = 1;
mat.outlineCullMode = 2;
const sphere = CreateSphere(`${mat.name}_Sphere`, {}, scene);
sphere.position = new Vector3(-1.2 * index, 1.2, 0);
sphere.receiveShadows = true;
Expand Down

0 comments on commit 0ed9757

Please sign in to comment.