Skip to content

Commit

Permalink
Merge pull request #851 from pixiv/fix-rim-lighting
Browse files Browse the repository at this point in the history
[1.0] Fix rim lighting
  • Loading branch information
0b5vr authored Nov 4, 2021
2 parents 53ead3d + 124885b commit 56d3dcb
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 96 deletions.
153 changes: 84 additions & 69 deletions packages/three-vrm-materials-mtoon/src/MToonMaterial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import fragmentShader from './shaders/mtoon.frag';
import { MToonMaterialDebugMode } from './MToonMaterialDebugMode';
import { MToonMaterialOutlineWidthMode } from './MToonMaterialOutlineWidthMode';
import type { MToonMaterialParameters } from './MToonMaterialParameters';
import { getTextureEncodingFromMap } from './utils/getTextureEncodingFromMap';

/**
* MToon is a material specification that has various features.
Expand Down Expand Up @@ -280,7 +281,7 @@ export class MToonMaterial extends THREE.ShaderMaterial {
public set ignoreVertexColor(value: boolean) {
this._ignoreVertexColor = value;

this._updateShaderCode();
this.needsUpdate = true;
}

private _debugMode: MToonMaterialDebugMode = MToonMaterialDebugMode.None;
Expand All @@ -291,7 +292,7 @@ export class MToonMaterial extends THREE.ShaderMaterial {
set debugMode(m: MToonMaterialDebugMode) {
this._debugMode = m;

this._updateShaderCode();
this.needsUpdate = true;
}

private _outlineWidthMode: MToonMaterialOutlineWidthMode = MToonMaterialOutlineWidthMode.None;
Expand All @@ -302,7 +303,7 @@ export class MToonMaterial extends THREE.ShaderMaterial {
set outlineWidthMode(m: MToonMaterialOutlineWidthMode) {
this._outlineWidthMode = m;

this._updateShaderCode();
this.needsUpdate = true;
}

private _isOutline = false;
Expand All @@ -313,7 +314,7 @@ export class MToonMaterial extends THREE.ShaderMaterial {
set isOutline(b: boolean) {
this._isOutline = b;

this._updateShaderCode();
this.needsUpdate = true;
}

/**
Expand All @@ -324,7 +325,7 @@ export class MToonMaterial extends THREE.ShaderMaterial {
}

constructor(parameters: MToonMaterialParameters = {}) {
super();
super({ vertexShader, fragmentShader });

// override depthWrite with transparentWithZWrite
if (parameters.transparentWithZWrite) {
Expand Down Expand Up @@ -400,7 +401,83 @@ export class MToonMaterial extends THREE.ShaderMaterial {
this.setValues(parameters);

// == update shader stuff ======================================================================
this._updateShaderCode();
this.onBeforeCompile = (shader, renderer) => {
/**
* Will be needed to determine whether we should inline convert sRGB textures or not.
* See: https://github.com/mrdoob/three.js/pull/22551
*/
const isWebGL2 = renderer.capabilities.isWebGL2;

const useUvInVert = this.outlineWidthMultiplyTexture !== null;
const useUvInFrag =
this.map !== null ||
this.shadeMultiplyTexture !== null ||
this.shadingShiftTexture !== null ||
this.rimMultiplyTexture !== null ||
this.uvAnimationMaskTexture !== null;

const threeRevision = parseInt(THREE.REVISION, 10);

