javascriptthree.jsshadows

Three.js - Why is the shadow of these items looking like this?


I'm learning about three.js.

I make a example to practise but the shadow of the items is looking wrong or stranger.

This is my problem: the shadows

Should be like this (this image is of a old tutorial):

Good shadows

And the code is this:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/89/three.js"></script>

<div id="WebGL-salida">
</div>

<script type="text/javascript">
  $(function() {
    var scene = new THREE.Scene();

    var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

    var renderer = new THREE.WebGLRenderer();

    var color = new THREE.Color("rgb(200, 250, 250)");
    renderer.setClearColor(new THREE.Color(color));
    renderer.setSize(window.innerWidth, window.innerHeight);

    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap


    var ejesAyuda = new THREE.AxesHelper(20); //son los ejes de ayuda creo
    scene.add(ejesAyuda);

    var planeGeometry = new THREE.PlaneGeometry(60, 20);

    var planeMaterial = new THREE.MeshLambertMaterial({
      color: 0xcccccc
    });

    var plane = new THREE.Mesh(planeGeometry, planeMaterial);

    plane.receiveShadow = true;


    plane.rotation.x = -0.5 * Math.PI; // -90º
    plane.position.x = 15;
    plane.position.y = 0;
    plane.position.z = 0;

    scene.add(plane);


    var cubeGeometry = new THREE.CubeGeometry(4, 4, 4);


    var cubeMaterial = new THREE.MeshLambertMaterial({
      color: 0xff0000
    });
    var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

    cube.castShadow = true; //con esto le indicamos que queremos que emita sombra

    cube.position.x = -4;
    cube.position.y = 3;
    cube.position.z = 0;

    scene.add(cube);

    var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
    var sphereMaterial = new THREE.MeshLambertMaterial({
      color: 0x7777ff
    });
    var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);

    sphere.castShadow = true; //con esto le indicamos que queremos que emita sombra

    sphere.position.x = 20;
    sphere.position.y = 4;
    sphere.position.z = 2;
    scene.add(sphere);

    camera.position.x = -30;
    camera.position.y = 40;
    camera.position.z = 30;
    camera.lookAt(scene.position);

    var spotLight = new THREE.SpotLight(0xffffff, 0.8);
    spotLight.position.set(-40, 60, -10);
    spotLight.castShadow = true;
    scene.add(spotLight);

    $("#WebGL-salida").append(renderer.domElement);
    renderer.render(scene, camera);
  });
</script>

And please, if you know about a good beginner three.js tutorial or course (I don't care if it isn't free), tell me, because i'm a bit lost in this and like web developer i'm interested in this webGL world :)


Solution

  • You can improve the quality of the shadow by increasing the size (and thus the resolution) of the shadow map (See SpotLight):

    var spotLight = new THREE.SpotLight(0xffffff, 0.8);
    spotLight.position.set(-40, 60, -10);
    spotLight.castShadow = true;
        
    spotLight.shadow.mapSize.width = 2048;
    spotLight.shadow.mapSize.height = 2048;
    

    The quality can be further increased by restricting the light cone angle of the spotlight

    spotLight.angle = Math.PI / 8.0;
    

    These two enhancements together have the effect of increasing the resolution of the shadow map and decreasing the area to which the shadow map is mapped. As a result, more pixels of the shadow map are projected onto less area of the scene and the quality is improved.

    See the code snippet:

    var renderer, camera, scene, controls;
    
    var init = function (){
      scene  = new THREE.Scene();
      camera = new THREE.PerspectiveCamera (45, window.innerWidth/window.innerHeight, 0.1, 1000); 
      renderer = new THREE.WebGLRenderer(); 
      controls = new THREE.OrbitControls( camera, renderer.domElement );
    
      var color = new THREE.Color("rgb(200, 250, 250)");
      renderer.setClearColor(new THREE.Color(color));
      renderer.setSize(window.innerWidth, window.innerHeight);
    
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
    
    
      var ejesAyuda = new THREE.AxesHelper(20); //son los ejes de ayuda creo
      scene.add(ejesAyuda); 
    
      var planeGeometry = new THREE.PlaneGeometry(60, 20);
      var planeMaterial = new THREE.MeshLambertMaterial({color: 0xcccccc});
    
      var plane = new THREE.Mesh(planeGeometry, planeMaterial);
      plane.receiveShadow = true;
      plane.rotation.x = -0.5*Math.PI; // -90º
      plane.position.x = 15;
      plane.position.y = 0;
      plane.position.z = 0;
      scene.add(plane);
    
    
      var cubeGeometry = new THREE.BoxGeometry( 4, 4, 4);
      var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
      var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
      cube.castShadow = true; //con esto le indicamos que queremos que emita sombra
      cube.position.x= -4;
      cube.position.y = 3;
      cube.position.z = 0;
      scene.add(cube);
    
      var sphereGeometry = new THREE.SphereGeometry (4, 20, 20);
      var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
      var sphere = new THREE.Mesh (sphereGeometry, sphereMaterial);
      sphere.castShadow = true; //con esto le indicamos que queremos que emita sombra
      sphere.position.x = 20;
      sphere.position.y = 4;
      sphere.position.z = 2;
      scene.add(sphere);
    
      camera.position.x = -30;
      camera.position.y = 40;
      camera.position.z = 30;
      camera.lookAt(scene.position); 
    
      var spotLight = new THREE.SpotLight(0xffffff, 0.8);
      spotLight.position.set(-40, 60, -10);
      spotLight.castShadow = true;
      spotLight.angle = Math.PI / 8.0;
      spotLight.shadow.mapSize.width = 2048;
      spotLight.shadow.mapSize.height = 2048;
      scene.add(spotLight);
    
      document.getElementById("WebGL-salida").append(renderer.domElement);
      resize();
      window.onresize = resize;
    };
    
    function animate() {
        requestAnimationFrame( animate );
        renderer.render( scene, camera );
    }
    
    function resize() {
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
    //controls.handleResize();
    }
    
    init();
    animate();
    <!--script src="https://threejs.org/build/three.min.js"></script-->
    <!--script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script-->
    <script src="https://cdn.jsdelivr.net/npm/three@0.146/build/three.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.146/examples/js/controls/OrbitControls.js"></script>
    <div id="WebGL-salida">
    </div>