Closed gfcrba closed 1 year ago
Hello and thanks!
For now you need to create a custom material to support this. Maybe I can add this feature in StandardMaterial at a later time.
This is the code for the material (please note that this code will only work when using WebGL 2, let me know if you need to support WebGL 1 as well.):
let vert = `#version 300 es
precision highp float;
in vec3 a_Position;
uniform mat4 u_Model;
uniform mat4 u_ViewProjection;
uniform mat4 u_LightViewProjectionMatrix;
out vec4 v_PositionLightSpace;
void main() {
vec4 pos = u_Model * vec4(a_Position, 1.0);
v_PositionLightSpace = u_LightViewProjectionMatrix * pos;
gl_Position = u_ViewProjection * u_Model * vec4(a_Position, 1.0);
}`
let frag = `#version 300 es
precision highp float;
in vec4 v_PositionLightSpace;
out vec4 fragColor;
uniform vec3 u_Color;
uniform sampler2D u_ShadowSampler;
uniform vec4 u_ShadowColor;
float linstep(float low, float high, float v) {
return clamp((v-low) / (high-low), 0.0, 1.0);
}
float getShadowContribution() {
vec3 coords = v_PositionLightSpace.xyz / v_PositionLightSpace.w * 0.5 + 0.5;
if (coords.z < 0.01 || coords.z > 0.99 || coords.x < 0.01 || coords.x > 0.99 || coords.y < 0.01 || coords.y > 0.99) {
return 1.0;
}
vec2 moments = vec2(1.0) - texture(u_ShadowSampler, coords.xy).xy;
float p = step(coords.z, moments.x);
float variance = max(moments.y - moments.x * moments.x, 0.00002);
float d = coords.z - moments.x;
float pMax = linstep(0.2, 1.0, variance / (variance + d*d));
return min(max(p, pMax), 1.0);
}
void main() {
float shadow = 1.0 - getShadowContribution();
vec4 shadowColor = u_ShadowColor * vec4(shadow);
float shadowAlpha = shadow * shadowColor.a;
fragColor = vec4(shadowColor.rgb * shadowAlpha, shadowAlpha);
}`
class TransparentShadowReceiverMaterial extends PIXI3D.Material {
constructor(shadowCastingLight) {
super()
this._shadowCastingLight = shadowCastingLight
this.shadowColor = new PIXI3D.Color(0, 0, 0, 0.25)
}
updateUniforms(mesh, shader) {
shader.uniforms.u_Model = mesh.worldTransform.array
shader.uniforms.u_ViewProjection = PIXI3D.Camera.main.viewProjection.array
shader.uniforms.u_Color = [255, 0, 255]
if (this._shadowCastingLight) {
shader.uniforms.u_ShadowSampler = this._shadowCastingLight.shadowTexture
shader.uniforms.u_LightViewProjectionMatrix = this._shadowCastingLight.lightViewProjection
shader.uniforms.u_ShadowColor = this.shadowColor.rgba
}
}
createShader() {
return new PIXI3D.MeshShader(PIXI.Program.from(vert, frag))
}
}
Here is a complete example:
let app = new PIXI.Application({
backgroundColor: 0xff00ff, resizeTo: window, antialias: true
})
document.body.appendChild(app.view)
let control = new PIXI3D.CameraOrbitControl(app.view)
control.enableDamping = true
app.loader.add("assets/chromatic/diffuse.cubemap")
app.loader.add("assets/chromatic/specular.cubemap")
app.loader.add("assets/teapot/teapot.gltf")
let vert = `#version 300 es
precision highp float;
in vec3 a_Position;
uniform mat4 u_Model;
uniform mat4 u_ViewProjection;
uniform mat4 u_LightViewProjectionMatrix;
out vec4 v_PositionLightSpace;
void main() {
vec4 pos = u_Model * vec4(a_Position, 1.0);
v_PositionLightSpace = u_LightViewProjectionMatrix * pos;
gl_Position = u_ViewProjection * u_Model * vec4(a_Position, 1.0);
}`
let frag = `#version 300 es
precision highp float;
in vec4 v_PositionLightSpace;
out vec4 fragColor;
uniform vec3 u_Color;
uniform sampler2D u_ShadowSampler;
uniform vec4 u_ShadowColor;
float linstep(float low, float high, float v) {
return clamp((v-low) / (high-low), 0.0, 1.0);
}
float getShadowContribution() {
vec3 coords = v_PositionLightSpace.xyz / v_PositionLightSpace.w * 0.5 + 0.5;
if (coords.z < 0.01 || coords.z > 0.99 || coords.x < 0.01 || coords.x > 0.99 || coords.y < 0.01 || coords.y > 0.99) {
return 1.0;
}
vec2 moments = vec2(1.0) - texture(u_ShadowSampler, coords.xy).xy;
float p = step(coords.z, moments.x);
float variance = max(moments.y - moments.x * moments.x, 0.00002);
float d = coords.z - moments.x;
float pMax = linstep(0.2, 1.0, variance / (variance + d*d));
return min(max(p, pMax), 1.0);
}
void main() {
float shadow = 1.0 - getShadowContribution();
vec4 shadowColor = u_ShadowColor * vec4(shadow);
float shadowAlpha = shadow * shadowColor.a;
fragColor = vec4(shadowColor.rgb * shadowAlpha, shadowAlpha);
}`
class TransparentShadowReceiverMaterial extends PIXI3D.Material {
constructor(shadowCastingLight) {
super()
this._shadowCastingLight = shadowCastingLight
this.shadowColor = new PIXI3D.Color(0, 0, 0, 0.25)
}
updateUniforms(mesh, shader) {
shader.uniforms.u_Model = mesh.worldTransform.array
shader.uniforms.u_ViewProjection = PIXI3D.Camera.main.viewProjection.array
shader.uniforms.u_Color = [255, 0, 255]
if (this._shadowCastingLight) {
shader.uniforms.u_ShadowSampler = this._shadowCastingLight.shadowTexture
shader.uniforms.u_LightViewProjectionMatrix = this._shadowCastingLight.lightViewProjection
shader.uniforms.u_ShadowColor = this.shadowColor.rgba
}
}
createShader() {
return new PIXI3D.MeshShader(PIXI.Program.from(vert, frag))
}
}
app.loader.load((_, resources) => {
PIXI3D.LightingEnvironment.main.imageBasedLighting = new PIXI3D.ImageBasedLighting(
resources["assets/chromatic/diffuse.cubemap"].cubemap,
resources["assets/chromatic/specular.cubemap"].cubemap
)
let model = app.stage.addChild(
PIXI3D.Model.from(resources["assets/teapot/teapot.gltf"].gltf))
model.y = -0.8
model.meshes.forEach(mesh => {
mesh.material.exposure = 1.3
})
let ground = app.stage.addChild(PIXI3D.Mesh3D.createPlane())
ground.y = -0.8
ground.scale.set(10, 1, 10)
let directionalLight = Object.assign(new PIXI3D.Light(), {
intensity: 1,
type: "directional"
})
directionalLight.rotationQuaternion.setEulerAngles(25, 120, 0)
PIXI3D.LightingEnvironment.main.lights.push(directionalLight)
let shadowCastingLight = new PIXI3D.ShadowCastingLight(
app.renderer, directionalLight, { shadowTextureSize: 1024, quality: PIXI3D.ShadowQuality.medium })
shadowCastingLight.softness = 1
shadowCastingLight.shadowArea = 15
ground.material = new TransparentShadowReceiverMaterial(shadowCastingLight)
let pipeline = app.renderer.plugins.pipeline
pipeline.enableShadows(ground, shadowCastingLight)
pipeline.enableShadows(model, shadowCastingLight)
})
Should look like this
Thank you very much. It works like a charm! You saved me a ton of time. Do you have a patreon or smth for small thank you?
No problem!
Oh just one notice for ios (at least for 16.4).
uniform vec3 u_Color;
Not used in fragment shader, so it optimise it somehow and reduces uniform buffer so data passing become corrupted.
Hope this will help someone as well.
Hey! Thanks for great library it works great!
I've got a question and haven't found answer in docs, but sure it can be helpful in some situations for everyone. I need to have fully transparent plane on stage the only purpose of which is to render shadows on it.
Can I do it somehow with tools we have right now or maybe you can point on approach I should use to do it?