diff --git a/packages/three-vrm-materials-mtoon/examples/feature-test.html b/packages/three-vrm-materials-mtoon/examples/feature-test.html index dd3b29f3e..ba7d447a6 100644 --- a/packages/three-vrm-materials-mtoon/examples/feature-test.html +++ b/packages/three-vrm-materials-mtoon/examples/feature-test.html @@ -270,11 +270,57 @@ }, + // outline world + { + map: textureUVGrid, + outlineWidthMode: 'worldCoordinates', + outlineWidthFactor: 0.05, + outlineColorFactor: 0x00ff00, + }, + + // outline world, masked, unlit + { + map: textureUVGrid, + outlineWidthMode: 'worldCoordinates', + outlineWidthFactor: 0.05, + outlineColorFactor: 0x00ff00, + outlineWidthMultiplyTexture: textureBinaryHalf, + outlineLightingMixFactor: 0.0, + }, + + // outline screen + { + map: textureUVGrid, + outlineWidthMode: 'screenCoordinates', + outlineWidthFactor: 0.05, + outlineColorFactor: 0xff0000, + }, + ].map( ( params, i ) => { const material = new MToonMaterial( params ); const mesh = new THREE.Mesh( geometrySphere, material ); + // if outline is enabled we need to duplicate the material and assign it to the mesh + if ( material.outlineWidthMode !== 'none' ) { + + // duplicate the material for outline use + const materialOutline = mesh.material.clone(); + materialOutline.isOutline = true; + materialOutline.side = THREE.BackSide; + + mesh.material = [ mesh.material, materialOutline ]; + + // make two geometry groups out of a same buffer + const geometry = mesh.geometry; // mesh.geometry is guaranteed to be a BufferGeometry in GLTFLoader + const primitiveVertices = geometry.index ? geometry.index.count : geometry.attributes.position.count / 3; + geometry.addGroup( 0, primitiveVertices, 0 ); + geometry.addGroup( 0, primitiveVertices, 1 ); + + } + + console.log( mesh.material ); + const x = ( i % 5 ) - 2.0; const y = Math.floor( i / 5 ) - 2.0; mesh.position.set( x, y, 0 ); @@ -309,7 +355,20 @@ for ( const mesh of meshes ) { - mesh.material.update( delta ); + // mesh.material can be either array or single material + if ( Array.isArray( mesh.material ) ) { + + for ( const material of mesh.material ) { + + material.update( delta ); + + } + + } else { + + mesh.material.update( delta ); + + } } diff --git a/packages/three-vrm-materials-mtoon/examples/webgpu-feature-test.html b/packages/three-vrm-materials-mtoon/examples/webgpu-feature-test.html index f8c540055..e884a8b91 100644 --- a/packages/three-vrm-materials-mtoon/examples/webgpu-feature-test.html +++ b/packages/three-vrm-materials-mtoon/examples/webgpu-feature-test.html @@ -273,11 +273,55 @@ }, + // outline world + { + map: textureUVGrid, + outlineWidthMode: 'worldCoordinates', + outlineWidthFactor: 0.05, + outlineColorFactor: 0x00ff00, + }, + + // outline world, masked, unlit + { + map: textureUVGrid, + outlineWidthMode: 'worldCoordinates', + outlineWidthFactor: 0.05, + outlineColorFactor: 0x00ff00, + outlineWidthMultiplyTexture: textureBinaryHalf, + outlineLightingMixFactor: 0.0, + }, + + // outline screen + { + map: textureUVGrid, + outlineWidthMode: 'screenCoordinates', + outlineWidthFactor: 0.05, + outlineColorFactor: 0xff0000, + }, + ].map( ( params, i ) => { const material = new MToonNodeMaterial( params ); const mesh = new THREE.Mesh( geometrySphere, material ); + // if outline is enabled we need to duplicate the material and assign it to the mesh + if ( params.outlineWidthMode !== 'none' ) { + + // duplicate the material for outline use + const materialOutline = mesh.material.clone(); + materialOutline.isOutline = true; + materialOutline.side = THREE.BackSide; + + mesh.material = [ mesh.material, materialOutline ]; + + // make two geometry groups out of a same buffer + const geometry = mesh.geometry; // mesh.geometry is guaranteed to be a BufferGeometry in GLTFLoader + const primitiveVertices = geometry.index ? geometry.index.count : geometry.attributes.position.count / 3; + geometry.addGroup( 0, primitiveVertices, 0 ); + geometry.addGroup( 0, primitiveVertices, 1 ); + + } + const x = ( i % 5 ) - 2.0; const y = Math.floor( i / 5 ) - 2.0; mesh.position.set( x, y, 0 ); @@ -312,7 +356,20 @@ for ( const mesh of meshes ) { - mesh.material.update( delta ); + // mesh.material can be either array or single material + if ( Array.isArray( mesh.material ) ) { + + for ( const material of mesh.material ) { + + material.update( delta ); + + } + + } else { + + mesh.material.update( delta ); + + } } diff --git a/packages/three-vrm-materials-mtoon/src/MToonMaterial.ts b/packages/three-vrm-materials-mtoon/src/MToonMaterial.ts index 5ec1fb16f..76774ac88 100644 --- a/packages/three-vrm-materials-mtoon/src/MToonMaterial.ts +++ b/packages/three-vrm-materials-mtoon/src/MToonMaterial.ts @@ -606,7 +606,6 @@ export class MToonMaterial extends THREE.ShaderMaterial { DEBUG_NORMAL: this._debugMode === 'normal', DEBUG_LITSHADERATE: this._debugMode === 'litShadeRate', DEBUG_UV: this._debugMode === 'uv', - OUTLINE_WIDTH_WORLD: this._isOutline && this._outlineWidthMode === MToonMaterialOutlineWidthMode.WorldCoordinates, OUTLINE_WIDTH_SCREEN: this._isOutline && this._outlineWidthMode === MToonMaterialOutlineWidthMode.ScreenCoordinates, }; diff --git a/packages/three-vrm-materials-mtoon/src/shaders/mtoon.vert b/packages/three-vrm-materials-mtoon/src/shaders/mtoon.vert index 72664cfa2..f1815e97b 100644 --- a/packages/three-vrm-materials-mtoon/src/shaders/mtoon.vert +++ b/packages/three-vrm-materials-mtoon/src/shaders/mtoon.vert @@ -92,27 +92,22 @@ void main() { vViewPosition = - mvPosition.xyz; - float outlineTex = 1.0; - #ifdef OUTLINE + float worldNormalLength = length( transformedNormal ); + vec3 outlineOffset = outlineWidthFactor * worldNormalLength * objectNormal; + #ifdef USE_OUTLINEWIDTHMULTIPLYTEXTURE vec2 outlineWidthMultiplyTextureUv = ( outlineWidthMultiplyTextureUvTransform * vec3( vUv, 1 ) ).xy; - outlineTex = texture2D( outlineWidthMultiplyTexture, outlineWidthMultiplyTextureUv ).g; - #endif - - #ifdef OUTLINE_WIDTH_WORLD - float worldNormalLength = length( transformedNormal ); - vec3 outlineOffset = outlineWidthFactor * outlineTex * worldNormalLength * objectNormal; - gl_Position = projectionMatrix * modelViewMatrix * vec4( outlineOffset + transformed, 1.0 ); + float outlineTex = texture2D( outlineWidthMultiplyTexture, outlineWidthMultiplyTextureUv ).g; + outlineOffset *= outlineTex; #endif #ifdef OUTLINE_WIDTH_SCREEN - vec3 clipNormal = ( projectionMatrix * modelViewMatrix * vec4( objectNormal, 0.0 ) ).xyz; - vec2 projectedNormal = normalize( clipNormal.xy ); - projectedNormal.x *= projectionMatrix[ 0 ].x / projectionMatrix[ 1 ].y; - gl_Position.xy += 2.0 * outlineWidthFactor * outlineTex * projectedNormal.xy; + outlineOffset *= vViewPosition.z / projectionMatrix[ 1 ].y; #endif + gl_Position = projectionMatrix * modelViewMatrix * vec4( outlineOffset + transformed, 1.0 ); + gl_Position.z += 1E-6 * gl_Position.w; // anti-artifact magic #endif