Custom light / shadow pipeline - Issue with PointLight

Hello everyone,

Background: I need to override the way three.js renders different types of lights and their shadowmaps by making several optimizations and gaining more flexibility to finalize my hybrid rasterized/pathtracing rendering pipeline.

So I simply “copy/pasted” snippets of code from the WebGLLights and WebGLShadowMap classes, adapting them to my needs (for now, my approach is pretty naive; I’d like it to work already!).

Everything is working fine in my custom rendering shader with the uniforms defined in the updateLight() method (var shadowsData). I get ISO illumination with the vanilla three.js renderer. It’s also fine for applying the shadowmaps of the different lights, except for the PointLights.

Problem: This is the whole problem: the PointLights don’t seem to be generating their shadowMap correctly. If I inspect my shadow texture’s data buffer, I only get RGBA values ​​of 1. Everything is white. Yet, I don’t see any difference between my code and the vanilla code regarding the rendering of the PointLights’ shadows.

I must be missing something; my doubts are directed towards the distanceMaterial and its rendering.

Rendering a DirectionalLight:

Rendering a PointLight:

renderLight(renderer, entity, camera) {
    const shadow = entity.shadow;
    const renderTarget = shadow.map;

    if (!renderTarget || entity.entities.length === 0) return false;

    renderer.getViewport(_oldViewPort);
    const ogRenderTarget = renderer.getRenderTarget();
    const ogRenderType = renderer.renderType;
		const activeCubeFace = renderer.getActiveCubeFace();
    renderer.getClearColor(ogClearColor);
    const oldScissorTest = renderer.getScissorTest();
		const activeMipmapLevel = renderer.getActiveMipmapLevel();

    renderer.setRenderTarget(renderTarget);
    renderer.renderType = 'DepthShadowPass';
    renderer.setClearColor(clearColor, 1);
    renderer.setScissorTest( false );
    renderer.clear();

    const viewportCount = shadow.getViewportCount();

    renderTarget.setSize(this.shadowsData.lights[entity.indexInArray].shadowMapSize.x, this.shadowsData.lights[entity.indexInArray].shadowMapSize.y);

    _viewportSize.copy(this.shadowsData.lights[entity.indexInArray].viewportSize);
    for ( let vp = 0; vp < viewportCount; vp ++ ) {

      const viewport = shadow.getViewport( vp );

      _viewport.set(
        _viewportSize.x * viewport.x,
        _viewportSize.y * viewport.y,
        _viewportSize.x * viewport.z,
        _viewportSize.y * viewport.w
      );
      
      renderer.setViewport( _viewport );
      shadow.updateMatrices(entity, vp);

      entity.renderEntities(renderer, camera, shadow.camera);
    }

    entity._needShadowsUpdate = false;
    this._shadowsTextures[entity.indexInArray] = shadow.map.texture;
    this._shadowsTextures[entity.indexInArray].version++;

		renderer.setRenderTarget( ogRenderTarget, activeCubeFace, activeMipmapLevel );
    renderer.renderType = ogRenderType;
    renderer.setScissorTest( oldScissorTest );
    renderer.setClearColor(ogClearColor);
    renderer.setViewport( _oldViewPort );

    if (renderTarget._debugContext) {
      renderer.readRenderTargetPixels( renderTarget, 0, 0, renderTarget.width, renderTarget.height, renderTarget._debugBuffer );
      const d = renderTarget._debugImageData.data;
      for ( let i = 0, l = renderTarget.width * renderTarget.height; i < l; i ++ ) {
       // Rgba to depth
        const r = renderTarget._debugBuffer[ i * 4 + 0 ];
        const g = renderTarget._debugBuffer[ i * 4 + 1 ];
        const b = renderTarget._debugBuffer[ i * 4 + 2 ];
        const a = renderTarget._debugBuffer[ i * 4 + 3 ];
        d[ i * 4 + 0 ] = ( r * 255 ) | 0;
        d[ i * 4 + 1 ] = ( g * 255 ) | 0;
        d[ i * 4 + 2 ] = ( b * 255 ) | 0;
        d[ i * 4 + 3 ] = 255;
      }
      renderTarget._debugContext.putImageData( renderTarget._debugImageData, 0, 0 );
      console.log('renderTarget._debugImageData', renderTarget._debugImageData);
    }

    return true;
  }

  updateLight(entity, camera) {
    if (!entity) return;
    const light = entity.light;
    const shadow = entity.shadow;
    _shadowMapSize.copy( shadow.mapSize ).multiplyScalar( this.resolution );
    _shadowMapSize.x = Math.floor( _shadowMapSize.x * 0.5 );

    const mapSize = _shadowMapSize.clone();

    const shadowFrameExtents = shadow.getFrameExtents();
    _shadowMapSize.multiply( shadowFrameExtents );
    _viewportSize.copy( mapSize );

    const _maxTextureSize = this._maxTextureSize;


    if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) {

      if ( _shadowMapSize.x > _maxTextureSize ) {

        _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x );
        _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
        shadow.mapSize.x = _viewportSize.x;

      }

      if ( _shadowMapSize.y > _maxTextureSize ) {

        _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y );
        _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
        shadow.mapSize.y = _viewportSize.y;

      }

    }

    const coneCos = entity.angle ? Math.cos(entity.angle || 0) : 0;
    const penumbraCos = entity.angle ? Math.cos((entity.angle || 0) * (1 - (entity.penumbra || 0))) : 0;

    if (entity.target) {
      entity.target.updateMatrixWorld();
      _tempVector3.setFromMatrixPosition(entity.target.matrixWorld);
    } else {
      _tempVector3.set(0, 0, 0);
    }

    shadow.updateMatrices(entity);

    this.shadowsData.lights[entity.indexInArray] = {
      uuid: entity.uuid,
      type: light.isDirectionalLight ? 0 : light.isSpotLight ? 1 : 2, // 0: Directional, 1: Spot, 2: Point
      shadowMapSize: mapSize,
      viewportSize: _viewportSize.clone(),
      shadowRadius: shadow.radius || 1.0,
      shadowBias: shadow.bias || 0.0,
      shadowIntensity: shadow.intensity || 1.0,
      shadowNormalBias: shadow.normalBias || 0.02,
      color: entity.color.clone().multiplyScalar(entity.intensity),
      distance: entity.distance || 1.0,
      decay: entity.decay || 1.0,
      coneCos: coneCos,
      penumbraCos: penumbraCos,
      near: shadow.camera.near || 0.001,
      far: shadow.camera.far || 1000.0,
      inFrustum: entity.inFrustum || false,
      index: entity.indexInArray,
      originalLight: light,
      originalEntity: entity,
      shadowMatrix: shadow.matrix.clone(),
    };
    entity._shadowData = this.shadowsData.lights[entity.indexInArray];

    const viewMatrix = camera.matrixWorldInverse;

    if (!this.shadowsData.lights[entity.indexInArray].position) {
      this.shadowsData.lights[entity.indexInArray].position = new Vector3();
    }
    this.shadowsData.lights[entity.indexInArray].position.setFromMatrixPosition(entity.matrixWorld).applyMatrix4(viewMatrix);

    if (!this.shadowsData.lights[entity.indexInArray].direction) {
      this.shadowsData.lights[entity.indexInArray].direction = new Vector3();
    }
    this.shadowsData.lights[entity.indexInArray].direction.setFromMatrixPosition(entity.matrixWorld).sub(_tempVector3).transformDirection(viewMatrix);

  }
