Skip to content

Commit

Permalink
Merge pull request #8081 from Popov72/shadow-transparent
Browse files Browse the repository at this point in the history
Add suport for soft transparent shadows
  • Loading branch information
mergify[bot] authored Apr 28, 2020
2 parents 2ba7bed + 318d9d9 commit c0907e1
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 14 deletions.
1 change: 1 addition & 0 deletions dist/preview release/what's new.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Added support for `material.disableColorWrite` ([Deltakosh](https://github.com/deltakosh))
- The Mesh Asset Task also accepts File as sceneInput ([RaananW](https://github.com/RaananW))
- Added support preserving vert colors for CSG objects ([PirateJC](https://github.com/PirateJC))
- Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))

### Engine

Expand Down
5 changes: 4 additions & 1 deletion materialsLibrary/src/custom/customMaterial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,12 @@ export class CustomMaterial extends StandardMaterial {
.replace('#define CUSTOM_FRAGMENT_UPDATE_DIFFUSE', (this.CustomParts.Fragment_Custom_Diffuse ? this.CustomParts.Fragment_Custom_Diffuse : ""))
.replace('#define CUSTOM_FRAGMENT_UPDATE_ALPHA', (this.CustomParts.Fragment_Custom_Alpha ? this.CustomParts.Fragment_Custom_Alpha : ""))
.replace('#define CUSTOM_FRAGMENT_BEFORE_LIGHTS', (this.CustomParts.Fragment_Before_Lights ? this.CustomParts.Fragment_Before_Lights : ""))
.replace('#define CUSTOM_FRAGMENT_BEFORE_FOG', (this.CustomParts.Fragment_Before_Fog ? this.CustomParts.Fragment_Before_Fog : ""))
.replace('#define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR', (this.CustomParts.Fragment_Before_FragColor ? this.CustomParts.Fragment_Before_FragColor : ""));

if (this.CustomParts.Fragment_Before_Fog) {
Effect.ShadersStore[name + "PixelShader"] = Effect.ShadersStore[name + "PixelShader"].replace('#define CUSTOM_FRAGMENT_BEFORE_FOG', this.CustomParts.Fragment_Before_Fog);
}

this._isCreatedShader = true;
this._createdShaderName = name;

Expand Down
5 changes: 4 additions & 1 deletion materialsLibrary/src/custom/pbrCustomMaterial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,12 @@ export class PBRCustomMaterial extends PBRMaterial {
.replace('#define CUSTOM_FRAGMENT_BEFORE_LIGHTS', (this.CustomParts.Fragment_Before_Lights ? this.CustomParts.Fragment_Before_Lights : ""))
.replace('#define CUSTOM_FRAGMENT_UPDATE_METALLICROUGHNESS', (this.CustomParts.Fragment_Custom_MetallicRoughness ? this.CustomParts.Fragment_Custom_MetallicRoughness : ""))
.replace('#define CUSTOM_FRAGMENT_UPDATE_MICROSURFACE', (this.CustomParts.Fragment_Custom_MicroSurface ? this.CustomParts.Fragment_Custom_MicroSurface : ""))
.replace('#define CUSTOM_FRAGMENT_BEFORE_FOG', (this.CustomParts.Fragment_Before_Fog ? this.CustomParts.Fragment_Before_Fog : ""))
.replace('#define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR', (this.CustomParts.Fragment_Before_FragColor ? this.CustomParts.Fragment_Before_FragColor : ""));

if (this.CustomParts.Fragment_Before_Fog) {
Effect.ShadersStore[name + "PixelShader"] = Effect.ShadersStore[name + "PixelShader"].replace('#define CUSTOM_FRAGMENT_BEFORE_FOG', this.CustomParts.Fragment_Before_Fog);
}

this._isCreatedShader = true;
this._createdShaderName = name;

Expand Down
37 changes: 28 additions & 9 deletions src/Lights/Shadows/shadowGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Constants } from "../../Engines/constants";
import "../../Shaders/shadowMap.fragment";
import "../../Shaders/shadowMap.vertex";
import "../../Shaders/depthBoxBlur.fragment";
import "../../Shaders/ShadersInclude/shadowMapFragmentSoftTransparentShadow";
import { Observable } from '../../Misc/observable';
import { _DevTools } from '../../Misc/devTools';
import { EffectFallbacks } from '../../Materials/effectFallbacks';
Expand Down Expand Up @@ -75,9 +76,10 @@ export interface IShadowGenerator {
* Determine wheter the shadow generator is ready or not (mainly all effects and related post processes needs to be ready).
* @param subMesh The submesh we want to render in the shadow map
* @param useInstances Defines wether will draw in the map using instances
* @param isTransparent Indicates that isReady is called for a transparent subMesh
* @returns true if ready otherwise, false
*/
isReady(subMesh: SubMesh, useInstances: boolean): boolean;
isReady(subMesh: SubMesh, useInstances: boolean, isTransparent: boolean): boolean;

/**
* Prepare all the defines in a material relying on a shadow map at the specified light index.
Expand Down Expand Up @@ -661,6 +663,15 @@ export class ShadowGenerator implements IShadowGenerator {
return this;
}

/**
* Enables or disables shadows with varying strength based on the transparency
* When it is enabled, the strength of the shadow is taken equal to mesh.visibility
* If you enabled an alpha texture on your material, the alpha value red from the texture is also combined to compute the strength:
* mesh.visibility * alphaTexture.a
* Note that by definition transparencyShadow must be set to true for enableSoftTransparentShadow to work!
*/
public enableSoftTransparentShadow: boolean = false;

protected _shadowMap: Nullable<RenderTargetTexture>;
protected _shadowMap2: Nullable<RenderTargetTexture>;

Expand Down Expand Up @@ -1017,7 +1028,7 @@ export class ShadowGenerator implements IShadowGenerator {

if (this._transparencyShadow) {
for (index = 0; index < transparentSubMeshes.length; index++) {
this._renderSubMeshForShadowMap(transparentSubMeshes.data[index]);
this._renderSubMeshForShadowMap(transparentSubMeshes.data[index], true);
}
}
}
Expand All @@ -1040,7 +1051,7 @@ export class ShadowGenerator implements IShadowGenerator {
effect.setMatrix(matriceNames?.worldView ?? "worldView", tmpMatrix2);
}

protected _renderSubMeshForShadowMap(subMesh: SubMesh): void {
protected _renderSubMeshForShadowMap(subMesh: SubMesh, isTransparent: boolean = false): void {
var ownerMesh = subMesh.getMesh();
var replacementMesh = ownerMesh._internalAbstractMeshDataInfo._actAsRegularMesh ? ownerMesh : null;
var renderingMesh = subMesh.getRenderingMesh();
Expand All @@ -1065,7 +1076,7 @@ export class ShadowGenerator implements IShadowGenerator {
}

var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
if (this.isReady(subMesh, hardwareInstancedRendering)) {
if (this.isReady(subMesh, hardwareInstancedRendering, isTransparent)) {
const shadowDepthWrapper = renderingMesh.material?.shadowDepthWrapper;

let effect = shadowDepthWrapper?.getEffect(subMesh, this) ?? this._effect;
Expand All @@ -1089,6 +1100,10 @@ export class ShadowGenerator implements IShadowGenerator {
effect.setFloat2("depthValuesSM", this.getLight().getDepthMinZ(scene.activeCamera), this.getLight().getDepthMinZ(scene.activeCamera) + this.getLight().getDepthMaxZ(scene.activeCamera));
}

if (isTransparent && this.enableSoftTransparentShadow) {
effect.setFloat("softTransparentShadowSM", effectiveMesh.visibility);
}

if (shadowDepthWrapper) {
subMesh._effectOverride = effect;
if (shadowDepthWrapper.standalone) {
Expand Down Expand Up @@ -1219,7 +1234,7 @@ export class ShadowGenerator implements IShadowGenerator {
return;
}

while (this.isReady(subMeshes[currentIndex], localOptions.useInstances)) {
while (this.isReady(subMeshes[currentIndex], localOptions.useInstances, subMeshes[currentIndex].getMaterial()?.needAlphaBlendingForMesh(subMeshes[currentIndex].getMesh()) ?? false)) {
currentIndex++;
if (currentIndex >= subMeshes.length) {
if (onCompiled) {
Expand Down Expand Up @@ -1250,7 +1265,7 @@ export class ShadowGenerator implements IShadowGenerator {
protected _isReadyCustomDefines(defines: any, subMesh: SubMesh, useInstances: boolean): void {
}

private _prepareShadowDefines(subMesh: SubMesh, useInstances: boolean, defines: string[]): string[] {
private _prepareShadowDefines(subMesh: SubMesh, useInstances: boolean, defines: string[], isTransparent: boolean): string[] {
defines.push("#define SM_FLOAT " + (this._textureType !== Constants.TEXTURETYPE_UNSIGNED_INT ? "1" : "0"));

defines.push("#define SM_ESM " + (this.useExponentialShadowMap || this.useBlurExponentialShadowMap ? "1" : "0"));
Expand All @@ -1266,6 +1281,9 @@ export class ShadowGenerator implements IShadowGenerator {
// Point light
defines.push("#define SM_USEDISTANCE " + (this._light.needCube() ? "1" : "0"));

// Soft transparent shadows
defines.push("#define SM_SOFTTRANSPARENTSHADOW " + (this.enableSoftTransparentShadow && isTransparent ? "1" : "0"));

this._isReadyCustomDefines(defines, subMesh, useInstances);

return defines;
Expand All @@ -1275,15 +1293,16 @@ export class ShadowGenerator implements IShadowGenerator {
* Determine wheter the shadow generator is ready or not (mainly all effects and related post processes needs to be ready).
* @param subMesh The submesh we want to render in the shadow map
* @param useInstances Defines wether will draw in the map using instances
* @param isTransparent Indicates that isReady is called for a transparent subMesh
* @returns true if ready otherwise, false
*/
public isReady(subMesh: SubMesh, useInstances: boolean): boolean {
public isReady(subMesh: SubMesh, useInstances: boolean, isTransparent: boolean): boolean {
const material = subMesh.getMaterial(),
shadowDepthWrapper = material?.shadowDepthWrapper;

const defines: string[] = [];

this._prepareShadowDefines(subMesh, useInstances, defines);
this._prepareShadowDefines(subMesh, useInstances, defines, isTransparent);

if (shadowDepthWrapper) {
if (!shadowDepthWrapper.isReadyForSubMesh(subMesh, defines, this, useInstances)) {
Expand Down Expand Up @@ -1402,7 +1421,7 @@ export class ShadowGenerator implements IShadowGenerator {

let shaderName = "shadowMap";
let uniforms = ["world", "mBones", "viewProjection", "diffuseMatrix", "lightDataSM", "depthValuesSM", "biasAndScaleSM", "morphTargetInfluences", "boneTextureWidth",
"vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "vClipPlane5", "vClipPlane6"];
"vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "vClipPlane5", "vClipPlane6", "softTransparentShadowSM"];
let samplers = ["diffuseSampler", "boneSampler"];

// Custom shader?
Expand Down
4 changes: 3 additions & 1 deletion src/Materials/shadowDepthWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export class ShadowDepthWrapper {

const vertexNormalBiasCode = this._options && this._options.remappedVariables ? `#include<shadowMapVertexNormalBias>(${this._options.remappedVariables.join(",")})` : Effect.IncludesShadersStore["shadowMapVertexNormalBias"],
vertexMetricCode = this._options && this._options.remappedVariables ? `#include<shadowMapVertexMetric>(${this._options.remappedVariables.join(",")})` : Effect.IncludesShadersStore["shadowMapVertexMetric"],
fragmentSoftTransparentShadow = this._options && this._options.remappedVariables ? `#include<shadowMapFragmentSoftTransparentShadow>(${this._options.remappedVariables.join(",")})` : Effect.IncludesShadersStore["shadowMapFragmentSoftTransparentShadow"],
fragmentBlockCode = Effect.IncludesShadersStore["shadowMapFragment"];

vertexCode = vertexCode.replace(/void\s+?main/g, Effect.IncludesShadersStore["shadowMapVertexDeclaration"] + "\r\nvoid main");
Expand All @@ -216,6 +217,7 @@ export class ShadowDepthWrapper {
vertexCode = vertexCode.replace(/#define SHADER_NAME.*?\n|out vec4 glFragColor;\n/g, "");

fragmentCode = fragmentCode.replace(/void\s+?main/g, Effect.IncludesShadersStore["shadowMapFragmentDeclaration"] + "\r\nvoid main");
fragmentCode = fragmentCode.replace(/#define SHADOWDEPTH_SOFTTRANSPARENTSHADOW|#define CUSTOM_FRAGMENT_BEFORE_FOG/g, fragmentSoftTransparentShadow);
if (fragmentCode.indexOf("#define SHADOWDEPTH_FRAGMENT") !== -1) {
fragmentCode = fragmentCode.replace(/#define SHADOWDEPTH_FRAGMENT/g, fragmentBlockCode);
} else {
Expand All @@ -225,7 +227,7 @@ export class ShadowDepthWrapper {

const uniforms = origEffect.getUniformNames().slice();

uniforms.push("biasAndScaleSM", "depthValuesSM", "lightDataSM");
uniforms.push("biasAndScaleSM", "depthValuesSM", "lightDataSM", "softTransparentShadowSM");

params.depthEffect = this._scene.getEngine().createEffect({
vertexSource: vertexCode,
Expand Down
2 changes: 1 addition & 1 deletion src/Meshes/mesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {

if (generator && (!generator.getShadowMap()?.renderList || generator.getShadowMap()?.renderList && generator.getShadowMap()?.renderList?.indexOf(this) !== -1)) {
for (var subMesh of this.subMeshes) {
if (!generator.isReady(subMesh, hardwareInstancedRendering)) {
if (!generator.isReady(subMesh, hardwareInstancedRendering, subMesh.getMaterial()?.needAlphaBlendingForMesh(this) ?? false)) {
return false;
}
}
Expand Down
25 changes: 25 additions & 0 deletions src/Shaders/ShadersInclude/bayerDitherFunctions.fx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// from https://www.shadertoy.com/view/Mlt3z8

// Generates the basic 2x2 Bayer permutation matrix:
// [1 2]
// [3 0]
// Expects _P in [0,1]
float bayerDither2(vec2 _P) {
return mod(2.0 * _P.y + _P.x + 1.0, 4.0);
}

// Generates the 4x4 matrix
// Expects _P any pixel coordinate
float bayerDither4(vec2 _P) {
vec2 P1 = mod(_P, 2.0); // (P >> 0) & 1
vec2 P2 = floor(0.5 * mod(_P, 4.0)); // (P >> 1) & 1
return 4.0 * bayerDither2(P1) + bayerDither2(P2);
}

// Generates the 8x8 matrix
float bayerDither8(vec2 _P) {
vec2 P1 = mod(_P, 2.0); // (P >> 0) & 1
vec2 P2 = floor(0.5 * mod(_P, 4.0)); // (P >> 1) & 1
vec2 P4 = floor(0.25 * mod(_P, 8.0)); // (P >> 2) & 1
return 4.0 * (4.0 * bayerDither2(P1) + bayerDither2(P2)) + bayerDither2(P4);
}
6 changes: 6 additions & 0 deletions src/Shaders/ShadersInclude/shadowMapFragmentDeclaration.fx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
#include<packingFunctions>
#endif

#if SM_SOFTTRANSPARENTSHADOW == 1
#include<bayerDitherFunctions>

uniform float softTransparentShadowSM;
#endif

varying float vDepthMetricSM;

#if SM_USEDISTANCE == 1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#if SM_SOFTTRANSPARENTSHADOW == 1
if ((bayerDither8(floor(mod(gl_FragCoord.xy, 8.0)))) / 64.0 >= softTransparentShadowSM * alpha) discard;
#endif
11 changes: 10 additions & 1 deletion src/Shaders/shadowMap.fragment.fx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,18 @@ void main(void)
#include<clipPlaneFragment>

#ifdef ALPHATEST
if (texture2D(diffuseSampler, vUV).a < 0.4)
float alphaFromAlphaTexture = texture2D(diffuseSampler, vUV).a;
if (alphaFromAlphaTexture < 0.4)
discard;
#endif

#if SM_SOFTTRANSPARENTSHADOW == 1
#ifdef ALPHATEST
if ((bayerDither8(floor(mod(gl_FragCoord.xy, 8.0)))) / 64.0 >= softTransparentShadowSM * alphaFromAlphaTexture) discard;
#else
if ((bayerDither8(floor(mod(gl_FragCoord.xy, 8.0)))) / 64.0 >= softTransparentShadowSM) discard;
#endif
#endif

#include<shadowMapFragment>
}

0 comments on commit c0907e1

Please sign in to comment.