未验证 提交 5d1d3064 编写于 作者: B bill42362 提交者: GitHub

MMDLoader: Implement MMDToonMaterial. (#21922)

* Add prototype of MMDToonMaterial.

* Try to use ShaderMaterial to simulate MeshToonMaterial.

* Done simulate MeshToonMaterial with ShaderMaterial.

* Clean up MMDToonMaterial.

* Add envMap uniforms.

* Enable merge gradient map with phong material but got warning.

* Enable specular and shininess in MMDToonMaterial.

* Done combining Phong/Toon/Matcap materials.

* Pre PR chores.

* fix typos.

* Set color to diffuse uniform.

* Update e2e test screenshots.

* Set .shininess to original and cut half irradiance.

* Remove dotNL from irradiance and update screenshots.

* Fix with PR comments.

* Modify maps file name storing position.

* Clean extra code.

* Update comment and use setter/getters to improve compatibility.

* Simplify defineProterties with defineProterty.

* Improve exposing property pattern.

* Simplify defineProterties with defineProterty.
上级 b2f6ec86
......@@ -921,7 +921,7 @@
* @param {BufferGeometry} geometry - some properties are dependend on geometry
* @param {function} onProgress
* @param {function} onError
* @return {Array<MeshToonMaterial>}
* @return {Array<MMDToonMaterial>}
*/
......@@ -937,23 +937,26 @@
const material = data.materials[ i ];
const params = {
userData: {}
userData: {
MMD: {}
}
};
if ( material.name !== undefined ) params.name = material.name;
/*
* THREE.Color
*
* MMD THREE.MeshToonMaterial
* diffuse - color
* MMD MMDToonMaterial
* ambient - emissive * a
* (a = 1.0 without map texture or 0.2 with map texture)
*
* THREE.MeshToonMaterial doesn't have ambient. Set it to emissive instead.
* MMDToonMaterial doesn't have ambient. Set it to emissive instead.
* It'll be too bright if material has map texture so using coef 0.2.
*/
params.color = new THREE.Color().fromArray( material.diffuse );
params.diffuse = new THREE.Color().fromArray( material.diffuse );
params.opacity = material.diffuse[ 3 ];
params.specular = new THREE.Color().fromArray( material.specular );
params.shininess = material.shininess;
params.emissive = new THREE.Color().fromArray( material.ambient );
params.transparent = params.opacity !== 1.0; //
......@@ -1016,15 +1019,21 @@
// map
if ( material.textureIndex !== - 1 ) {
params.map = this._loadTexture( data.textures[ material.textureIndex ], textures );
params.map = this._loadTexture( data.textures[ material.textureIndex ], textures ); // Since PMX spec don't have standard to list map files except color map and env map,
// we need to save file name for further mapping, like matching normal map file names after model loaded.
// ref: https://gist.github.com/felixjones/f8a06bd48f9da9a4539f#texture
params.userData.MMD.mapFileName = data.textures[ material.textureIndex ];
} // envMap TODO: support m.envFlag === 3
if ( material.envTextureIndex !== - 1 && ( material.envFlag === 1 || material.envFlag == 2 ) ) {
params.envMap = this._loadTexture( data.textures[ material.envTextureIndex ], textures );
params.combine = material.envFlag === 1 ? THREE.MultiplyOperation : THREE.AddOperation;
params.matcap = this._loadTexture( data.textures[ material.envTextureIndex ], textures ); // Same as color map above, keep file name in userData for further usage.
params.userData.MMD.matcapFileName = data.textures[ material.envTextureIndex ];
params.matcapCombine = material.envFlag === 1 ? THREE.MultiplyOperation : THREE.AddOperation;
} // gradientMap
......@@ -1070,7 +1079,7 @@
}
materials.push( new THREE.MeshToonMaterial( params ) );
materials.push( new MMDToonMaterial( params ) );
}
......@@ -1811,6 +1820,93 @@
}
class MMDToonMaterial extends THREE.ShaderMaterial {
constructor( parameters ) {
super();
this._matcapCombine = THREE.AddOperation;
this.emissiveIntensity = 1.0;
this.normalMapType = THREE.TangentSpaceNormalMap;
this.combine = THREE.MultiplyOperation;
this.wireframeLinecap = 'round';
this.wireframeLinejoin = 'round';
this.flatShading = false;
this.lights = true;
this.vertexShader = THREE.MMDToonShader.vertexShader;
this.fragmentShader = THREE.MMDToonShader.fragmentShader;
this.defines = Object.assign( {}, THREE.MMDToonShader.defines );
Object.defineProperty( this, 'matcapCombine', {
get: function () {
return this._matcapCombine;
},
set: function ( value ) {
this._matcapCombine = value;
switch ( value ) {
case THREE.MultiplyOperation:
this.defines.MATCAP_BLENDING_MULTIPLY = true;
delete this.defines.MATCAP_BLENDING_ADD;
break;
default:
case THREE.AddOperation:
this.defines.MATCAP_BLENDING_ADD = true;
delete this.defines.MATCAP_BLENDING_MULTIPLY;
break;
}
}
} );
this.uniforms = THREE.UniformsUtils.clone( THREE.MMDToonShader.uniforms ); // merged from MeshToon/Phong/MatcapMaterial
const exposePropertyNames = [ 'specular', 'shininess', 'opacity', 'diffuse', 'map', 'matcap', 'gradientMap', 'lightMap', 'lightMapIntensity', 'aoMap', 'aoMapIntensity', 'emissive', 'emissiveMap', 'bumpMap', 'bumpScale', 'normalMap', 'normalScale', 'displacemantBias', 'displacemantMap', 'displacemantScale', 'specularMap', 'alphaMap', 'envMap', 'reflectivity', 'refractionRatio' ];
for ( const propertyName of exposePropertyNames ) {
Object.defineProperty( this, propertyName, {
get: function () {
return this.uniforms[ propertyName ].value;
},
set: function ( value ) {
this.uniforms[ propertyName ].value = value;
}
} );
}
Object.defineProperty( this, 'color', Object.getOwnPropertyDescriptor( this, 'diffuse' ) );
this.setValues( parameters );
}
copy( source ) {
super.copy( source );
this.matcapCombine = source.matcapCombine;
this.emissiveIntensity = source.emissiveIntensity;
this.normalMapType = source.normalMapType;
this.combine = source.combine;
this.wireframeLinecap = source.wireframeLinecap;
this.wireframeLinejoin = source.wireframeLinejoin;
this.flatShading = source.flatShading;
return this;
}
}
MMDToonMaterial.prototype.isMMDToonMaterial = true;
THREE.MMDLoader = MMDLoader;
} )();
( function () {
/**
* MMD Toon Shader
*
* This shader is extended from MeshPhongMaterial, and merged algorithms with
* MeshToonMaterial and MeshMetcapMaterial.
* Ideas came from https://github.com/mrdoob/three.js/issues/19609
*
* Combining steps:
* * Declare matcap uniform.
* * Add gradientmap_pars_fragment.
* * Use gradient irradiances instead of dotNL irradiance from MeshPhongMaterial.
* (Replace lights_phong_pars_fragment with lights_mmd_toon_pars_fragment)
* * Add mmd_toon_matcap_fragment.
*/
const lights_mmd_toon_pars_fragment = `
varying vec3 vViewPosition;
#ifndef FLAT_SHADED
varying vec3 vNormal;
#endif
struct BlinnPhongMaterial {
vec3 diffuseColor;
vec3 specularColor;
float specularShininess;
float specularStrength;
};
void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
vec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;
#ifndef PHYSICALLY_CORRECT_LIGHTS
irradiance *= PI; // punctual light
#endif
reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );
reflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;
}
void RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );
}
#define RE_Direct RE_Direct_BlinnPhong
#define RE_IndirectDiffuse RE_IndirectDiffuse_BlinnPhong
#define Material_LightProbeLOD( material ) (0)
`;
const mmd_toon_matcap_fragment = `
#ifdef USE_MATCAP
vec3 viewDir = normalize( vViewPosition );
vec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );
vec3 y = cross( viewDir, x );
vec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5; // 0.495 to remove artifacts caused by undersized matcap disks
vec4 matcapColor = texture2D( matcap, uv );
matcapColor = matcapTexelToLinear( matcapColor );
#ifdef MATCAP_BLENDING_MULTIPLY
outgoingLight *= matcapColor.rgb;
#elif defined( MATCAP_BLENDING_ADD )
outgoingLight += matcapColor.rgb;
#endif
#endif
`;
const MMDToonShader = {
defines: {
TOON: true,
MATCAP: true,
MATCAP_BLENDING_ADD: true
},
uniforms: THREE.UniformsUtils.merge( [ THREE.ShaderLib.toon.uniforms, THREE.ShaderLib.phong.uniforms, THREE.ShaderLib.matcap.uniforms ] ),
vertexShader: THREE.ShaderLib.phong.vertexShader,
fragmentShader: THREE.ShaderLib.phong.fragmentShader.replace( '#include <common>', `
#ifdef USE_MATCAP
uniform sampler2D matcap;
#endif
#include <common>
` ).replace( '#include <envmap_common_pars_fragment>', `
#include <gradientmap_pars_fragment>
#include <envmap_common_pars_fragment>
` ).replace( '#include <lights_phong_pars_fragment>', lights_mmd_toon_pars_fragment ).replace( '#include <envmap_fragment>', `
#include <envmap_fragment>
${mmd_toon_matcap_fragment}
` )
};
THREE.MMDToonShader = MMDToonShader;
} )();
......@@ -5,6 +5,7 @@ import {
BufferGeometry,
Color,
CustomBlending,
TangentSpaceNormalMap,
DoubleSide,
DstAlphaFactor,
Euler,
......@@ -14,7 +15,8 @@ import {
Interpolant,
Loader,
LoaderUtils,
MeshToonMaterial,
UniformsUtils,
ShaderMaterial,
MultiplyOperation,
NearestFilter,
NumberKeyframeTrack,
......@@ -35,6 +37,7 @@ import {
RGB_ETC1_Format,
RGB_ETC2_Format
} from '../../../build/three.module.js';
import { MMDToonShader } from '../shaders/MMDToonShader.js';
import { TGALoader } from '../loaders/TGALoader.js';
import { MMDParser } from '../libs/mmdparser.module.js';
......@@ -1075,7 +1078,7 @@ class MaterialBuilder {
* @param {BufferGeometry} geometry - some properties are dependend on geometry
* @param {function} onProgress
* @param {function} onError
* @return {Array<MeshToonMaterial>}
* @return {Array<MMDToonMaterial>}
*/
build( data, geometry /*, onProgress, onError */ ) {
......@@ -1091,23 +1094,24 @@ class MaterialBuilder {
const material = data.materials[ i ];
const params = { userData: {} };
const params = { userData: { MMD: {} } };
if ( material.name !== undefined ) params.name = material.name;
/*
* Color
*
* MMD MeshToonMaterial
* diffuse - color
* MMD MMDToonMaterial
* ambient - emissive * a
* (a = 1.0 without map texture or 0.2 with map texture)
*
* MeshToonMaterial doesn't have ambient. Set it to emissive instead.
* MMDToonMaterial doesn't have ambient. Set it to emissive instead.
* It'll be too bright if material has map texture so using coef 0.2.
*/
params.color = new Color().fromArray( material.diffuse );
params.diffuse = new Color().fromArray( material.diffuse );
params.opacity = material.diffuse[ 3 ];
params.specular = new Color().fromArray( material.specular );
params.shininess = material.shininess;
params.emissive = new Color().fromArray( material.ambient );
params.transparent = params.opacity !== 1.0;
......@@ -1199,18 +1203,26 @@ class MaterialBuilder {
params.map = this._loadTexture( data.textures[ material.textureIndex ], textures );
// Since PMX spec don't have standard to list map files except color map and env map,
// we need to save file name for further mapping, like matching normal map file names after model loaded.
// ref: https://gist.github.com/felixjones/f8a06bd48f9da9a4539f#texture
params.userData.MMD.mapFileName = data.textures[ material.textureIndex ];
}
// envMap TODO: support m.envFlag === 3
if ( material.envTextureIndex !== - 1 && ( material.envFlag === 1 || material.envFlag == 2 ) ) {
params.envMap = this._loadTexture(
params.matcap = this._loadTexture(
data.textures[ material.envTextureIndex ],
textures
);
params.combine = material.envFlag === 1
// Same as color map above, keep file name in userData for further usage.
params.userData.MMD.matcapFileName = data.textures[ material.envTextureIndex ];
params.matcapCombine = material.envFlag === 1
? MultiplyOperation
: AddOperation;
......@@ -1263,7 +1275,7 @@ class MaterialBuilder {
}
materials.push( new MeshToonMaterial( params ) );
materials.push( new MMDToonMaterial( params ) );
}
......@@ -2064,4 +2076,151 @@ class CubicBezierInterpolation extends Interpolant {
}
class MMDToonMaterial extends ShaderMaterial {
constructor( parameters ) {
super();
this._matcapCombine = AddOperation;
this.emissiveIntensity = 1.0;
this.normalMapType = TangentSpaceNormalMap;
this.combine = MultiplyOperation;
this.wireframeLinecap = 'round';
this.wireframeLinejoin = 'round';
this.flatShading = false;
this.lights = true;
this.vertexShader = MMDToonShader.vertexShader;
this.fragmentShader = MMDToonShader.fragmentShader;
this.defines = Object.assign( {}, MMDToonShader.defines );
Object.defineProperty( this, 'matcapCombine', {
get: function () {
return this._matcapCombine;
},
set: function ( value ) {
this._matcapCombine = value;
switch ( value ) {
case MultiplyOperation:
this.defines.MATCAP_BLENDING_MULTIPLY = true;
delete this.defines.MATCAP_BLENDING_ADD;
break;
default:
case AddOperation:
this.defines.MATCAP_BLENDING_ADD = true;
delete this.defines.MATCAP_BLENDING_MULTIPLY;
break;
}
},
} );
this.uniforms = UniformsUtils.clone( MMDToonShader.uniforms );
// merged from MeshToon/Phong/MatcapMaterial
const exposePropertyNames = [
'specular',
'shininess',
'opacity',
'diffuse',
'map',
'matcap',
'gradientMap',
'lightMap',
'lightMapIntensity',
'aoMap',
'aoMapIntensity',
'emissive',
'emissiveMap',
'bumpMap',
'bumpScale',
'normalMap',
'normalScale',
'displacemantBias',
'displacemantMap',
'displacemantScale',
'specularMap',
'alphaMap',
'envMap',
'reflectivity',
'refractionRatio',
];
for ( const propertyName of exposePropertyNames ) {
Object.defineProperty( this, propertyName, {
get: function () {
return this.uniforms[ propertyName ].value;
},
set: function ( value ) {
this.uniforms[ propertyName ].value = value;
},
} );
}
Object.defineProperty(
this,
'color',
Object.getOwnPropertyDescriptor( this, 'diffuse' )
);
this.setValues( parameters );
}
copy( source ) {
super.copy( source );
this.matcapCombine = source.matcapCombine;
this.emissiveIntensity = source.emissiveIntensity;
this.normalMapType = source.normalMapType;
this.combine = source.combine;
this.wireframeLinecap = source.wireframeLinecap;
this.wireframeLinejoin = source.wireframeLinejoin;
this.flatShading = source.flatShading;
return this;
}
}
MMDToonMaterial.prototype.isMMDToonMaterial = true;
export { MMDLoader };
/**
* MMD Toon Shader
*
* This shader is extended from MeshPhongMaterial, and merged algorithms with
* MeshToonMaterial and MeshMetcapMaterial.
* Ideas came from https://github.com/mrdoob/three.js/issues/19609
*
* Combining steps:
* * Declare matcap uniform.
* * Add gradientmap_pars_fragment.
* * Use gradient irradiances instead of dotNL irradiance from MeshPhongMaterial.
* (Replace lights_phong_pars_fragment with lights_mmd_toon_pars_fragment)
* * Add mmd_toon_matcap_fragment.
*/
import { UniformsUtils, ShaderLib } from '../../../build/three.module.js';
const lights_mmd_toon_pars_fragment = `
varying vec3 vViewPosition;
#ifndef FLAT_SHADED
varying vec3 vNormal;
#endif
struct BlinnPhongMaterial {
vec3 diffuseColor;
vec3 specularColor;
float specularShininess;
float specularStrength;
};
void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
vec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;
#ifndef PHYSICALLY_CORRECT_LIGHTS
irradiance *= PI; // punctual light
#endif
reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );
reflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;
}
void RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );
}
#define RE_Direct RE_Direct_BlinnPhong
#define RE_IndirectDiffuse RE_IndirectDiffuse_BlinnPhong
#define Material_LightProbeLOD( material ) (0)
`;
const mmd_toon_matcap_fragment = `
#ifdef USE_MATCAP
vec3 viewDir = normalize( vViewPosition );
vec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );
vec3 y = cross( viewDir, x );
vec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5; // 0.495 to remove artifacts caused by undersized matcap disks
vec4 matcapColor = texture2D( matcap, uv );
matcapColor = matcapTexelToLinear( matcapColor );
#ifdef MATCAP_BLENDING_MULTIPLY
outgoingLight *= matcapColor.rgb;
#elif defined( MATCAP_BLENDING_ADD )
outgoingLight += matcapColor.rgb;
#endif
#endif
`;
const MMDToonShader = {
defines: {
TOON: true,
MATCAP: true,
MATCAP_BLENDING_ADD: true,
},
uniforms: UniformsUtils.merge( [
ShaderLib.toon.uniforms,
ShaderLib.phong.uniforms,
ShaderLib.matcap.uniforms,
] ),
vertexShader: ShaderLib.phong.vertexShader,
fragmentShader:
ShaderLib.phong.fragmentShader
.replace(
'#include <common>',
`
#ifdef USE_MATCAP
uniform sampler2D matcap;
#endif
#include <common>
`
)
.replace(
'#include <envmap_common_pars_fragment>',
`
#include <gradientmap_pars_fragment>
#include <envmap_common_pars_fragment>
`
)
.replace(
'#include <lights_phong_pars_fragment>',
lights_mmd_toon_pars_fragment
)
.replace(
'#include <envmap_fragment>',
`
#include <envmap_fragment>
${mmd_toon_matcap_fragment}
`
),
};
export { MMDToonShader };
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册