Engine Only Shadows

Hey guys, I hope this is not just me. But it seems like the powers that be at PlayCanvas are purposely WITH HOLDING all the crucial documentation on how to do anything.

For example Shadows… EVERY PRINTED WORD on the internet that has ANYTHING to do with shadows and PlayCanvas (which is not much) say Just goto Editor and Check this box… Im sorry… BUT What kind of developer can just live with that… No explanation on what you need to really setup via code (WHICH YOU GUYS WHO MAKE THE EDITOR… HAVE TO HAVE DOCS FOR).

I mean its like saying: Dont worry your pretty little head about how things are getting done, you just pay us, use our ONLINE EDITOR ONLY (even if it does not fit with your workflow) and just be happy… What is that about ???

I get that you need to make money and want people to pay for your on-line editor. But what about us FOLKS who need to know how to get to the nitty gritty and setup things like Shadows, Skyboxes etc… I guess we are just OUT OF LUCK.

We all that being said… I am going to beg for help here on the forum. Because there is NO PRINTED WORDS ON THE ENTIRE PLANET (that i can find) that details all your need to do as a developer to get lights and shadows going (Beside of course… Just our Editor and dont worry about it)… Im just guessing here

i have castShadows and receive shadows checked on the Model Component

I am setting the light Component Up:

        let lightOptions:any = {};
        let lightOffset:number = 0;
        let lightScale:number = 0;
        let lightAngle:number = component.spotangle * component.conefactor;
        let lightType:number = component.type.Key;
        if (lightType === 0) { // SPOT_LIGHT
            lightOptions.type = "spot";
            lightScale = component.spotscale;
            lightOffset = component.angleoffset;
        } else if (lightType === 2) { // POINT_LIGHT
            lightOptions.type = "point";
            lightScale = component.pointscale;
            lightOffset = 0;
        } else { // DIRECTIONAL_LIGHT
            lightOptions.type = "directional";
            lightScale = component.directionalscale;
            lightOffset = component.angleoffset;
        }
        // ..
        // TODO: Setup light cookie texture options
        // ..
        lightOptions.color = CanvasTools.ParseColorProperty(component.color);
        lightOptions.range = component.range;
        lightOptions.intensity = component.intensity * lightScale;
        lightOptions.innerConeAngle = lightAngle;
        lightOptions.outerConeAngle = lightAngle + 5;
        // Setup shadaw map generation options
        let shadows:number = component.type_aux.Key;
        lightOptions.castShadows = (shadows !== 0);
        lightOptions.shadowBias = component.bias;
        lightOptions.normalOffsetBias = component.normalbias;
        let resolution:number = component.resolution.Key;
        if (resolution === 1) { // LOW_RESOLUTION
            lightOptions.shadowResolution = 512;
        } else if (resolution === 2) { // MEDIUM_RESOLUTION
            lightOptions.shadowResolution = 1024;
        } else if (resolution === 3) { // HIGH_RESOLUTION
            lightOptions.shadowResolution = 2048;
        } else if (resolution === 4) { // VERY_HIGH_RESOLUTION
            lightOptions.shadowResolution = 4096;
        } else { // DEFAULT_RESOLUTION
            lightOptions.shadowResolution = 1024;
        }
        entity.addComponent("light", lightOptions);

So light bias = 0.05 and normal bias = 0.5… But my shadow edges look like crap…

It also ssays somewhere that you can change the change type to use HARD or SOFT shadows…

BUT NOTHING on how to actually do so… What the heck do i set to enable hard or soft shadows…

I cant be the only one in world that feels this way… How is an indie developer ever supposed to really make a game with the PlayCanvas Engine…

OR ARE WE NOT SUPPOSED TO… JUST PAY, USE THE ONLINE EDITOR AND DONT WORRY YOUR PRETTY LITTLE HEAD ABOUT ACTUALLY LEARNING THE INS AND OUT OF THE ENGINE…

Example Shadows in Online Edit… Look GOOD

Example done from Engine only code… Like CRAPPY

P.S.

I am so sorry to be the A-HOLE that is bringing this up… But i dont know what else to do… where else to look for answers… Again sorry so negative :slight_smile:

