Extensions

Source: renderers/AmbientOcclusionMapRenderer.js

import { Utils } from "../core/Utils";
import { BlendMode } from "../rendering/BlendMode";
import {
  BASE_VERTEX_SHADER,
  BASE_VERTEX_SHADER_INITIALIZATION,
  BASE_VERTEX_SHADER_POSITION,
} from "../utils/shaderUtils";
import { BaseRenderer } from "./BaseRenderer";

/**
 * @typedef {Object} AmbientOcclusionMapRendererConfig
 * @extends {RendererConfig}
 * @property {TextureInfo} sourceTexture // Optional source texture
 * @property {TextureInfo} heightMap // Height map texture
 * @property {number} radius // Sampling radius
 * @property {number} samples // Number of samples
 * @property {number} multiplier // Occlusion multiplier
 * @property {number} depthMultiplier // Depth multiplier
 * @property {number} offsetX // Offset x
 * @property {number} offsetY // Offset y
 */

/**
 * <pre>
 *  Ambient occlusion map renderer
 *    - Creates an ambient occlusion texture from a height map texture
 * </pre>
 * @extends {BaseRenderer}
 */
export class AmbientOcclusionMapRenderer extends BaseRenderer {
  /**
   * Creates an instance of AmbientOcclusionMapRenderer.
   * @constructor
   * @param {AmbientOcclusionMapRendererConfig} config
   */
  constructor(config = {}) {
    config = Utils.initRendererConfig(config);

    // prettier-ignore
    Utils.setLocations(config, [
      "uC",
      "uD",
      "uE",
      "uF",
      "uG",
    ]);

    super(config);

    this.sourceTexture = config.sourceTexture;
    this.heightMap = config.heightMap;
    this.radius = config.radius ?? 4;
    this.samples = config.samples ?? 4;
    this.multiplier = config.multiplier ?? 1;
    this.depthMultiplier = config.depthMultiplier ?? 1;
    this.offsetX = config.offsetX ?? 0;
    this.offsetY = config.offsetY ?? 0;
  }

  /**
   * Sets the offset for the ambient occlusion map.
   * @param {number} x - The x offset.
   * @param {number} y - The y offset.
   */
  setOffset(x, y) {
    this.offsetX = x;
    this.offsetY = y;
  }

  /**
   * @ignore
   */
  $render() {
    const { $gl, $locations } = this;
    const sourceTextureBoolean = !!this.sourceTexture;

    this.context.setBlendMode(BlendMode.NORMAL);

    this.$useTextureAt(this.heightMap, $locations.uB, 0);

    $gl.uniform2f($locations.uF, this.width, this.height);

    sourceTextureBoolean && this.$useTextureAt(this.sourceTexture, $locations.uC, 1);

    $gl.uniform1f($locations.uE, sourceTextureBoolean);

    $gl.uniform4f($locations.uD, this.radius, this.samples, this.multiplier, this.depthMultiplier);
    $gl.uniform2f($locations.uG, this.offsetX, this.offsetY);

    this.$uploadBuffers();

    this.$drawInstanced(1);
  }

  // prettier-ignore
  /**
   * @returns {string}
   * @ignore
   */
  $createVertexShader() {
    return `${Utils.GLSL.DEFINE.RADIANS_360}` +

    `${BASE_VERTEX_SHADER_INITIALIZATION}` +

    `uniform vec4 ` +
      `uD;` +

    `out vec2 ` +
      `v0;` +
    `flat out vec2 ` +
      `v1;` +

    `void main(){` +
      `${BASE_VERTEX_SHADER}` +
      
      `float ` + 
        `r=RADIANS_360/uD.y;` + 

      `v0=${BASE_VERTEX_SHADER_POSITION};` +
      `v1=vec2(cos(r),sin(r));` +
    `}`;
  }

  // prettier-ignore
  /**
   * @returns {string}
   * @ignore
   */
  $createFragmentShader() {
    return `${Utils.GLSL.DEFINE.Z}` +
    `${Utils.GLSL.DEFINE.RADIANS_360}` +

    `in vec2 ` +
      `v0;` +
    `flat in vec2 ` +
      `v1;` +

    `uniform sampler2D ` +
      `uB,` +
      `uC;` +
    `uniform float ` +
      `uE;` +
    `uniform vec2 ` +
      `uF,` +
      `uG;` +
    `uniform vec4 ` +
      `uD;` +

    `out vec4 ` +
      `oCl;` +

    `${Utils.GLSL.RANDOM}` +

    `void main(){` +
      `float ` +
        `tx=texture(uB,v0).g,` +
        `v=0.;` +
        
      `if(uD.y>0.&&uD.x>0.){` +
        `int ` +
          `l=clamp(int(uD.y),1,32);` +
        
        `vec2 ` +
          `ts=1./floor(uF);` +

        `float ` +
          `rnd=rand(v0+floor(uG)*ts),` + 
          `ta=RADIANS_360*rnd,` +
          `ln=1.-rnd;` +
        
        `vec2  ` +
          `rg,` +
          `n=uD.x*ts*rnd,` +
          `dr=vec2(cos(ta),sin(ta));` +

        `for(int i=0;i<l;i++){` +
          `rg=max(texture(uB,v0+n*dr).rg-tx,Z.xx);` +
          `v+=max(0.,(rg.y-rg.x)*(1.-rg.x)*ln);` +
          
          `dr=vec2(` + 
            `v1.x*dr.x-v1.y*dr.y,` +
            `v1.y*dr.x+v1.x*dr.y` +
          `);` +
        `}` +
        `v/=float(l);` +
      `}` +
      
      `vec3 ` +
        `stCl=uE>0.?texture(uC,v0).rgb:Z.yyy;` +

      `oCl=vec4(stCl*mix(1.,tx,uD.w)*(1.-v*uD.z),1);` +
    `}`;
  }
}