function ShadowMap(options) {
  options = options ?? Frozen.EMPTY_OBJECT;
  const context = options.context;

  //>>includeStart('debug', pragmas.debug);
  if (!defined(context)) {
    throw new DeveloperError("context is required.");
  }
  if (!defined(options.lightCamera)) {
    throw new DeveloperError("lightCamera is required.");
  }
  if (
    defined(options.numberOfCascades) &&
    options.numberOfCascades !== 1 &&
    options.numberOfCascades !== 4
  ) {
    throw new DeveloperError("Only one or four cascades are supported.");
  }
  //>>includeEnd('debug');

  this._enabled = options.enabled ?? true;
  this._softShadows = options.softShadows ?? false;
  this._normalOffset = options.normalOffset ?? true;
  this.dirty = true;

  /**
   * Specifies whether the shadow map originates from a light source. Shadow maps that are used for analytical
   * purposes should set this to false so as not to affect scene rendering.
   *
   * @private
   */
  this.fromLightSource = options.fromLightSource ?? true;

  /**
   * Determines the darkness of the shadows.
   *
   * @type {number}
   * @default 0.3
   */
  this.darkness = options.darkness ?? 0.3;
  this._darkness = this.darkness;

  /**
   * Determines whether shadows start to fade out once the light gets closer to the horizon.
   *
   * @type {boolean}
   * @default true
   */
  this.fadingEnabled = options.fadingEnabled ?? true;

  /**
   * Determines the maximum distance of the shadow map. Only applicable for cascaded shadows. Larger distances may result in lower quality shadows.
   *
   * @type {number}
   * @default 5000.0
   */
  this.maximumDistance = options.maximumDistance ?? 5000.0;

  this._outOfView = false;
  this._outOfViewPrevious = false;
  this._needsUpdate = true;

  // In IE11 and Edge polygon offset is not functional.
  // TODO : Also disabled for instances of Firefox and Chrome running ANGLE that do not support depth textures.
  // Re-enable once https://github.com/CesiumGS/cesium/issues/4560 is resolved.
  let polygonOffsetSupported = true;
  if (
    FeatureDetection.isInternetExplorer() ||
    FeatureDetection.isEdge() ||
    ((FeatureDetection.isChrome() || FeatureDetection.isFirefox()) &&
      FeatureDetection.isWindows() &&
      !context.depthTexture)
  ) {
    polygonOffsetSupported = false;
  }
  this._polygonOffsetSupported = polygonOffsetSupported;

  this._terrainBias = {
    polygonOffset: polygonOffsetSupported,
    polygonOffsetFactor: 1.1,
    polygonOffsetUnits: 4.0,
    normalOffset: this._normalOffset,
    normalOffsetScale: 0.5,
    normalShading: true,
    normalShadingSmooth: 0.3,
    depthBias: 0.0001,
  };

  this._primitiveBias = {
    polygonOffset: polygonOffsetSupported,
    polygonOffsetFactor: 1.1,
    polygonOffsetUnits: 4.0,
    normalOffset: this._normalOffset,
    normalOffsetScale: 0.1,
    normalShading: true,
    normalShadingSmooth: 0.05,
    depthBias: 0.00002,
  };

  this._pointBias = {
    polygonOffset: false,
    polygonOffsetFactor: 1.1,
    polygonOffsetUnits: 4.0,
    normalOffset: this._normalOffset,
    normalOffsetScale: 0.0,
    normalShading: true,
    normalShadingSmooth: 0.1,
    depthBias: 0.0005,
  };

  // Framebuffer resources
  this._depthAttachment = undefined;
  this._colorAttachment = undefined;

  // Uniforms
  this._shadowMapMatrix = new Matrix4();
  this._shadowMapTexture = undefined;
  this._lightDirectionEC = new Cartesian3();
  this._lightPositionEC = new Cartesian4();
  this._distance = 0.0;

  this._lightCamera = options.lightCamera;
  this._shadowMapCamera = new ShadowMapCamera();
  this._shadowMapCullingVolume = undefined;
  this._sceneCamera = undefined;
  this._boundingSphere = new BoundingSphere();

  this._isPointLight = options.isPointLight ?? false;
  this._pointLightRadius = options.pointLightRadius ?? 100.0;

  this._cascadesEnabled = this._isPointLight
    ? false
    : (options.cascadesEnabled ?? true);
  this._numberOfCascades = !this._cascadesEnabled
    ? 0
    : (options.numberOfCascades ?? 4);
  this._fitNearFar = true;
  this._maximumCascadeDistances = [25.0, 150.0, 700.0, Number.MAX_VALUE];

  this._textureSize = new Cartesian2();

  this._isSpotLight = false;
  if (this._cascadesEnabled) {
    // Cascaded shadows are always orthographic. The frustum dimensions are calculated on the fly.
    this._shadowMapCamera.frustum = new OrthographicOffCenterFrustum();
  } else if (defined(this._lightCamera.frustum.fov)) {
    // If the light camera uses a perspective frustum, then the light source is a spot light
    this._isSpotLight = true;
  }

  // Uniforms
  this._cascadeSplits = [new Cartesian4(), new Cartesian4()];
  this._cascadeMatrices = [
    new Matrix4(),
    new Matrix4(),
    new Matrix4(),
    new Matrix4(),
  ];
  this._cascadeDistances = new Cartesian4();

  let numberOfPasses;
  if (this._isPointLight) {
    numberOfPasses = 6; // One shadow map for each direction
  } else if (!this._cascadesEnabled) {
    numberOfPasses = 1;
  } else {
    numberOfPasses = this._numberOfCascades;
  }

  this._passes = new Array(numberOfPasses);
  for (let i = 0; i < numberOfPasses; ++i) {
    this._passes[i] = new ShadowPass(context);
  }

  this.debugShow = false;
  this.debugFreezeFrame = false;
  this._debugFreezeFrame = false;
  this._debugCascadeColors = false;
  this._debugLightFrustum = undefined;
  this._debugCameraFrustum = undefined;
  this._debugCascadeFrustums = new Array(this._numberOfCascades);
  this._debugShadowViewCommand = undefined;

  this._usesDepthTexture = context.depthTexture;

  if (this._isPointLight) {
    this._usesDepthTexture = false;
  }

  // Create render states for shadow casters
  this._primitiveRenderState = undefined;
  this._terrainRenderState = undefined;
  this._pointRenderState = undefined;
  createRenderStates(this);

  // For clearing the shadow map texture every frame
  this._clearCommand = new ClearCommand({
    depth: 1.0,
    color: new Color(),
  });

  this._clearPassState = new PassState(context);

  this._size = options.size ?? 2048;
  this.size = this._size;
}
<div class="notice">
Use {@link Viewer#shadowMap} to get the scene's shadow map. Do not construct this directly.
</div>

<p>
The normalOffset bias pushes the shadows forward slightly, and may be disabled
for applications that require ultra precise shadows.
</p>

@alias ShadowMap
@internalConstructor
@class

@privateParam {object} options An object containing the following properties:
@privateParam {Context} options.context The context
@privateParam {Camera} options.lightCamera A camera representing the light source.
@privateParam {boolean} [options.enabled=true] Whether the shadow map is enabled.
@privateParam {boolean} [options.isPointLight=false] Whether the light source is a point light. Point light shadows do not use cascades.
@privateParam {number} [options.pointLightRadius=100.0] Radius of the point light.
@privateParam {boolean} [options.cascadesEnabled=true] Use multiple shadow maps to cover different partitions of the view frustum.
@privateParam {number} [options.numberOfCascades=4] The number of cascades to use for the shadow map. Supported values are one and four.
@privateParam {number} [options.maximumDistance=5000.0] The maximum distance used for generating cascaded shadows. Lower values improve shadow quality.
@privateParam {number} [options.size=2048] The width and height, in pixels, of each shadow map.
@privateParam {boolean} [options.softShadows=false] Whether percentage-closer-filtering is enabled for producing softer shadows.
@privateParam {number} [options.darkness=0.3] The shadow darkness.
@privateParam {boolean} [options.normalOffset=true] Whether a normal bias is applied to shadows.
@privateParam {boolean} [options.fadingEnabled=true] Whether shadows start to fade out once the light gets closer to the horizon.

@exception {DeveloperError} Only one or four cascades are supported.

@demo {@link https://sandcastle.cesium.com/index.html?src=Shadows.html|Cesium Sandcastle Shadows Demo}