packages/miew/src/gfx/shaders/UberMaterial.js

Summary

Maintainability
D
1 day
Test Coverage
/* eslint-disable no-magic-numbers */
/* eslint-disable guard-for-in */

import vertexShader from './Uber.vert'
import fragmentShader from './Uber.frag'
import capabilities from '../capabilities'
import noise from '../noiseTexture'
import {
  UniformsLib,
  UniformsUtils,
  Vector2,
  Color,
  Matrix4,
  RawShaderMaterial,
  DoubleSide,
  Material
} from 'three'

// Length of _samplesKernel is used in Uber.frag
// If you want to change length of _samplesKernel, please, remember change it in Uber.frag too.
// You can easy find places for replace using word:_samplesKernel
const _samplesKernel = [
  new Vector2(-0.541978, 0.840393),
  new Vector2(0.125533, -0.992089),
  new Vector2(0.374329, 0.927296),
  new Vector2(-0.105475, 0.994422)
]

const defaultUniforms = UniformsUtils.merge([
  UniformsLib.fog,
  UniformsLib.lights,

  {
    // are updated automatically by three.js (see ShaderLib.common)
    diffuse: { value: new Color(0xeeeeee) },
    opacity: { value: 1.0 },

    specular: { type: 'c', value: new Color(0x111111) },
    shininess: { type: 'f', value: 30 },
    fixedColor: { type: 'c', value: new Color(0xffffff) },
    zOffset: { type: 'f', value: 0.0 },
    zClipValue: { type: 'f', value: 0.0 },
    clipPlaneValue: { type: 'f', value: 0.0 },
    nearPlaneValue: { type: 'f', value: -0.5 },
    invModelViewMatrix: { type: '4fv', value: new Matrix4() },
    world2colorMatrix: { type: '4fv', value: new Matrix4() },
    dashedLineSize: { type: 'f', value: 0.1 },
    dashedLinePeriod: { type: 'f', value: 0.2 },
    projMatrixInv: { type: '4fv', value: new Matrix4() },
    viewport: { type: 'v2', value: new Vector2() },
    lineWidth: { type: 'f', value: 2.0 },
    // default value must be the same as settings
    fogAlpha: { type: 'f', value: 1.0 },
    samplesKernel: { type: 'v2v', value: null },
    noiseTex: { type: 't', value: null },
    noiseTexelSize: { type: 'v2', value: null },
    srcTexelSize: { type: 'v2', value: null }
  }
])

const uberOptionNames = [
  'shininess',
  'opacity',
  'zOffset',
  'diffuse',
  'specular',
  'fixedColor',
  'zClipCoef',
  'zClipValue',
  'clipPlaneValue',
  'world2colorMatrix',
  'dashedLineSize',
  'dashedLinePeriod',
  'projMatrixInv',
  'viewport',
  'lineWidth',
  'fogAlpha',
  'samplesKernel',
  'noiseTex',
  'noiseTexelSize',
  'srcTexelSize'
]

// properties that convert to uniforms
const uberOptions = {
  diffuse: new Color(0xffffff), // used in phong lighting
  specular: new Color(0x111111), // used in phong lighting
  shininess: 30, // used in phong lighting
  opacity: 1, // set mesh opacity
  fixedColor: new Color(0xffffff), // color to override (see OVERRIDE_COLOR)
  zOffset: 0.0, // used fo zsprites (see SPHERE_SPRITE CYLINDER_SPRITE)
  zClipCoef: 2.0, // use for Surfs clipping (mesh param, isn't used in shader)  FIXME move to representation param
  zClipValue: 0.0, //  value to clip Surfs in shader  (see ZCLIP)
  clipPlaneValue: 0.0, // value to clip scene globally (see CLIPPLANE)
  world2colorMatrix: new Matrix4(),
  dashedLineSize: 0.1,
  dashedLinePeriod: 0.3,
  projMatrixInv: new Matrix4(),
  viewport: new Vector2(800, 600),
  lineWidth: 2.0,
  fogAlpha: 1.0,
  samplesKernel: _samplesKernel,
  noiseTex: noise.noiseTexture,
  noiseTexelSize: new Vector2(1.0 / noise.noiseWidth, 1.0 / noise.noiseHeight),
  srcTexelSize: new Vector2(1.0 / 800.0, 1.0 / 600.0),

  copy(source) {
    this.diffuse.copy(source.diffuse)
    this.specular.copy(source.specular)
    this.shininess = source.shininess
    this.opacity = source.opacity
    this.fixedColor.copy(source.fixedColor)
    this.zOffset = source.zOffset
    this.zClipCoef = source.zClipCoef
    this.zClipValue = source.zClipValue
    this.clipPlaneValue = source.clipPlaneValue
    this.world2colorMatrix.copy(source.world2colorMatrix)
    this.dashedLineSize = source.dashedLineSize
    this.dashedLinePeriod = source.dashedLinePeriod
    this.projMatrixInv = source.projMatrixInv
    this.viewport = source.viewport
    this.lineWidth = source.lineWidth // used for thick lines only
    this.toonShading = source.toonShading
    this.fogAlpha = source.fogAlpha
    this.samplesKernel = source.samplesKernel
    this.noiseTex = source.noiseTex
    this.noiseTexelSize = source.noiseTexelSize
    this.srcTexelSize = source.srcTexelSize
  }
}