renderEntities(renderer, camera, shadowCamera) {
    this.scene.transformEntitiesToInstanced(this.entities, this._instancedMeshes, this._instancedMeshesGroup);

    this._instancedMeshesGroup.children.forEach(entity => {
      if (!entity.depthMaterial) {
        const depthMaterial = this.getDepthMaterial( renderer, entity.material );
        entity.depthMaterial = depthMaterial;
      }

      entity._material = entity.material;
      entity.material = entity.depthMaterial;
    });

    renderer.render( this._instancedMeshesGroup, shadowCamera );

    this._instancedMeshesGroup.children.forEach(entity => {
      entity.material = entity._material;
      delete entity._material;
    });
  }

  getDepthMaterial( renderer, material ) {
    const light = this.light;
		let result = ( light.isPointLight === true ) ? _distanceMaterial.clone() : _depthMaterial.clone()

    if ( ( renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) ||
      ( material.displacementMap && material.displacementScale !== 0 ) ||
      ( material.alphaMap && material.alphaTest > 0 ) ||
      ( material.map && material.alphaTest > 0 ) ||
      ( material.alphaToCoverage === true ) ) {

      const keyA = result.uuid, keyB = material.uuid;

      let materialsForVariant = _materialCache[ keyA ];

      if ( materialsForVariant === undefined ) {

        materialsForVariant = {};
        _materialCache[ keyA ] = materialsForVariant;

      }

      let cachedMaterial = materialsForVariant[ keyB ];

      if ( cachedMaterial === undefined ) {

        cachedMaterial = result.clone();
        materialsForVariant[ keyB ] = cachedMaterial;
        material.addEventListener( 'dispose', this.onMaterialDispose );

      }

      result = cachedMaterial;

    }
		result.visible = material.visible;
		result.wireframe = material.wireframe;

    result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];

		result.alphaMap = material.alphaMap;
		result.alphaTest = ( material.alphaToCoverage === true ) ? 0.5 : material.alphaTest; // approximate alphaToCoverage by using a fixed alphaTest value
		result.map = material.map;

		result.clipShadows = material.clipShadows;
		result.clippingPlanes = material.clippingPlanes;
		result.clipIntersection = material.clipIntersection;

		result.displacementMap = material.displacementMap;
		result.displacementScale = material.displacementScale;
		result.displacementBias = material.displacementBias;

		result.wireframeLinewidth = material.wireframeLinewidth;
		result.linewidth = material.linewidth;

		if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {

			const materialProperties = renderer.properties.get( result );
			materialProperties.light = this;

		}

		return result;

	}

An urban legend says that point lights in Three.js are handled differently compared to other lights. Namely, to get shadows they are projected 6 times onto a cube’s map faces - once in each direction. I don’t know any specific details, but it might be possible you are missing this sextuple rendering. If you put a sphere and a point light in its center, and many objects around the light, do you get at least some partial shadows?

This part of the code is responsible for rendering the different projections of the lights, including the 6 directions (vp) of the PointLights.