threepipe
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ParallaxMappingPlugin.ts 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import {MaterialExtension, updateMaterialDefines} from '../../materials'
  2. import {AViewerPluginSync, ThreeViewer} from '../../viewer'
  3. import {onChange, serialize} from 'ts-browser-helpers'
  4. import {uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js'
  5. import {shaderReplaceString} from '../../utils'
  6. import {ShaderChunk} from 'three'
  7. import {PhysicalMaterial} from '../../core'
  8. import ParallaxMappingPluginReliefShader from './shaders/ParallaxMappingPlugin.relief.glsl'
  9. /**
  10. * Parallax Mapping Plugin
  11. * Adds a material extension to PhysicalMaterial which parallax mapping to bump map in the material.
  12. * This is a port of Relief Parallax Mapping from [Rabbid76/graphics-snippets](https://github.com/Rabbid76/graphics-snippets/blob/master/html/technique/parallax_005_parallax_relief_mapping_derivative_tbn.html)
  13. * @category Plugins
  14. */
  15. @uiFolderContainer('Parallax Mapping')
  16. export class ParallaxMappingPlugin extends AViewerPluginSync<''> {
  17. public static PluginType = 'ReliefParallaxMapping'
  18. @onChange(ParallaxMappingPlugin.prototype._updateExtension)
  19. @serialize()
  20. @uiToggle('Enabled') enabled = true
  21. @uiSlider('Step count', [1, 32], 1)
  22. @onChange(ParallaxMappingPlugin.prototype._updateExtension)
  23. @serialize() stepCount = 12
  24. @uiSlider('Binary search steps', [1, 8], 1)
  25. @onChange(ParallaxMappingPlugin.prototype._updateExtension)
  26. @serialize() binaryStepCount = 3
  27. @onChange(ParallaxMappingPlugin.prototype._updateExtension)
  28. @uiToggle('Debug Normals') debugNormals = false
  29. @onChange(ParallaxMappingPlugin.prototype._updateExtension)
  30. @uiToggle('Debug Hit Height') debugHitHeight = false
  31. private _defines: any = {
  32. ['PARALLAX_NORMAL_MAP_QUALITY']: 0,
  33. }
  34. constructor(enabled = true) {
  35. super()
  36. this.enabled = enabled
  37. this._updateExtension = this._updateExtension.bind(this)
  38. }
  39. private _updateExtension() {
  40. this._bumpMapExtension?.setDirty?.()
  41. this._viewer?.setDirty()
  42. }
  43. private _bumpMapExtension: MaterialExtension = {
  44. shaderExtender: (shader, material, _renderer) => {
  45. if (!material.bumpMap || this.isDisabled()) return
  46. shader.fragmentShader = shader.fragmentShader.replace('#include <normal_fragment_begin>', '')
  47. shader.fragmentShader = shader.fragmentShader.replace('#include <normal_fragment_maps>', '')
  48. shader.fragmentShader = shader.fragmentShader.replace('#include <map_fragment>',
  49. '#include <normal_fragment_begin>\n#include <normal_fragment_maps>\n#include <map_fragment>')
  50. for (const s of ['map_fragment', 'alphamap_fragment', 'roughnessmap_fragment', 'metalnessmap_fragment', 'emissivemap_fragment', 'transmission_fragment']) {
  51. shader.fragmentShader = shaderReplaceString(shader.fragmentShader, `#include <${s}>`,
  52. (ShaderChunk as any)[s].replace(/\bv\w+Uv\b/g, 'parallaxUv.xy', {replaceAll: true})
  53. )
  54. }
  55. if (this.debugNormals || this.debugHitHeight)
  56. shader.fragmentShader = shaderReplaceString(shader.fragmentShader,
  57. // .replace('texture2D( map, parallaxUv.xy )', 'texture2D( map, parallaxUv.xy )')
  58. 'texture2D( map, parallaxUv.xy )',
  59. this.debugNormals ? 'vec4(normal, 1.); normal = geometryNormal' : 'vec4(parallaxUv.z,0., 0., 1.)')
  60. shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#include <normal_fragment_maps>',
  61. shaderReplaceString(
  62. shaderReplaceString(
  63. ShaderChunk.normal_fragment_maps,
  64. '#elif defined( USE_NORMALMAP_TANGENTSPACE )', '#elif defined( USE_NORMALMAP_TANGENTSPACE ) && !defined( USE_BUMPMAP )'),
  65. 'normal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );',
  66. // 'diffuseColor.rgb = vec3(0, dHdxy_fwd());'
  67. // 'diffuseColor.rgb = CalculateNormal(vUv).rgb;'
  68. 'vec3 parallaxUv = reliefParallaxPerturbNormal(faceDirection, normal);'
  69. )
  70. )
  71. },
  72. parsFragmentSnippet: ()=> {
  73. return this.isDisabled() ? '' : (ParallaxMappingPluginReliefShader + '\n')
  74. .replaceAll('PARALLAX_MAP_STEPS', this.stepCount.toString()) // replacing here to unroll for loop.
  75. .replaceAll('PARALLAX_MAP_B_STEPS', this.binaryStepCount.toString())
  76. },
  77. isCompatible: (material: PhysicalMaterial) => {
  78. return material.isPhysicalMaterial
  79. },
  80. computeCacheKey: material => {
  81. return '' + !this.isDisabled() + material.bumpMap?.uuid + this.debugNormals + this.debugHitHeight + this.stepCount.toString() + this.binaryStepCount.toString()
  82. },
  83. onObjectRender: (_object, material, _renderer) => {
  84. if (this.isDisabled()) return // todo: use extraDefines
  85. updateMaterialDefines({
  86. ...this._defines,
  87. }, material)
  88. },
  89. } as MaterialExtension
  90. onAdded(viewer: ThreeViewer) {
  91. viewer.materialManager.registerMaterialExtension(this._bumpMapExtension)
  92. return super.onAdded(viewer)
  93. }
  94. onRemove(viewer: ThreeViewer) {
  95. viewer.materialManager.unregisterMaterialExtension(this._bumpMapExtension)
  96. return super.onRemove(viewer)
  97. }
  98. }