class UberMaterial extends RawShaderMaterial {
  constructor(params) {
    super()
    // add fog
    this.fog = true
    // used for instanced geometry
    this.instancedPos = false
    this.instancedMatrix = false
    // atoms and links color
    this.attrColor = false
    // second link color for cylinders
    this.attrColor2 = false
    //
    this.attrAlphaColor = false
    // overrides color for all vertices (used in selection)
    this.overrideColor = false
    // zsrpites
    this.sphereSprite = false
    this.cylinderSprite = false
    // clip Surfs individually
    this.zClip = false
    // clip scene with global clip plane
    this.clipPlane = false
    // enable fake (chess-like) opacity
    this.fakeOpacity = false
    // render only depth, don't take care about the pixel color (used for transparency depth prepass)
    this.prepassTransparancy = false
    // used to render pixel positions
    this.colorFromPos = false
    // used to render shadowmap
    this.shadowmap = false
    // used to describe shadowmap type
    this.shadowmapType = 'random'
    // used to render pixel view deph
    this.colorFromDepth = false
    // mark that rendering is for orthographic camera
    this.orthoCam = false
    // used to render dashed line
    this.dashedLine = false
    // mark as transparent
    this.transparent = true
    // mark as thick lines
    this.thickLine = false
    // makes fog begin transparency (required for transparent background)
    this.fogTransparent = false
    // used to render surface normals to G buffer for ssao effect
    this.normalsToGBuffer = false
    // used for toon material
    this.toonShading = false
    // uber options of "root" materials are inherited from single uber-options object that resides in prototype
    this.uberOptions = Object.create(uberOptions)
    // set default values
    super.setValues({
      uniforms: UniformsUtils.clone(defaultUniforms),
      vertexShader: this.precisionString() + vertexShader,
      fragmentShader: this.precisionString() + fragmentShader,
      lights: true,
      fog: true,
      side: DoubleSide
    })

    this.setValues(params)
  }

  precisionString() {
    const { precision } = capabilities
    const str =
      `precision ${precision} float;\n` + `precision ${precision} int;\n\n`
    return str
  }

  // create copy of this material
  copy(source) {
    super.copy(source)

    this.fragmentShader = source.fragmentShader
    this.vertexShader = source.vertexShader

    this.uniforms = UniformsUtils.clone(source.uniforms)
    this.defines = { ...source.defines }
    this.extensions = source.extensions

    this.fog = source.fog
    this.instancedPos = source.instancedPos
    this.instancedMatrix = source.instancedMatrix
    this.attrColor = source.attrColor
    this.attrColor2 = source.attrColor2
    this.attrAlphaColor = source.attrAlphaColor
    this.overrideColor = source.overrideColor
    this.sphereSprite = source.sphereSprite
    this.cylinderSprite = source.cylinderSprite
    this.zClip = source.zClip
    this.clipPlane = source.clipPlane
    this.fakeOpacity = source.fakeOpacity
    this.colorFromPos = source.colorFromPos
    this.shadowmap = source.shadowmap
    this.shadowmapType = source.shadowmapType
    this.colorFromDepth = source.colorFromDepth
    this.orthoCam = source.orthoCam
    this.prepassTransparancy = source.prepassTransparancy
    this.dashedLine = source.dashedLine
    this.thickLine = source.thickLine
    this.fogTransparent = source.fogTransparent
    this.normalsToGBuffer = source.normalsToGBuffer
    this.toonShading = source.toonShading

    this.uberOptions.copy(source.uberOptions)

    return this
  }