Define what you mean by ‘crappy’? The difference between the two seems to be the ambient colour of the scene. The editor defaults to #333333 whereas yours seems to be #000000.

Could you share the full code? It makes it easier to help work out what the exact problem is and provide an answer.

image
vs
image

The first shot was a white cube on a plane made in editor… shadow on ground looks nice and crisp.

I set the same plane and cube in engine only code… there is only bias and normal bias that I see to tweek … but the same values I use for bias in editor but in engine only looks Blocky and fuzzy… I even play different higher and lower values but still never looks the editor version.

There has to be more to it than trying to tweek bias… something the editor does for you I’m missing… something

I guess the second shot you can’t tell how blocky and fuzzy the edges are. I don’t see in code how to set to to hard or soft edges , maybe that has to do with it too

You know… there are examples on github…

Have you tried changing the shadow type? In terms of what will affect the shadows fidelity, there is the shadow types (different types expose different tuning parameters, including blur modes) and there is also the shadow resolution. Pretty sure it’s all exposed via the API too.

Does anyone know how to enable Soft Shadows ???

Also… what is the common practice for mobile… should we be trying to use real-time shadows on mobile. Should not try even use real time shadows on mobile… what about lights… should those be real time… or should we be trying to bake all lighting into static lightmaps and use some sort emmisve texture on materials

^^^^

If you can use a pre baked lightmap, AO, that’s prefable. I’ve found one or two fixed lights to be fine with one casting shadows. It also depends on how complex the scene is and how realistic you would like the lighting to be.

Thank yanstar… one last thing before I move to skyboxes… how do a set soft shadows… is there a switch for that … or is simply using the variance shadow map VSM Instead the PCM … considered making is soft shadows ???

I’ve asked for the code that you are using several times so I can have a look at how you’ve set the scene.

I just need to know what is the deal wit soft shadows. How to turn them on…

But as far as seeing what the project code looks …

I am not making a traditional script component. So i hope this is not hard to follow.

But here goes…

I am extending the playcanvas-gltf.js to support
CVTOOLS_unity_metadata … take a look at what that is https://github.com/MackeyK24/glTF/tree/master/extensions/2.0/Vendor/CVTOOLS_unity_metadata

So in my updates to playcanvas-gltf parsing i am check nodes to see if they have any “Extra MetaData” on them if so, i am creating the native PlayCnvas component using the MetaData Suppiled… So to parse a Light (Which has the shadow setup code) here it is:

    private static ParseLightNode(app:pc.Application, entity:pc.Entity, data:any, resources:any, component:any):void {
        let lightOptions:any = {};
        let lightOffset:number = 0;
        let lightScale:number = 0;
        let lightAngle:number = component.spotangle * component.conefactor;
        let lightType:number = component.type.Key;
        if (lightType === 0) { // SPOT_LIGHT
            lightOptions.type = "spot";
            lightScale = component.spotscale;
            lightOffset = component.angleoffset;
        } else if (lightType === 2) { // POINT_LIGHT
            lightOptions.type = "point";
            lightScale = component.pointscale;
            lightOffset = 0;
        } else if (lightType === 3) { // AREA_LIGHT
            lightOptions.type = "point";
            lightScale = component.pointscale;
            lightOffset = 0;
        } else { // DIRECTIONAL_LIGHT
            lightOptions.type = "directional";
            lightScale = component.directionalscale;
            lightOffset = component.angleoffset;
        }
        // ..
        // TODO: Setup light cookie texture options
        // ..
        lightOptions.color = CanvasTools.ParseColorProperty(component.color);
        lightOptions.range = component.range;
        lightOptions.intensity = component.intensity * lightScale;
        lightOptions.innerConeAngle = lightAngle;
        lightOptions.outerConeAngle = lightAngle + 5;
        // Prepare shadow map quality settings
        // let qualitylevel:number = component.qualitylevel;
        // let qualitynames:string[] = component.qualitynames;
        // let shadowdistance:number = component.shadowdistance;
        // Setup shadaw map generation options
        let shadows:number = component.type_aux.Key;
        lightOptions.shadowType = component.shadowtype;
        lightOptions.castShadows = (shadows !== 0);
        lightOptions.shadowBias = component.bias;
        lightOptions.normalOffsetBias = component.normalbias;
        lightOptions.vsmBlurSize = component.vsmblursize;
        lightOptions.vsmBlurMode = component.vsmblurmode;
        lightOptions.vsmBias = component.vsmbias;
        // Setup shadow map resolution options
        let resolution:number = component.resolution.Key;
        if (resolution === 1) { // LOW_RESOLUTION
            lightOptions.shadowResolution = 1024;
        } else if (resolution === 2) { // MEDIUM_RESOLUTION
            lightOptions.shadowResolution = 2048;
        } else if (resolution === 3) { // HIGH_RESOLUTION
            lightOptions.shadowResolution = 3072;
        } else if (resolution === 4) { // VERY_HIGH_RESOLUTION
            lightOptions.shadowResolution = 4096;
        } else { // TODO: GET QUALITY SETTINGS RESOLUTION
            lightOptions.shadowResolution = 2048;
        }
        // ..
        // Note: PlayCanvas Lights Require A Child Pivot Entity For Angle Offsets
        // ..
        let pivot = new pc.Entity(entity.getName() + ".Pivot");
        entity.addChild(pivot);
        if (lightOffset !== 0) {
            pivot.rotateLocal(lightOffset, 0, 0);
            pivot.rotateLocal(0, lightOffset, 0);
        }
        pivot.addComponent("light", lightOptions);
        if (pivot.light == null) {
            console.warn("Failed to add light component for entity: " + pivot.getName());
        }
    }