const defines =
Object.entries({
// Temporary compat against shader change @ Three.js r126
// See: #21205, #21307, #21299
THREE_VRM_THREE_REVISION: threeRevision,

OUTLINE: this._isOutline,
MTOON_USE_UV: useUvInVert || useUvInFrag, // we can't use `USE_UV` , it will be redefined in WebGLProgram.js
MTOON_UVS_VERTEX_ONLY: useUvInVert && !useUvInFrag,
USE_SHADEMULTIPLYTEXTURE: this.shadeMultiplyTexture !== null,
USE_SHADINGSHIFTTEXTURE: this.shadingShiftTexture !== null,
USE_MATCAPTEXTURE: this.matcapTexture !== null,
USE_RIMMULTIPLYTEXTURE: this.rimMultiplyTexture !== null,
USE_OUTLINEWIDTHMULTIPLYTEXTURE: this.outlineWidthMultiplyTexture !== null,
USE_UVANIMATIONMASKTEXTURE: this.uvAnimationMaskTexture !== null,
IGNORE_VERTEX_COLOR: this._ignoreVertexColor === true,
DEBUG_NORMAL: this._debugMode === 'normal',
DEBUG_LITSHADERATE: this._debugMode === 'litShadeRate',
DEBUG_UV: this._debugMode === 'uv',
OUTLINE_WIDTH_WORLD: this._outlineWidthMode === MToonMaterialOutlineWidthMode.WorldCoordinates,
OUTLINE_WIDTH_SCREEN: this._outlineWidthMode === MToonMaterialOutlineWidthMode.ScreenCoordinates,
})
.filter(([token, macro]) => !!macro)
.map(([token, macro]) => `#define ${token} ${macro}`)
.join('\n') + '\n';

// -- texture encodings ----------------------------------------------------------------------
const encodings =
(this.matcapTexture !== null
? getTexelDecodingFunction(
'matcapTextureTexelToLinear',
getTextureEncodingFromMap(this.matcapTexture, isWebGL2),
) + '\n'
: '') +
(this.shadeMultiplyTexture !== null
? getTexelDecodingFunction(
'shadeMultiplyTextureTexelToLinear',
getTextureEncodingFromMap(this.shadeMultiplyTexture, isWebGL2),
) + '\n'
: '') +
(this.rimMultiplyTexture !== null
? getTexelDecodingFunction(
'rimMultiplyTextureTexelToLinear',
getTextureEncodingFromMap(this.rimMultiplyTexture, isWebGL2),
) + '\n'
: '');

// -- generate shader code -------------------------------------------------------------------
shader.vertexShader = defines + shader.vertexShader;
shader.fragmentShader = defines + encodings + shader.fragmentShader;

// -- compat ---------------------------------------------------------------------------------

// COMPAT
// Three.js r132 introduces new shader chunks <normal_pars_fragment> and <alphatest_pars_fragment>
if (threeRevision < 132) {
shader.fragmentShader = shader.fragmentShader.replace('#include <normal_pars_fragment>', '');
shader.fragmentShader = shader.fragmentShader.replace('#include <alphatest_pars_fragment>', '');
}
};
}

