Adding shadows to planet demo

I’m adding more planets to this demo (i.e. Babylon.js - Planet demo) and am wondering how to cast shadows when a body passes between the light source and the planet. I’ve tried to create a shadow generator and have added the moons mesh via “shadowGenerator.addShadowCaster(moon1.mesh)” and have set planetMesh.receiveShadows = true, but is it possible to cast the shadow on the planet when the planet’s ShaderMaterial is used? It certainly seems like the shadow gets cast when the material is not set on my planet. My moon is just a copy of the planet in that demo using the moon options.

I like the effects that the ShaderMaterial has on generating the planet’s surface, but it doesn’t seem clear on how to cast a shadow on the planet’s surface.

Welcome aboard!

The shadows should be cast. If there are some transparency involved in the shader, you must set shadowGenerator.transparencyShadow= true for the shadows to work.

…tried it, but it did not work unfortunately. Maybe some other texture/material is interfering?

Here is my planet implementation:

export class Planet {
    constructor(scene, {
        camera: camera,
        light: light,
        x: x,
        y: y,
        z: z,
        ox: ox,
        oy: oy,
        oz: oz,
        radius: radius,
        biome: biome,
    }) {
        var options = {
            biomes: "earth",
            clouds: true,
            mapSize: 1024,
            upperColor: new BABYLON.Color3(2, 1, 0),
            lowerColor: new BABYLON.Color3(0, .2, 1),
            haloColor: new BABYLON.Color3(0, .2, 1),
            maxResolution: 64,
            seed: .3,
            cloudSeed: .55,
            lowerClamp: new BABYLON.Vector2(.6, 1),
            groundAlbedo: 1.2,
            cloudAlbedo: 1,
            directNoise: false,
            lowerClip: new BABYLON.Vector2(0, 0),
            range: new BABYLON.Vector2(.3, .35)
        }

        var terrain = new BABYLON.DynamicTexture("random", 128, scene, false, BABYLON.Texture.NEAREST_SAMPLINGMODE);
        var clouds = new BABYLON.DynamicTexture("random", 128, scene, false, BABYLON.Texture.NEAREST_SAMPLINGMODE);

        var updateRandomSurface = function (random) {
            var context = random.getContext();
            var data = context.getImageData(0, 0, 512, 512);
            for (var i = 0; i < 512 * 512 * 4; i++) {
                data.data[i] = Math.random() * 256 | 0
            }
            context.putImageData(data, 0, 0);
            random.update()
        };

        var noiseTexture;
        var cloudTexture;

        var diameter = radius * 2;
        var cx =  (ox > 0) ? ox * Math.cos(0): x;
        var cy =  (oy > 0) ? oy * Math.cos(0): y;
        var cz =  (oz > 0) ? oz * Math.sin(0): z;

        var planetShell = BABYLON.Mesh.CreateSphere("planet", 64, diameter, scene, false, BABYLON.Mesh.FRONTSIDE);
        planetShell.position.x = cx;
        planetShell.position.y = cy;
        planetShell.position.z = cz;
        planetShell.receiveShadows = true;

        // the diameter should be 3 less than the shell 
        var planetBody = BABYLON.Mesh.CreateSphere("planetBody", 32, diameter-3, scene);
        planetBody.isBlocker = true;
        planetBody.checkCollisions = true;
        planetBody.position.x = cx;
        planetBody.position.y = cy;
        planetBody.position.z = cz;

        var shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, {
            vertex: "./space/planet",
            fragment: "./space/planet"
        }, {
            attributes: ["position", "normal", "uv"],
            uniforms: ["world", "worldView", "worldViewProjection", "view", "projection"],
            needAlphaBlending: true
        });
        shaderMaterial.setVector3("cameraPosition", camera.position);
        shaderMaterial.setVector3("lightPosition", light.position);
        planetShell.material = shaderMaterial;
        
        var angle = 0;
        scene.registerBeforeRender(function () {
            var ratio = scene.getAnimationRatio();
            planetShell.rotation.y += .001 * ratio;
            shaderMaterial.setMatrix("rotation", BABYLON.Matrix.RotationY(angle));
            angle -= 4e-4 * ratio;
            shaderMaterial.setVector3("options", new BABYLON.Vector3(options.clouds, options.groundAlbedo, options.cloudAlbedo))
        });

