Hi,
I’ve been playing with a shader material and added some perlin noise to it.
The scene is simple:
- a sphere
- a light source
- a plane with a custom vertex/fragment shader code
After a lot of researching I’ve managed to get the shadow casted by the sphere working on the ShaderMaterial by adding the required includes in the vertex/shader code.
Everything went well apart from “self-shadows”. I just cannot get the “waves” on the plane to have their own shadows.
Following different topics and forums I went ahead and added a custom depth material to the plane mesh but even that doesn’t seem to do the trick.
I have a codepen here with the entire thing.
What am I doing wrong?
Any help appreciated.
Thank you!
Full code here:
import * as THREE from "https://cdn.skypack.dev/three@0.156.1";
import OrbitControls from "https://cdn.skypack.dev/threejs-orbit-controls";
const canvas = document.getElementById("c");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// webgl Renderer ------------------
const webglRenderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: true
});
webglRenderer.outputColorSpace = THREE.SRGBColorSpace;
webglRenderer.toneMapping = THREE.ACESFilmicToneMapping;
webglRenderer.toneMappingExposure = 2.3;
webglRenderer.shadowMap.enabled = true;
webglRenderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Scene ----------------------------
const scene = new THREE.Scene();
// Camera ---------------------------
const perspectiveCamera = new THREE.PerspectiveCamera(
35,
canvas.width / canvas.height,
0.1,
100
);
perspectiveCamera.position.x = -10;
perspectiveCamera.position.y = -10;
perspectiveCamera.position.z = 20;
perspectiveCamera.lookAt(scene.position);
scene.add(perspectiveCamera);
// OrbitControls ---------------------
const orbitControls = new OrbitControls(perspectiveCamera, canvas);
orbitControls.enableDamping = true;
orbitControls.enableZoom = true;
orbitControls.enablePan = true;
orbitControls.maxPolarAngle = Math.PI;
orbitControls.target.set(0, 0, 0);
// Lights ----------------------------
const directionalLight = new THREE.DirectionalLight(0xffffff, 3, 100, 1);
directionalLight.position.set(3, 1, 2);
directionalLight.castShadow = true;
directionalLight.shadow.camera.near = 0.01;
directionalLight.shadow.camera.far = 30;
directionalLight.shadow.camera.top = 15;
directionalLight.shadow.camera.bottom = -15;
directionalLight.shadow.camera.left = -15;
directionalLight.shadow.camera.right = 15;
directionalLight.shadow.mapSize.set(2048, 2048);
directionalLight.shadow.normalBias = 0.05;
scene.add(directionalLight);
const helper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
scene.add(helper);
// Sphere -----------------------------
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
const sphereMaterial = new THREE.MeshLambertMaterial({
color: 0xca6234
});
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial, 2);
sphereMesh.position.set(0, 0, 2);
sphereMesh.castShadow = true;
sphereMesh.receiveShadow = true;
scene.add(sphereMesh);
// Plane ------------------------------
const planeGeometry = new THREE.PlaneGeometry(15, 15, 800, 800);
const vertex_shader = `
#include <common>
#include <shadowmap_pars_vertex>
varying vec3 currentPosition;
uniform float time;
vec4 mod289(vec4 x)
{
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x)
{
return mod289(((x*34.0)+10.0)*x);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
vec2 fade(vec2 t) {
return t*t*t*(t*(t*6.0-15.0)+10.0);
}
// Classic Perlin noise
float cnoise(vec2 P)
{
vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
Pi = mod289(Pi); // To avoid truncation effects in permutation
vec4 ix = Pi.xzxz;
vec4 iy = Pi.yyww;
vec4 fx = Pf.xzxz;
vec4 fy = Pf.yyww;
vec4 i = permute(permute(ix) + iy);
vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
vec4 gy = abs(gx) - 0.5 ;
vec4 tx = floor(gx + 0.5);
gx = gx - tx;
vec2 g00 = vec2(gx.x,gy.x);
vec2 g10 = vec2(gx.y,gy.y);
vec2 g01 = vec2(gx.z,gy.z);
vec2 g11 = vec2(gx.w,gy.w);
vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
g00 *= norm.x;
g01 *= norm.y;
g10 *= norm.z;
g11 *= norm.w;
float n00 = dot(g00, vec2(fx.x, fy.x));
float n10 = dot(g10, vec2(fx.y, fy.y));
float n01 = dot(g01, vec2(fx.z, fy.z));
float n11 = dot(g11, vec2(fx.w, fy.w));
vec2 fade_xy = fade(Pf.xy);
vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
return 2.3 * n_xy;
}
void main() {
#include <beginnormal_vertex>
#include <defaultnormal_vertex>
#include <begin_vertex>
#include <worldpos_vertex>
#include <shadowmap_vertex>
vec3 pos = position;
pos.z += cnoise(pos.xy * 0.4 + time * 0.1) * 1.5;
pos.z += cnoise(pos.xy * 0.4 - time * 0.1) * 0.5;
currentPosition = pos;
gl_Position += projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`;
const fragment_shader = `
#include <common>
#include <packing>
#include <lights_pars_begin>
#include <shadowmap_pars_fragment>
#include <shadowmask_pars_fragment>
varying vec3 currentPosition;
void main() {
vec3 shadowColor = vec3(0, 0, 0);
float shadowPower = 0.5;
vec3 color = vec3(0.0, 0.51, 0.91);
color.r = clamp(currentPosition.z * 1.5, 0.0, 1.0);
color.g = smoothstep(0.0, 1.0, currentPosition.z * 1.5);
gl_FragColor = vec4( mix( color, shadowColor, (1.0 - getShadowMask() ) * shadowPower), 1.0);
}
`;
const planeMaterial = new THREE.ShaderMaterial({
vertexShader: vertex_shader,
fragmentShader: fragment_shader,
lights: true,
uniforms: THREE.UniformsUtils.merge([
THREE.UniformsLib.lights,
{
time: { value: 0 }
}
])
});
// Custom depth material for the plane mesh -----
const vertex_shader_depth_beforeVoidMain = `
varying vec2 vUv;
varying vec3 currentPosition;
uniform float time;
vec4 mod289(vec4 x)
{
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x)
{
return mod289(((x*34.0)+10.0)*x);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
vec2 fade(vec2 t) {
return t*t*t*(t*(t*6.0-15.0)+10.0);
}
// Classic Perlin noise
float cnoise(vec2 P)
{
vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
Pi = mod289(Pi); // To avoid truncation effects in permutation
vec4 ix = Pi.xzxz;
vec4 iy = Pi.yyww;
vec4 fx = Pf.xzxz;
vec4 fy = Pf.yyww;
vec4 i = permute(permute(ix) + iy);
vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
vec4 gy = abs(gx) - 0.5 ;
vec4 tx = floor(gx + 0.5);
gx = gx - tx;
vec2 g00 = vec2(gx.x,gy.x);
vec2 g10 = vec2(gx.y,gy.y);
vec2 g01 = vec2(gx.z,gy.z);
vec2 g11 = vec2(gx.w,gy.w);
vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
g00 *= norm.x;
g01 *= norm.y;
g10 *= norm.z;
g11 *= norm.w;
float n00 = dot(g00, vec2(fx.x, fy.x));
float n10 = dot(g10, vec2(fx.y, fy.y));
float n01 = dot(g01, vec2(fx.z, fy.z));
float n11 = dot(g11, vec2(fx.w, fy.w));
vec2 fade_xy = fade(Pf.xy);
vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
return 2.3 * n_xy;
}
`;
const vertex_shader_depth_innerVoidMain = `
vec3 pos = position;
pos.z += cnoise(pos.xy * 0.4 + time * 0.1) * 1.5;
pos.z += cnoise(pos.xy * 0.4 - time * 0.1) * 0.5;
currentPosition = pos;
gl_Position += projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
`;
const depthMaterial = new THREE.MeshDepthMaterial({
side: THREE.DoubleSide,
depthTest: true,
depthWrite: true
});
depthMaterial.onBeforeCompile = onBeforeCompile;
// Extend the MeshDepthMaterial and insert the same vertex transformations
// to match the plane vertex shader
function onBeforeCompile(shader) {
shader.vertexShader = shader.vertexShader.replace(
`void main()`,
`${vertex_shader_depth_beforeVoidMain}\n\nvoid main()`
);
shader.vertexShader = shader.vertexShader.replace(
`#include <uv_vertex>`,
`${vertex_shader_depth_innerVoidMain}\n#include <uv_vertex>`
);
shader.uniforms.time = { value: 0.0 };
depthMaterial.userData.shader = shader;
}
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.position.set(0, 1, 0);
planeMesh.castShadow = true;
planeMesh.receiveShadow = true;
planeMesh.customDepthMaterial = depthMaterial;
scene.add(planeMesh);
function updateUniforms(t) {
planeMesh.material.uniforms.time.value = t;
if (depthMaterial.userData.shader) {
depthMaterial.userData.shader.uniforms.time.value = t;
}
}
function render() {
webglRenderer.render(scene, perspectiveCamera);
}
function update(t) {
updateUniforms(t * 0.001);
render();
orbitControls.update();
requestAnimationFrame(update);
}
update();