/**
Expand Down Expand Up @@ -478,7 +555,7 @@ export class MToonMaterial extends THREE.ShaderMaterial {
this.isOutline = source.isOutline;

// == update shader stuff ======================================================================
this._updateShaderCode();
this.needsUpdate = true;

return this;
}
Expand All @@ -492,66 +569,4 @@ export class MToonMaterial extends THREE.ShaderMaterial {
dst.value.copy(src.value.matrix);
}
}

private _updateShaderCode(): void {
const useUvInVert = this.outlineWidthMultiplyTexture !== null;
const useUvInFrag =
this.map !== null ||
this.shadeMultiplyTexture !== null ||
this.shadingShiftTexture !== null ||
this.rimMultiplyTexture !== null ||
this.uvAnimationMaskTexture !== null;

const threeRevision = parseInt(THREE.REVISION, 10);

this.defines = {
// Temporary compat against shader change @ Three.js r126
// See: #21205, #21307, #21299
THREE_VRM_THREE_REVISION: threeRevision,

OUTLINE: this._isOutline,
MTOON_USE_UV: useUvInVert || useUvInFrag, // we can't use `USE_UV` , it will be redefined in WebGLProgram.js
MTOON_UVS_VERTEX_ONLY: useUvInVert && !useUvInFrag,
USE_SHADEMULTIPLYTEXTURE: this.shadeMultiplyTexture !== null,
USE_SHADINGSHIFTTEXTURE: this.shadingShiftTexture !== null,
USE_MATCAPTEXTURE: this.matcapTexture !== null,
USE_RIMMULTIPLYTEXTURE: this.rimMultiplyTexture !== null,
USE_OUTLINEWIDTHMULTIPLYTEXTURE: this.outlineWidthMultiplyTexture !== null,
USE_UVANIMATIONMASKTEXTURE: this.uvAnimationMaskTexture !== null,
IGNORE_VERTEX_COLOR: this._ignoreVertexColor === true,
DEBUG_NORMAL: this._debugMode === 'normal',
DEBUG_LITSHADERATE: this._debugMode === 'litShadeRate',
DEBUG_UV: this._debugMode === 'uv',
OUTLINE_WIDTH_WORLD: this._outlineWidthMode === MToonMaterialOutlineWidthMode.WorldCoordinates,
OUTLINE_WIDTH_SCREEN: this._outlineWidthMode === MToonMaterialOutlineWidthMode.ScreenCoordinates,
};

// == texture encodings ========================================================================
const encodings =
(this.matcapTexture !== null
? getTexelDecodingFunction('matcapTextureTexelToLinear', this.matcapTexture.encoding) + '\n'
: '') +
(this.shadeMultiplyTexture !== null
? getTexelDecodingFunction('shadeMultiplyTextureTexelToLinear', this.shadeMultiplyTexture.encoding) + '\n'
: '') +
(this.rimMultiplyTexture !== null
? getTexelDecodingFunction('rimMultiplyTextureTexelToLinear', this.rimMultiplyTexture.encoding) + '\n'
: '');

// == generate shader code =====================================================================
this.vertexShader = vertexShader;
this.fragmentShader = encodings + fragmentShader;

// == compat ===================================================================================

// COMPAT
// Three.js r132 introduces new shader chunks <normal_pars_fragment> and <alphatest_pars_fragment>
if (threeRevision < 132) {
this.fragmentShader = this.fragmentShader.replace('#include <normal_pars_fragment>', '');
this.fragmentShader = this.fragmentShader.replace('#include <alphatest_pars_fragment>', '');
}

// == set needsUpdate flag =====================================================================
this.needsUpdate = true;
}
}
17 changes: 11 additions & 6 deletions packages/three-vrm-materials-mtoon/src/shaders/mtoon.frag
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ uniform float shadingShiftFactor;
uniform float shadingToonyFactor;

#ifdef USE_SHADINGSHIFTTEXTURE
vec2 shadingShiftTextureUv = ( shadingShiftTextureUvTransform * vec3( uv, 1 ) ).xy;
uniform sampler2D shadingShiftTexture;
uniform mat3 shadingShiftTextureUvTransform;
uniform float shadingShiftTextureScale;
Expand Down Expand Up @@ -154,25 +153,27 @@ vec3 getDiffuse(

void RE_Direct_MToon( const in IncidentLight directLight, const in GeometricContext geometry, const in MToonMaterial material, const in float shadow, inout ReflectedLight reflectedLight ) {
float dotNL = saturate( dot( geometry.normal, directLight.direction ) );
vec3 irradiance = dotNL * directLight.color;
vec3 irradiance = directLight.color;

#if THREE_VRM_THREE_REVISION < 132
#ifndef PHYSICALLY_CORRECT_LIGHTS
irradiance *= PI;
#endif
#endif

// directSpecular will be used for rim lighting, not an actual specular
reflectedLight.directSpecular += irradiance;

irradiance *= dotNL;

float shading = getShading( dotNL, shadow, material.shadingShift );

// toon shaded diffuse
reflectedLight.directDiffuse += getDiffuse( material, shading, directLight.color );

// directSpecular will be used for rim lighting, not an actual specular
reflectedLight.directSpecular += irradiance;
}

void RE_IndirectDiffuse_MToon( const in vec3 irradiance, const in GeometricContext geometry, const in MToonMaterial material, inout ReflectedLight reflectedLight ) {
// indirect diffuse will be use diffuseColor, no shadeColor involved
// indirect diffuse will use diffuseColor, no shadeColor involved
reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );

// directSpecular will be used for rim lighting, not an actual specular
Expand Down Expand Up @@ -643,6 +644,10 @@ void main() {

// -- MToon: rim lighting -----------------------------------------
vec3 viewDir = normalize( vViewPosition );

#ifndef PHYSICALLY_CORRECT_LIGHTS
reflectedLight.directSpecular /= PI;
#endif
vec3 rimMix = mix( vec3( 1.0 ), reflectedLight.directSpecular, 1.0 );

vec3 rim = parametricRimColorFactor * pow( saturate( 1.0 - dot( viewDir, normal ) + parametricRimLiftFactor ), parametricRimFresnelPowerFactor );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as THREE from 'three';

export const getEncodingComponents = (encoding: THREE.TextureEncoding): [string, string] => {
switch (encoding) {
case THREE.LinearEncoding:
return ['Linear', '( value )'];
case THREE.sRGBEncoding:
return ['sRGB', '( value )'];
case THREE.RGBEEncoding:
return ['RGBE', '( value )'];
case THREE.RGBM7Encoding:
return ['RGBM', '( value, 7.0 )'];
case THREE.RGBM16Encoding:
return ['RGBM', '( value, 16.0 )'];
case THREE.RGBDEncoding:
return ['RGBD', '( value, 256.0 )'];
case THREE.GammaEncoding:
return ['Gamma', '( value, float( GAMMA_FACTOR ) )'];
case THREE.LogLuvEncoding:
return ['LogLuv', '( value )'];
default:
throw new Error('unsupported encoding: ' + encoding);
}
};
Original file line number Diff line number Diff line change
@@ -1,25 +1,5 @@
import * as THREE from 'three';

export const getEncodingComponents = (encoding: THREE.TextureEncoding): [string, string] => {
switch (encoding) {
case THREE.LinearEncoding:
return ['Linear', '( value )'];
case THREE.sRGBEncoding:
return ['sRGB', '( value )'];
case THREE.RGBEEncoding:
return ['RGBE', '( value )'];
case THREE.RGBM7Encoding:
return ['RGBM', '( value, 7.0 )'];
case THREE.RGBM16Encoding:
return ['RGBM', '( value, 16.0 )'];
case THREE.RGBDEncoding:
return ['RGBD', '( value, 256.0 )'];
case THREE.GammaEncoding:
return ['Gamma', '( value, float( GAMMA_FACTOR ) )'];
default:
throw new Error('unsupported encoding: ' + encoding);
}
};
import { getEncodingComponents } from './getEncodingComponents';

export const getTexelDecodingFunction = (functionName: string, encoding: THREE.TextureEncoding): string => {
const components = getEncodingComponents(encoding);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as THREE from 'three';

/**
* Retrieved from https://github.com/mrdoob/three.js/blob/88b6328998d155fa0a7c1f1e5e3bd6bff75268c0/src/renderers/webgl/WebGLPrograms.js#L92
*
* Diff:
* - Remove WebGLRenderTarget handler because it increases code complexities on TypeScript
* - Add a boolean `isWebGL2` as a second argument.
*/
export function getTextureEncodingFromMap(map: THREE.Texture, isWebGL2: boolean): THREE.TextureEncoding {
let encoding;

if (map && map.isTexture) {
encoding = map.encoding;
// } else if ( map && map.isWebGLRenderTarget ) {
// console.warn( 'THREE.WebGLPrograms.getTextureEncodingFromMap: don\'t use render targets as textures. Use their .texture property instead.' );
// encoding = map.texture.encoding;
} else {
encoding = THREE.LinearEncoding;
}

if (parseInt(THREE.REVISION, 10) >= 133) {
if (
isWebGL2 &&
map &&
map.isTexture &&
map.format === THREE.RGBAFormat &&
map.type === THREE.UnsignedByteType &&
map.encoding === THREE.sRGBEncoding
) {
encoding = THREE.LinearEncoding; // disable inline decode for sRGB textures in WebGL 2
}
}

return encoding;
}

0 comments on commit 56d3dcb

Please sign in to comment.