threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ContactShadowGroundPlugin.ts 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import {onChange, serialize} from 'ts-browser-helpers'
  2. import {
  3. BasicDepthPacking,
  4. Color,
  5. Euler,
  6. LinearFilter,
  7. MeshDepthMaterial,
  8. NoBlending,
  9. NoColorSpace,
  10. OrthographicCamera,
  11. RGBAFormat,
  12. UnsignedByteType,
  13. Vector3,
  14. WebGLRenderTarget,
  15. } from 'three'
  16. import {BaseGroundPlugin} from '../base/BaseGroundPlugin'
  17. import {GBufferRenderPass} from '../../postprocessing'
  18. import {ThreeViewer} from '../../viewer'
  19. import {IRenderTarget} from '../../rendering'
  20. import {uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js'
  21. import {HVBlurHelper} from '../../three/utils/HVBlurHelper'
  22. import {shaderReplaceString} from '../../utils'
  23. @uiFolderContainer('Contact Shadow Ground')
  24. export class ContactShadowGroundPlugin extends BaseGroundPlugin {
  25. static readonly PluginType = 'ContactShadowGroundPlugin'
  26. @uiToggle('Contact Shadows')
  27. @onChange(ContactShadowGroundPlugin.prototype.refresh)
  28. @serialize() contactShadows = true
  29. @uiSlider('Shadow Scale', [0, 2])
  30. @serialize()
  31. @onChange(ContactShadowGroundPlugin.prototype._refreshShadowCameraFrustum)
  32. shadowScale = 1
  33. @uiSlider('Shadow Height', [0, 20])
  34. @serialize()
  35. @onChange(ContactShadowGroundPlugin.prototype._refreshShadowCameraFrustum)
  36. shadowHeight = 5
  37. @uiSlider('Blur Amount', [0, 10])
  38. @serialize()
  39. @onChange(ContactShadowGroundPlugin.prototype._setDirty)
  40. blurAmount = 1
  41. shadowCamera = new OrthographicCamera(-1, 1, 1, -1, 0.001, this.shadowHeight)
  42. private _depthPass?: GBufferRenderPass<'contactShadowGround', WebGLRenderTarget>
  43. private _blurHelper?: HVBlurHelper
  44. constructor() {
  45. super()
  46. this._refreshShadowCameraFrustum = this._refreshShadowCameraFrustum.bind(this)
  47. this.refresh = this.refresh.bind(this)
  48. }
  49. onAdded(viewer: ThreeViewer): void {
  50. const target = viewer.renderManager.createTarget<IRenderTarget & WebGLRenderTarget>({
  51. type: UnsignedByteType,
  52. format: RGBAFormat,
  53. colorSpace: NoColorSpace,
  54. size: {width: 512, height: 512},
  55. generateMipmaps: false,
  56. depthBuffer: true,
  57. minFilter: LinearFilter,
  58. magFilter: LinearFilter,
  59. // isAntialiased: this._viewer.isAntialiased,
  60. })
  61. target.texture.name = 'groundContactDepthTexture'
  62. // https://github.com/mrdoob/three.js/blob/master/examples/webgl_shadow_contact.html
  63. const material = new MeshDepthMaterial({
  64. // depthPacking: RGBADepthPacking, // todo
  65. depthPacking: BasicDepthPacking,
  66. transparent: false,
  67. blending: NoBlending,
  68. })
  69. material.onBeforeCompile = (shader) => {
  70. shader.uniforms.opacity.value = 1.
  71. shader.fragmentShader = shaderReplaceString(shader.fragmentShader,
  72. 'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
  73. 'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), 1.0 );',
  74. // 'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );',
  75. )
  76. }
  77. this._depthPass = new GBufferRenderPass('contactShadowGround', target, material, new Color(0, 0, 0), 0)
  78. this._blurHelper = new HVBlurHelper(viewer)
  79. super.onAdded(viewer)
  80. }
  81. onRemove(viewer: ThreeViewer): void {
  82. const target = this._depthPass?.target
  83. if (target) this._viewer?.renderManager.disposeTarget(target)
  84. this._depthPass?.dispose()
  85. this._depthPass = undefined
  86. this._blurHelper?.dispose()
  87. this._blurHelper = undefined
  88. return super.onRemove(viewer)
  89. }
  90. // todo: dispose target, material, pass and stuff
  91. protected _postFrame() {
  92. super._postFrame()
  93. if (!this._viewer) return
  94. }
  95. protected _preRender() {
  96. super._preRender()
  97. if (!this._viewer || !this._depthPass || !this._blurHelper) return
  98. this._depthPass.scene = this._viewer.scene
  99. this._depthPass.camera = this.shadowCamera
  100. this._depthPass.render(this._viewer.renderManager.renderer, null)
  101. const blurTarget = this._viewer.renderManager.getTempTarget<IRenderTarget&WebGLRenderTarget>({
  102. type: UnsignedByteType,
  103. format: RGBAFormat,
  104. colorSpace: NoColorSpace,
  105. size: {width: 1024, height: 1024},
  106. generateMipmaps: false,
  107. depthBuffer: false,
  108. minFilter: LinearFilter,
  109. magFilter: LinearFilter,
  110. // isAntialiased: this._viewer.isAntialiased,
  111. })
  112. this._blurHelper.blur(this._depthPass.target.texture, this._depthPass.target, blurTarget, this.blurAmount / 256)
  113. this._blurHelper.blur(this._depthPass.target.texture, this._depthPass.target, blurTarget, 0.4 * this.blurAmount / 256)
  114. this._viewer.renderManager.releaseTempTarget(blurTarget)
  115. }
  116. protected _refreshTransform() {
  117. super._refreshTransform()
  118. if (!this._mesh) return
  119. if (!this._viewer) return
  120. this.shadowCamera.position.copy(this._mesh.getWorldPosition(new Vector3()))
  121. this.shadowCamera.setRotationFromEuler(new Euler(Math.PI / 2., 0, 0))
  122. this.shadowCamera.updateMatrixWorld()
  123. this._refreshShadowCameraFrustum()
  124. this._mesh.scale.y = -this.size
  125. }
  126. private _refreshShadowCameraFrustum() {
  127. if (!this.shadowCamera) return
  128. this.shadowCamera.left = -this.size / (2 * this.shadowScale)
  129. this.shadowCamera.right = this.size / (2 * this.shadowScale)
  130. this.shadowCamera.top = this.size / (2 * this.shadowScale)
  131. this.shadowCamera.bottom = -this.size / (2 * this.shadowScale)
  132. this.shadowCamera.far = this.shadowHeight
  133. this.shadowCamera.updateProjectionMatrix()
  134. this._setDirty()
  135. }
  136. private _setDirty() {
  137. this._viewer?.setDirty()
  138. }
  139. protected _removeMaterial() {
  140. if (!this._material) return
  141. // todo: remove map or render target thats assigned
  142. super._removeMaterial()
  143. }
  144. public refresh(): void {
  145. if (!this._viewer) return
  146. // todo: shadow enabled check
  147. super.refresh()
  148. }
  149. protected _refreshMaterial(): boolean {
  150. if (!this._viewer) return false
  151. const isNewMaterial = super._refreshMaterial()
  152. if (!this._material) return isNewMaterial
  153. this._material.alphaMap = this._depthPass?.target.texture || null
  154. if (isNewMaterial) {
  155. this._material.roughness = 1
  156. this._material.metalness = 0
  157. this._material.color.set(0x111111)
  158. this._material.transparent = true
  159. this._material.userData.ssreflDisabled = true // todo: unset this in remove material.
  160. this._material.userData.ssreflNonPhysical = false
  161. // this._material.materialObject.userData.inverseAlphaMap = false // this must be false, if getting inverted colors, check clear color of gbuffer render pass.
  162. }
  163. return isNewMaterial
  164. }
  165. }