an example light meta data that has the actual values:

{
      "rotation": [
        0.408217877,
        -0.234569684,
        -0.109381631,
        -0.8754261
      ],
      "scale": [
        1.0,
        1.0,
        0.99999994
      ],
      "translation": [
        0.0,
        10.0,
        0.0
      ],
      "name": "Sky Light",
      "extras": {
        "metadata": {
          "alias": "entity",
          "id": "cf79bdf1-f31c-4dfd-8f79-86693e6578c0",
          "tag": "Untagged",
          "name": "Sky Light",
          "prefab": false,
          "layer": 0,
          "layername": "Default",
          "renderer": null,
          "components": [
            {
              "alias": "light",
              "type": {
                "Key": 1,
                "Value": "Directional"
              },
              "color": {
                "r": 0.7908334,
                "g": 0.8207547,
                "b": 0.2981043,
                "a": 1.0
              },
              "intensity": 1.0,
              "range": 10.0,
              "spotangle": 30.0,
              "cookiesize": 10.0,
              "type_aux": {
                "Key": 1,
                "Value": "Hard Shadows"
              },
              "resolution": {
                "Key": 0,
                "Value": "Use Quality Settings"
              },
              "customresolution": -1,
              "strength": 1.0,
              "bias": 0.05,
              "normalbias": 0.4,
              "nearplane": 0.2,
              "cookie": null,
              "drawhalo": false,
              "probeocclusionlightindex": -1,
              "occlusionmaskchannel": -1,
              "lightmapbaketype": {
                "Key": 2,
                "Value": "Realtime"
              },
              "mixedlightingmode": {
                "Key": 2,
                "Value": "Shadowmask"
              },
              "isbaked": false,
              "flare": null,
              "rendermode": {
                "Key": 0,
                "Value": "Auto"
              },
              "cullingmask": -1,
              "lightmapping": {
                "Key": 2,
                "Value": "Realtime"
              },
              "lightshadowcastermode": 0,
              "areasize": {
                "x": 1.0,
                "y": 1.0
              },
              "x": 1.0,
              "y": 1.0,
              "bounceintensity": 1.0,
              "colortemperature": 6570.0,
              "usecolortemperature": false,
              "shadowradius": 0.0,
              "shadowangle": 0.0,
              "directionalscale": 1.25,
              "pointscale": 1.0,
              "spotscale": 1.0,
              "conefactor": 0.4,
              "angleoffset": 90.0,
              "qualitylevel": 3,
              "qualitynames": [
                "Very Low",
                "Low",
                "Medium",
                "High",
                "Very High",
                "Ultra"
              ],
              "shadowdistance": 40.0,
              "shadowtype": 4,
              "vsmblursize": 11,
              "vsmblurmode": 1,
              "vsmbias": 0.0025
            }
          ]
        }
      }
    }