  // create copy of this material
  // its options are prototyped after this material's options
  createInstance() {
    const inst = new UberMaterial()
    inst.copy(this)
    inst.uberOptions = Object.create(this.uberOptions)
    return inst
  }

  setValues(values) {
    if (typeof values === 'undefined') {
      return
    }

    // set direct values
    super.setValues(values)

    const defines = {}
    const extensions = {}

    if (this.fog) {
      defines.USE_FOG = 1
    }
    if (this.instancedPos) {
      defines.INSTANCED_POS = 1
    }
    if (this.instancedMatrix) {
      defines.INSTANCED_MATRIX = 1
    }
    if (this.attrColor) {
      defines.ATTR_COLOR = 1
    }
    if (this.attrColor2) {
      defines.ATTR_COLOR2 = 1
    }
    if (this.attrAlphaColor) {
      defines.ATTR_ALPHA_COLOR = 1
    }
    if (this.overrideColor) {
      defines.OVERRIDE_COLOR = 1
    }
    if (this.sphereSprite) {
      defines.SPHERE_SPRITE = 1
      extensions.fragDepth = 1
    }
    if (this.cylinderSprite) {
      defines.CYLINDER_SPRITE = 1
      extensions.fragDepth = 1
    }
    if (this.zClip) {
      defines.ZCLIP = 1
    }
    if (this.clipPlane) {
      defines.CLIP_PLANE = 1
    }
    if (this.fakeOpacity) {
      defines.FAKE_OPACITY = 1
    }
    if (this.lights) {
      defines.USE_LIGHTS = 1
    }
    if (this.colorFromPos) {
      defines.COLOR_FROM_POS = 1
    }
    if (this.shadowmap) {
      defines.SHADOWMAP = 1
      if (this.shadowmapType === 'pcf') {
        defines.SHADOWMAP_PCF_SHARP = 1
      } else if (this.shadowmapType === 'random') {
        defines.SHADOWMAP_PCF_RAND = 1
      } else {
        defines.SHADOWMAP_BASIC = 1
      }
    }
    if (this.colorFromDepth) {
      defines.COLOR_FROM_DEPTH = 1
    }
    if (this.orthoCam) {
      defines.ORTHOGRAPHIC_CAMERA = 1
    }
    if (this.prepassTransparancy) {
      defines.PREPASS_TRANSP = 1
    }
    if (this.dashedLine) {
      defines.DASHED_LINE = 1
    }
    if (this.thickLine) {
      defines.THICK_LINE = 1
    }
    if (this.fogTransparent) {
      defines.FOG_TRANSPARENT = 1
    }
    if (this.normalsToGBuffer) {
      extensions.drawBuffers = 1
      defines.NORMALS_TO_G_BUFFER = 1
    }
    if (this.toonShading) {
      defines.TOON_SHADING = 1
    }
    // set dependent values
    this.defines = defines
    this.extensions = extensions
  }

  setUberOptions(values) {
    if (typeof values === 'undefined') {
      return
    }

    for (const key in values) {
      if (!Object.hasOwn(values, key)) {
        continue
      }

      if (this.uberOptions[key] instanceof Color) {
        this.uberOptions[key] = values[key].clone()
      } else {
        this.uberOptions[key] = values[key]
      }
    }
  }

  clone(shallow) {
    if (!shallow) {
      return Material.prototype.clone.call(this)
    }
    return this.createInstance()
  }

  updateUniforms() {
    const self = this

    uberOptionNames.forEach((p) => {
      if (Object.hasOwn(self.uniforms, p)) {
        if (
          self.uberOptions[p] instanceof Color ||
          self.uberOptions[p] instanceof Matrix4
        ) {
          self.uniforms[p].value = self.uberOptions[p].clone()
        } else {
          self.uniforms[p].value = self.uberOptions[p]
        }
      }
    })
  }
}

UberMaterial.prototype.uberOptions = uberOptions

export default UberMaterial