        var generateBiome = function(biome) {
            switch (biome) {
                case "earth":
                    //earthSetup();
                    break;
                case "volcanic":
                    options.upperColor = new BABYLON.Color3(.9, .45, .45);
                    options.lowerColor = new BABYLON.Color3(.6, 0, 0);
                    options.haloColor = new BABYLON.Color3(1, 0, .3);
                    options.seed = .3;
                    options.cloudSeed = .6;
                    options.clouds = false;
                    options.lowerClamp = new BABYLON.Vector2(0, 1);
                    options.maxResolution = 256;
                    options.cloudAlbedo = 0;
                    options.groundAlbedo = 1;
                    options.rings = false;
                    options.directNoise = false;
                    options.lowerClip = new BABYLON.Vector2(0, 0);
                    options.range = new BABYLON.Vector2(.3, .4);
                    break;
                case "jungle":
                    options.upperColor = new BABYLON.Color3(.1, .6, .4);
                    options.lowerColor = new BABYLON.Color3(0, 1, .1);
                    options.haloColor = new BABYLON.Color3(.5, 1, .5);
                    options.seed = .4;
                    options.cloudSeed = .7;
                    options.clouds = true;
                    options.lowerClamp = new BABYLON.Vector2(0, 1);
                    options.maxResolution = 512;
                    options.cloudAlbedo = 1;
                    options.groundAlbedo = 1.1;
                    options.rings = false;
                    options.directNoise = false;
                    options.lowerClip = new BABYLON.Vector2(0, 0);
                    options.range = new BABYLON.Vector2(.2, .4);
                    break;
                case "icy":
                    options.upperColor = new BABYLON.Color3(1, 1, 1);
                    options.lowerColor = new BABYLON.Color3(.7, .7, .9);
                    options.haloColor = new BABYLON.Color3(1, 1, 1);
                    options.seed = .8;
                    options.cloudSeed = .4;
                    options.clouds = true;
                    options.lowerClamp = new BABYLON.Vector2(0, 1);
                    options.maxResolution = 256;
                    options.cloudAlbedo = 1;
                    options.groundAlbedo = 1.1;
                    options.rings = true;
                    options.ringsColor = new BABYLON.Color3(.6, .6, .6);
                    options.directNoise = false;
                    options.lowerClip = new BABYLON.Vector2(0, 0);
                    options.range = new BABYLON.Vector2(.3, .4);
                    break;
                case "desert":
                    options.upperColor = new BABYLON.Color3(.9, .3, 0);
                    options.lowerColor = new BABYLON.Color3(1, .5, .1);
                    options.haloColor = new BABYLON.Color3(1, .5, .1);
                    options.seed = .18;
                    options.cloudSeed = .6;
                    options.clouds = false;
                    options.lowerClamp = new BABYLON.Vector2(.3, 1);
                    options.maxResolution = 512;
                    options.cloudAlbedo = 1;
                    options.groundAlbedo = 1;
                    options.rings = false;
                    options.directNoise = false;
                    options.lowerClip = new BABYLON.Vector2(0, 0);
                    options.range = new BABYLON.Vector2(.3, .4);
                    break;
                case "islands":
                    options.upperColor = new BABYLON.Color3(.4, 2, .4);
                    options.lowerColor = new BABYLON.Color3(0, .2, 2);
                    options.haloColor = new BABYLON.Color3(0, .2, 2);
                    options.seed = .15;
                    options.cloudSeed = .6;
                    options.clouds = true;
                    options.lowerClamp = new BABYLON.Vector2(.6, 1);
                    options.maxResolution = 512;
                    options.cloudAlbedo = 1;
                    options.groundAlbedo = 1.2;
                    options.rings = false;
                    options.directNoise = false;
                    options.lowerClip = new BABYLON.Vector2(0, 0);
                    options.range = new BABYLON.Vector2(.2, .3);
                    break;
                case "moon":
                    options.haloColor = new BABYLON.Color3(0, 0, 0);
                    options.seed = .5;
                    options.clouds = false;
                    options.maxResolution = 256;
                    options.groundAlbedo = .7;
                    options.rings = false;
                    options.directNoise = true;
                    options.lowerClip = new BABYLON.Vector2(.5, .9);
                    break
            }

            if (noiseTexture) {
                noiseTexture.dispose();
                cloudTexture.dispose()
            }
            updateRandomSurface(terrain);
            updateRandomSurface(clouds);
            noiseTexture = new BABYLON.ProceduralTexture("noise", options.mapSize, "./space/noise", scene, null, true, true);
            noiseTexture.setColor3("upperColor", options.upperColor);
            noiseTexture.setColor3("lowerColor", options.lowerColor);
            noiseTexture.setFloat("mapSize", options.mapSize);
            noiseTexture.setFloat("maxResolution", options.maxResolution);
            noiseTexture.setFloat("seed", options.seed);
            noiseTexture.setVector2("lowerClamp", options.lowerClamp);
            noiseTexture.setTexture("randomSampler", terrain);
            noiseTexture.setVector2("range", options.range);
            noiseTexture.setVector3("options", new BABYLON.Vector3(options.directNoise ? 1 : 0, options.lowerClip.x, options.lowerClip.y));
            shaderMaterial.setTexture("textureSampler", noiseTexture);
            cloudTexture = new BABYLON.ProceduralTexture("cloud", options.mapSize, "./space/noise", scene, null, true, true);
            cloudTexture.setTexture("randomSampler", clouds);
            cloudTexture.setFloat("mapSize", options.mapSize);
            cloudTexture.setFloat("maxResolution", 256);
            cloudTexture.setFloat("seed", options.cloudSeed);
            cloudTexture.setVector3("options", new BABYLON.Vector3(1, 0, 1));
            shaderMaterial.setTexture("cloudSampler", cloudTexture);
            shaderMaterial.setColor3("haloColor", options.haloColor);
        };

