Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support transparent shadow #2080

Merged
merged 10 commits into from
Jun 4, 2024
61 changes: 61 additions & 0 deletions e2e/case/shadow-transparent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @title Shadow Transparent
* @category Shadow
*/

import {
Camera,
DirectLight,
GLTFResource,
Logger,
MeshRenderer,
PBRMaterial,
PrimitiveMesh,
ShadowResolution,
ShadowType,
Vector3,
WebGLEngine,
WebGLMode
} from "@galacean/engine";
import { initScreenshot, updateForE2E } from "./.mockForE2E";
Logger.enable();

WebGLEngine.create({
canvas: "canvas",
graphicDeviceOptions: {
webGLMode: WebGLMode.WebGL2
}
}).then((engine) => {
engine.canvas.resizeByClientSize();
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();
scene.shadowResolution = ShadowResolution.Medium;
scene.shadowDistance = 10;
const cameraEntity = rootEntity.createChild("camera_node");
cameraEntity.transform.setPosition(0, 2, 3);
cameraEntity.transform.lookAt(new Vector3(0));
const camera = cameraEntity.addComponent(Camera);
const lightEntity = rootEntity.createChild("light_node");
const light = lightEntity.addComponent(DirectLight);
lightEntity.transform.setPosition(-6, 10, 0);
lightEntity.transform.lookAt(new Vector3(0, 0, -10));
light.shadowType = ShadowType.Hard;

const planeEntity = rootEntity.createChild("plane_node");
const renderer = planeEntity.addComponent(MeshRenderer);
renderer.mesh = PrimitiveMesh.createPlane(engine, 10, 10);
const planeMaterial = new PBRMaterial(engine);
renderer.setMaterial(planeMaterial);

engine.resourceManager
.load<GLTFResource>("https://mdn.alipayobjects.com/oasis_be/afts/file/A*kgYIRo36270AAAAAAAAAAAAADkp5AQ/bottle.glb")
.then((asset) => {
const defaultSceneRoot = asset.instantiateSceneRoot();
rootEntity.addChild(defaultSceneRoot);
defaultSceneRoot.transform.scale.set(0.05, 0.05, 0.05);
scene.enableTransparentShadow = true;

updateForE2E(engine, 500);
initScreenshot(engine, camera);
});
});
5 changes: 5 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ export const E2E_CONFIG = {
category: "Shadow",
caseFileName: "shadow-basic",
threshold: 0.2
},
transparent: {
category: "Shadow",
caseFileName: "shadow-transparent",
threshold: 0.2
}
},
Primitive: {
Expand Down
3 changes: 3 additions & 0 deletions e2e/fixtures/originImage/Shadow_shadow-transparent.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions packages/core/src/Scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class Scene extends EngineObject {
private _fogParams: Vector4 = new Vector4();
private _isActive: boolean = true;
private _sun: DirectLight | null;
private _enableTransparentShadow = false;

/**
* Whether the scene is active.
Expand Down Expand Up @@ -241,6 +242,24 @@ export class Scene extends EngineObject {
this._sun = light;
}

/**
* Whether to enable transparent shadow.
*/
get enableTransparentShadow(): boolean {
return this._enableTransparentShadow;
}

set enableTransparentShadow(v: boolean) {
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
if (v !== this._enableTransparentShadow) {
this._enableTransparentShadow = v;
if (v) {
this.shaderData.enableMacro("SCENE_ENABLE_TRANSPARENT_SHADOW");
} else {
this.shaderData.disableMacro("SCENE_ENABLE_TRANSPARENT_SHADOW");
}
}
}

/**
* Create scene.
* @param engine - Engine
Expand Down
41 changes: 38 additions & 3 deletions packages/core/src/material/BaseMaterial.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Engine } from "../Engine";
import { BlendFactor, BlendOperation, CullMode, Shader, ShaderProperty } from "../shader";
import { BlendFactor, BlendOperation, CullMode, Shader, ShaderPass, ShaderProperty } from "../shader";
import { RenderQueueType } from "../shader/enums/RenderQueueType";
import { ShaderMacro } from "../shader/ShaderMacro";
import { RenderState } from "../shader/state/RenderState";
Expand Down Expand Up @@ -27,6 +27,8 @@ export class BaseMaterial extends Material {
private _renderFace: RenderFace = RenderFace.Front;
private _isTransparent: boolean = false;
private _blendMode: BlendMode = BlendMode.Normal;
private _shadowPass: ShaderPass;
private _shadowPassIndex: number;

/**
* Shader used by the material.
Expand Down Expand Up @@ -60,6 +62,8 @@ export class BaseMaterial extends Material {
} else {
renderStates.length = maxPassCount;
}

this._refreshShadowPassInfo();
}

/**
Expand All @@ -71,8 +75,8 @@ export class BaseMaterial extends Material {

set isTransparent(value: boolean) {
if (value !== this._isTransparent) {
this.setIsTransparent(0, value);
this._isTransparent = value;
this.setIsTransparent(0, value);
}
}

Expand All @@ -86,8 +90,8 @@ export class BaseMaterial extends Material {

set blendMode(value: BlendMode) {
if (value !== this._blendMode) {
this.setBlendMode(0, value);
this._blendMode = value;
this.setBlendMode(0, value);
}
}

Expand Down Expand Up @@ -125,6 +129,8 @@ export class BaseMaterial extends Material {
}

shaderData.setFloat(BaseMaterial._alphaCutoffProp, value);

this._setShadowPassRenderQueueType();
}
}

Expand Down Expand Up @@ -177,6 +183,8 @@ export class BaseMaterial extends Material {
: RenderQueueType.Opaque;
this.shaderData.disableMacro(BaseMaterial._transparentMacro);
}

this._setShadowPassRenderQueueType();
}

/**
Expand Down Expand Up @@ -252,4 +260,31 @@ export class BaseMaterial extends Material {
target._isTransparent = this._isTransparent;
target._blendMode = this._blendMode;
}

private _refreshShadowPassInfo(): void {
const passes = this.shader.subShaders[0].passes;
const length = passes.length;
this._shadowPass = null;
this._shadowPassIndex = null;

for (let i = 0; i < length; i++) {
const pass = passes[i];
if (pass.name === "ShadowCaster") {
this._shadowPass = pass;
this._shadowPassIndex = i;
}
}
}

private _setShadowPassRenderQueueType(): void {
const shadowPass = this._shadowPass;

if (shadowPass) {
const alphaCutoff = this.shaderData.getFloat(BaseMaterial._alphaCutoffProp);
const renderState = shadowPass._renderState ?? this.renderStates[this._shadowPassIndex];

renderState.renderQueueType =
alphaCutoff || this._isTransparent ? RenderQueueType.AlphaTest : RenderQueueType.Opaque;
}
}
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
}
61 changes: 44 additions & 17 deletions packages/core/src/shaderlib/extra/shadow-map.fs.glsl
Original file line number Diff line number Diff line change
@@ -1,25 +1,52 @@
#ifdef ENGINE_NO_DEPTH_TEXTURE
/**
* Decompose and save depth value.
*/
vec4 pack (float depth) {
// Use rgba 4 bytes with a total of 32 bits to store the z value, and the accuracy of 1 byte is 1/256.
const vec4 bitShift = vec4(1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0);
const vec4 bitMask = vec4(1.0/256.0, 1.0/256.0, 1.0/256.0, 0.0);
/**
* Decompose and save depth value.
*/
vec4 pack (float depth) {
// Use rgba 4 bytes with a total of 32 bits to store the z value, and the accuracy of 1 byte is 1/256.
const vec4 bitShift = vec4(1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0);
const vec4 bitMask = vec4(1.0/256.0, 1.0/256.0, 1.0/256.0, 0.0);

vec4 rgbaDepth = fract(depth * bitShift); // Calculate the z value of each point
vec4 rgbaDepth = fract(depth * bitShift); // Calculate the z value of each point

// Cut off the value which do not fit in 8 bits
rgbaDepth -= rgbaDepth.gbaa * bitMask;
// Cut off the value which do not fit in 8 bits
rgbaDepth -= rgbaDepth.gbaa * bitMask;

return rgbaDepth;
}
return rgbaDepth;
}
#endif


uniform vec4 material_BaseColor;
uniform sampler2D material_BaseTexture;
uniform float material_AlphaCutoff;
varying vec2 v_uv;

void main() {
#ifdef ENGINE_NO_DEPTH_TEXTURE
gl_FragColor = pack(gl_FragCoord.z);
#else
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
#endif
#if defined(MATERIAL_IS_ALPHA_CUTOFF) || (defined(SCENE_ENABLE_TRANSPARENT_SHADOW) && defined(MATERIAL_IS_TRANSPARENT))
float alpha = material_BaseColor.a;
#ifdef MATERIAL_HAS_BASETEXTURE
alpha *= texture2D(material_BaseTexture, v_uv).a;
#endif

#ifdef MATERIAL_IS_ALPHA_CUTOFF
if(alpha < material_AlphaCutoff){
discard;
}
#endif

#if defined(SCENE_ENABLE_TRANSPARENT_SHADOW) && defined(MATERIAL_IS_TRANSPARENT)
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
// Interleaved gradient noise
float noise = fract(52.982919 * fract(dot(vec2(0.06711, 0.00584), gl_FragCoord.xy)));
if (alpha <= noise) {
discard;
};
#endif
#endif

#ifdef ENGINE_NO_DEPTH_TEXTURE
gl_FragColor = pack(gl_FragCoord.z);
#else
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
#endif
}
2 changes: 2 additions & 0 deletions packages/core/src/shaderlib/extra/shadow-map.vs.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <common_vert>
#include <blendShape_input>
#include <normal_share>
#include <uv_share>
uniform mat4 camera_VPMat;
uniform vec2 scene_ShadowBias; // x: depth bias, y: normal bias
uniform vec3 scene_LightDirection;
Expand All @@ -24,6 +25,7 @@ void main() {
#include <begin_normal_vert>
#include <blendShape_vert>
#include <skinning_vert>
#include <uv_vert>

vec4 positionWS = renderer_ModelMat * position;

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/shaderlib/pbr/brdf.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ float D_GGX(float alpha, float dotNH ) {
// https://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf Addenda
float D_GGX_Anisotropic(float at, float ab, float ToH, float BoH, float NoH) {
float a2 = at * ab;
highp vec3 d = vec3(ab * ToH, at * BoH, a2 * NoH);
highp float d2 = dot(d, d);
vec3 d = vec3(ab * ToH, at * BoH, a2 * NoH);
float d2 = dot(d, d);
float b2 = a2 / d2;
return a2 * b2 * b2 * RECIPROCAL_PI;
}
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/shadow/CascadedShadowCasterPass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class CascadedShadowCasterPass extends PipelinePass {
_shadowMatrices: shadowMatrices
} = this;

const { opaqueQueue, alphaTestQueue, transparentQueue } = camera._renderPipeline._cullingResults;
const { opaqueQueue, alphaTestQueue } = camera._renderPipeline._cullingResults;

const scene = camera.scene;
const componentsManager = scene._componentsManager;
Expand Down Expand Up @@ -210,7 +210,6 @@ export class CascadedShadowCasterPass extends PipelinePass {
splitBoundSpheres[offset + 3] = radius * radius;
opaqueQueue.clear();
alphaTestQueue.clear();
transparentQueue.clear();
const renderers = componentsManager._renderers;
const elements = renderers._elements;
for (let k = renderers.length - 1; k >= 0; --k) {
Expand Down
Loading