Extensions

Source: renderers/AmbientOcclusionMapRenderer.js

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

/**
 * @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 = this.$gl,
      locations = this.$locations,
      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);" +
    "}";
  }
}