UNPKG

threepipe

Version:

A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.

250 lines (221 loc) 9.79 kB
import {getOrCall, onChange, serialize} from 'ts-browser-helpers' import { BasicDepthPacking, Color, Euler, LinearFilter, MeshDepthMaterial, NoBlending, NoColorSpace, OrthographicCamera, RGBAFormat, Texture, UnsignedByteType, Vector3, WebGLRenderTarget, } from 'three' import {BaseGroundPlugin} from '../base/BaseGroundPlugin' import {GBufferRenderPass} from '../../postprocessing' import {ThreeViewer} from '../../viewer' import {IRenderTarget} from '../../rendering' import {uiDropdown, uiPanelContainer, uiSlider, uiToggle} from 'uiconfig.js' import {HVBlurHelper} from '../../three/utils/HVBlurHelper' import {shaderReplaceString} from '../../utils' import {PhysicalMaterial} from '../../core' @uiPanelContainer('Ground Plane (Contact Shadows)') export class ContactShadowGroundPlugin extends BaseGroundPlugin { static readonly PluginType = 'ContactShadowGroundPlugin' @uiToggle('Contact Shadows') @onChange(ContactShadowGroundPlugin.prototype.refresh) @serialize() contactShadows = true @uiSlider('Shadow Scale', [0, 2]) @serialize() @onChange(ContactShadowGroundPlugin.prototype._refreshShadowCameraFrustum) shadowScale = 1 @uiSlider('Shadow Height', [0, 20]) @serialize() @onChange(ContactShadowGroundPlugin.prototype._refreshShadowCameraFrustum) shadowHeight = 5 @uiSlider('Blur Amount', [0, 10]) @serialize() @onChange(ContactShadowGroundPlugin.prototype._setDirty) blurAmount = 1 @onChange(ContactShadowGroundPlugin.prototype._refreshMaterial) @uiDropdown('Map Mode', ['aoMap', 'map', 'alphaMap']) @serialize() mapMode: 'aoMap' | 'map' | 'alphaMap' = 'aoMap' shadowCamera = new OrthographicCamera(-1, 1, 1, -1, 0.001, this.shadowHeight) private _depthPass?: GBufferRenderPass<'contactShadowGround', WebGLRenderTarget|undefined> private _blurHelper?: HVBlurHelper constructor() { super() this._refreshShadowCameraFrustum = this._refreshShadowCameraFrustum.bind(this) this.refresh = this.refresh.bind(this) } onAdded(viewer: ThreeViewer): void { const target = viewer.renderManager.createTarget<IRenderTarget & WebGLRenderTarget>({ type: UnsignedByteType, format: RGBAFormat, colorSpace: NoColorSpace, size: {width: 512, height: 512}, generateMipmaps: false, depthBuffer: true, minFilter: LinearFilter, magFilter: LinearFilter, // samples? }) target.texture.name = 'groundContactDepthTexture' // https://github.com/mrdoob/three.js/blob/master/examples/webgl_shadow_contact.html const material = new MeshDepthMaterial({ // depthPacking: RGBADepthPacking, // todo depthPacking: BasicDepthPacking, transparent: false, blending: NoBlending, }) material.opacity = -1. // using opacity uniform to toggle for aomap/alphamap to flip the color in the shader material.onBeforeCompile = (shader) => { shader.fragmentShader = shaderReplaceString(shader.fragmentShader, 'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );', 'gl_FragColor = vec4( opacity > 0. ? vec3( 1.0 - fragCoordZ ) : vec3( fragCoordZ ), 1.0 );', // 'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );', ) } this._depthPass = new GBufferRenderPass('contactShadowGround', target, material, new Color(1, 1, 1), 1) this._blurHelper = new HVBlurHelper(viewer) super.onAdded(viewer) } onRemove(viewer: ThreeViewer): void { const target = getOrCall(this._depthPass?.target) if (target) this._viewer?.renderManager.disposeTarget(target) this._depthPass?.dispose() this._depthPass = undefined this._blurHelper?.dispose() this._blurHelper = undefined return super.onRemove(viewer) } // todo: dispose target, material, pass and stuff protected _postFrame() { super._postFrame() if (!this._viewer) return } protected _preRender() { super._preRender() if (!this._viewer || !this._depthPass || !this._blurHelper || !this.contactShadows || !this.visible) return this._depthPass.scene = this._viewer.scene this._depthPass.camera = this.shadowCamera this._depthPass.render(this._viewer.renderManager.renderer, null) const target = getOrCall(this._depthPass.target) if (!target) return const blurTarget = this._viewer.renderManager.getTempTarget<IRenderTarget & WebGLRenderTarget>({ type: UnsignedByteType, format: RGBAFormat, colorSpace: NoColorSpace, size: {width: 1024, height: 1024}, generateMipmaps: false, depthBuffer: false, minFilter: LinearFilter, magFilter: LinearFilter, // isAntialiased: this._viewer.isAntialiased, }) this._blurHelper.blur(target.texture, target, blurTarget, this.blurAmount / 256) this._blurHelper.blur(target.texture, target, blurTarget, 0.4 * this.blurAmount / 256) this._viewer.renderManager.releaseTempTarget(blurTarget) } protected _refreshTransform() { if (!super._refreshTransform()) return false if (!this._mesh) return false if (!this._viewer) return false this.shadowCamera.position.copy(this._mesh.getWorldPosition(new Vector3())) this.shadowCamera.setRotationFromEuler(new Euler(Math.PI / 2., 0, 0)) this.shadowCamera.updateMatrixWorld() this._refreshShadowCameraFrustum() this._mesh.scale.y = -this.size return true } private _refreshShadowCameraFrustum() { if (!this.shadowCamera) return this.shadowCamera.left = -this.size / (2 * this.shadowScale) this.shadowCamera.right = this.size / (2 * this.shadowScale) this.shadowCamera.top = this.size / (2 * this.shadowScale) this.shadowCamera.bottom = -this.size / (2 * this.shadowScale) this.shadowCamera.far = this.shadowHeight this.shadowCamera.updateProjectionMatrix() this._setDirty() } private _setDirty() { this._viewer?.setDirty() } protected _removeMaterial() { if (!this._material) return this._material.alphaMap = null this._material.aoMap = null this._material.map = null if (this._material.userData.ssaoDisabled) delete this._material.userData.ssaoDisabled if (this._material.userData.ssreflDisabled) delete this._material.userData.ssreflDisabled if (this._material.userData.ssreflNonPhysical) delete this._material.userData.ssreflNonPhysical super._removeMaterial() } private _depthTex: Texture|null = null public refresh(): void { if (!this._viewer) return if (!this.contactShadows) { if (this._material?.alphaMap === this._depthTex) { this._material.alphaMap = null this._material.setDirty() } if (this._material?.aoMap === this._depthTex) { this._material.aoMap = null this._material.setDirty() } if (this._material?.map === this._depthTex) { this._material.map = null this._material.setDirty() } if (this._material?.userData.__csgpParamsSet) { delete this._material.userData.__csgpParamsSet delete this._material.userData.ssaoDisabled delete this._material.userData.ssreflDisabled delete this._material.userData.ssreflNonPhysical } this._depthTex = null } else { this._depthTex = getOrCall(this._depthPass?.target)?.texture || null } super.refresh() } protected _createMaterial(material?: PhysicalMaterial): PhysicalMaterial { const mat = super._createMaterial(material) mat.roughness = 1 mat.metalness = 0 mat.color.set(0xffffff) mat.transparent = true // mat.userData.inverseAlphaMap = false // this must be false, if getting inverted colors, check clear color of gbuffer render pass. return mat } protected _refreshMaterial() { if (!this._viewer) return false const isNewMaterial = super._refreshMaterial() if (!this._material) return isNewMaterial if (this.contactShadows) { this._material.userData.ssaoDisabled = this.mapMode === 'aoMap' this._material.userData.ssreflDisabled = this.mapMode === 'alphaMap' this._material.userData.ssreflNonPhysical = false this._material.userData.__csgpParamsSet = true if (this._material.alphaMap === this._depthTex) { this._material.alphaMap = null } if (this._material.aoMap === this._depthTex) { this._material.aoMap = null } if (this._material.map === this._depthTex) { this._material.map = null } this._material[this.mapMode] = this._depthTex this._material.setDirty() if (this._depthPass) { this._depthPass.clearColor!.set(this.mapMode === 'aoMap' ? new Color(1, 1, 1) : new Color(0, 0, 0)) this._depthPass.clearAlpha = this.mapMode === 'aoMap' ? 1 : 0 this._depthPass.overrideMaterial!.opacity = this.mapMode === 'aoMap' ? -1 : 1 } } return isNewMaterial } }