        generateBiome(biome);

        var cx =  ox * Math.cos(0);
        var cy =  oy * Math.cos(0);
        var cz =  oz * Math.sin(0);
        this.orbitCenter = new BABYLON.Vector3(ox, oy, oz);
        this.planetShell = planetShell;
        this.planetBody = planetBody;
        this.scene = scene;
        this.planetShell.animations = [];
        this.planetBody.animations = [];
        this.radian = 0;
    }
}

This is how I’m using the planet class.

var sun = new BABYLON.PointLight("sun", new BABYLON.Vector3(100, 100, 100), scene);
var planet = new Planet(scene, {camera: camera, light: sun, x: -40, y: -20, z: -100, radius: 30, biome: "earth"});
var moon1 = new Planet(scene, {camera: camera, light: sun, x: -60, y: -10, z: -10, ox: -40, oy: -20, oz: -100, radius: 5, biome: "moon"});
var moon2 = new Planet(scene, {camera: camera, light: sun, x: -60, y: -10, z: -20, ox: -30, oy: -10, oz: -60, radius: 2, biome: "volcanic"});

var shadowGenerator = new BABYLON.ShadowGenerator(1024, sun);
shadowGenerator.transparencyShadow = true;
shadowGenerator.getShadowMap().renderList.push(planet.planetShell);
shadowGenerator.getShadowMap().renderList.push(moon1.planetShell);
shadowGenerator.getShadowMap().renderList.push(moon2.planetShell);
shadowGenerator.usePoissonSampling = true;
shadowGenerator.addShadowCaster(planet.planetShell);
shadowGenerator.addShadowCaster(moon1.planetShell);
shadowGenerator.addShadowCaster(moon2.planetShell);

Note: I also tried to use the planetBody as well (i.e “addShadowCaster(planet.planetBody) and shadowGenerator.getShadowMap().renderList.push(planet.planetBody)”. I don’t even know why the planetBody/planetImposter mesh is even needed in the original demo but I left it in there. Of course I renamed planetImposter -> planetBody in my code. When I comment out “planetShell.material = shaderMaterial” in the code above the shadow appears as expected, but then I don’t have the fancy biome. I wondering if casting a shadow upon the actual shaderMaterial is even suppose to work. I noticed that shaderMaterial.receiveShadows = true also does not work.

You should setup a Playground, it will be easier to look for the problem.

How do I upload the assets (i.e planet.fragment.fx, noise.fragment.fx, etc) to the playground?

Project can be found here:

As you are using a custom shader for the shadow receiver, your best bet would be to recreate your shader in the nme, that way you could have shadows cast on your planet.

However, I see that you are using cube textures, I’m not sure it is supported yet for custom accesses (there is the ReflectionTexture, but you need to use one of the predefined type of access). @Deltakosh?

You can also try to use a CustomMaterial and inject your code inside: the CustomMaterial does handle lights and shadows, as it is essentially a StandardMaterial where you can inject some code at some specific locations.

Ok, I’ll give it a shot! Thanks for the help!