I think this might be your reason. The editor defaults this 16 whereas the engine defaults to 40.

Shadow distance set at 40:
image

Shadow distance set at 16:
image

Using shadowType: pc.SHADOW_VSM8 ‘softens’ the edges

Full code (we use the firstperson script from here: https://github.com/playcanvas/engine/tree/master/examples/camera/first_person

<!doctype html>
<html>
<head>
    <script src="../../../build/output/playcanvas-latest.js"></script>
    <link href="../../style.css" rel="stylesheet" />
</head>

<body>
    <!-- The canvas element -->
    <canvas id="application-canvas"></canvas>

    <!-- The script -->
    <script>
        // ***********    Initialize application   *******************
        var canvas = document.getElementById("application-canvas");
        // focus the canvas for keyboard input
        canvas.focus();

        // Create the application and start the update loop
        var app = new pc.Application(canvas, {
            mouse: new pc.Mouse(canvas),
            keyboard: new pc.Keyboard(window)
        });

        // Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
        app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
        app.setCanvasResolution(pc.RESOLUTION_AUTO);

        app.scene.ambientLight = new pc.Color(0.2, 0.2, 0.2);
    </script>

    <script src="first_person_camera.js"></script>

    <script>        
        app.start();

        // ***********    Helper functions    *******************

        function createMaterial (color) {
            var material = new pc.PhongMaterial();
            material.diffuse = color;
            // we need to call material.update when we change its properties
            material.update()
            return material;
        }

        function createBox (position, size, material) {
            // create an entity and add a model component of type 'box'
            var box = new pc.Entity();
            box.addComponent("model", {
                type: "box",
                castShadows: true,
                recieveShadows: true
            });

            box.model.material = material;

            // move the box
            box.setLocalPosition(position);
            box.setLocalScale(size);

            // add the box to the hierarchy
            app.root.addChild(box);
        }


        // ***********    Create Boxes    *******************

        // create a few materials for our boxes
        var red = createMaterial(new pc.Color(1,0,0));
        var white = createMaterial(new pc.Color(1,1,1));

        // create a few boxes in our scene
        for (var i=0; i<3; i++) {
            for (var j=0; j<2; j++) {
                createBox(new pc.Vec3(i*2, 0, j*4), pc.Vec3.ONE, red);
            }
        }

        // create a floor
        createBox(new pc.Vec3(0, -0.5, 0), new pc.Vec3(10, 0.1, 10), white);

        // ***********    Create lights   *******************

        // make our scene prettier by adding a directional light
        var light = new pc.Entity();
        light.addComponent("light", {
            type: "directional",
            color: new pc.Color(1, 1, 1),
            intensity: 1,
            affectDynamic: true,
            castShadows: true,
            shadowBias: 0.005,
            normalOffsetBias: 0.5,
            shadowResolution: 2048,
            shadowDistance: 16,
            shadowType: pc.SHADOW_VSM16
        });
        light.setLocalPosition(0, 0, 0);
        light.setLocalEulerAngles(20, 30, 0);

        // add the light to the hierarchy
        app.root.addChild(light);
        
        console.log(light);

        // ***********    Create camera    *******************

        // Create an Entity with a camera component
        var camera = new pc.Entity();
        camera.addComponent("camera", {
            clearColor: new pc.Color(0.5, 0.5, 0.8),
            nearClip: 0.3,
            farClip: 30
        });

        // add the first person camera script to the camera
        camera.addComponent("script");
        camera.script.create("firstPersonCamera");

        // add the camera to the hierarchy
        app.root.addChild(camera);

        // Move the camera a little further away
        camera.translate(0, 0, 2);

    </script>
</body>
</html>

Holy crap… now that’s perfect. The shadow distance makes sense … I will check out when i can… so VSM is what is considered SOFT shadows… is it a big difference between The VSM 8, 16 and 32 … 16 seems like a good default like your sample

Thank you so much Steven … You da man
Thanks again
:grinning:

1 Like

Thanks Steven… Shadow Distance plays a big role… that did it :smile: