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.
409 lines (326 loc) • 15.8 kB
text/typescript
import {IPipelinePass} from './Pass'
import {RenderPass} from 'three/examples/jsm/postprocessing/RenderPass.js'
import {
Color,
HalfFloatType,
LinearFilter,
Material,
NoColorSpace,
RGBAFormat,
UnsignedByteType,
WebGLRenderTarget,
} from 'three'
import {generateUiConfig, UiObjectConfig, uiToggle} from 'uiconfig.js'
import {serialize} from 'ts-browser-helpers'
import {GenericBlendTexturePass} from './GenericBlendTexturePass'
import {IRenderTarget} from '../rendering'
import {ICamera, IRenderManager, IScene, IWebGLRenderer} from '../core'
import {ViewerRenderManager} from '../viewer'
export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'render'> {
readonly isExtendedRenderPass = true
enabled = true
readonly passId = 'render'
private _blendPass: GenericBlendTexturePass
readonly renderManager: ViewerRenderManager
private _doTransmissionFix = true
blurTransmissionTarget = true
preserveTransparentTarget = false
private _transparentTarget?: IRenderTarget
/**
* A render target to render transparent, transmissive objects to.
* Note that it is only used when {@link renderManager.rgbm} is true
*/
get transparentTarget(): IRenderTarget {
if (!this._transparentTarget) {
const msaa = this.renderManager.msaa
this._transparentTarget = this.renderManager.getTempTarget({
sizeMultiplier: 1,
samples: msaa ? typeof msaa !== 'number' ? ViewerRenderManager.DEFAULT_MSAA_SAMPLES : msaa : 0,
stencilBuffer: this.renderManager.composerTarget.stencilBuffer,
colorSpace: NoColorSpace,
type: this.renderManager.renderer.extensions.has('EXT_color_buffer_half_float') ? HalfFloatType : UnsignedByteType,
format: RGBAFormat,
minFilter: LinearFilter,
magFilter: LinearFilter,
depthBuffer: false,
})
}
return this._transparentTarget
}
private _releaseTransparentTarget() {
if (this._transparentTarget)
this.renderManager.releaseTempTarget(this._transparentTarget)
this._transparentTarget = undefined
}
/**
* Preserve the {@link opaqueTarget} after rendering.
*/
/* readonly */ preserveOpaqueTarget = false
private _opaqueTarget?: WebGLRenderTarget
/**
* Whether to render the scene background in this pass.
* Not serialized
*/
renderBackground = true
/**
* A render target to render opaque objects to.
* Note that it is only used when {@link renderManager.msaa} is true
*/
get opaqueTarget(): WebGLRenderTarget {
if (!this._opaqueTarget) {
const composerTarget = this.renderManager.composerTarget as WebGLRenderTarget
const msaa = this.renderManager.msaa
this._opaqueTarget = this.renderManager.getTempTarget({
sizeMultiplier: 1,
samples: msaa ? typeof msaa !== 'number' ? ViewerRenderManager.DEFAULT_MSAA_SAMPLES : msaa : 0,
stencilBuffer: this.renderManager.composerTarget.stencilBuffer,
colorSpace: composerTarget.texture.colorSpace,
type: this.renderManager.rgbm ? UnsignedByteType : HalfFloatType,
format: composerTarget.texture.format,
minFilter: composerTarget.texture.minFilter,
magFilter: composerTarget.texture.magFilter,
depthBuffer: composerTarget.depthBuffer,
generateMipmaps: composerTarget.texture.generateMipmaps,
})
// console.log(this._opaqueTarget.samples)
}
return this._opaqueTarget
}
private _releaseOpaqueTarget() {
if (this._opaqueTarget)
this.renderManager.releaseTempTarget(this._opaqueTarget)
this._opaqueTarget = undefined
}
constructor(renderManager: ViewerRenderManager, overrideMaterial?: Material, clearColor = new Color(0, 0, 0), clearAlpha = 0) {
super(undefined, undefined, overrideMaterial, clearColor, clearAlpha)
this.renderManager = renderManager
this._blendPass = new GenericBlendTexturePass({}, 'c = vec4(a.rgb * (1. - b.a) + b.rgb * b.a, 1.);', '', undefined, renderManager.maxHDRIntensity)
this.setDirty = this.setDirty.bind(this)
}
// names are incorrect. We read from `writeBuffer` and write to `readBuffer`. same in super class
render(renderer: IWebGLRenderer, writeBuffer?: WebGLRenderTarget|null, readBuffer?: WebGLRenderTarget, deltaTime?: number, maskActive?: boolean) {
if (!this.enabled) return
let needsSwap = false
renderer.userData.mainRenderPass = true
if (!this._doTransmissionFix && !this.renderManager.rgbm) {
super.render(renderer, writeBuffer || null, readBuffer, deltaTime, maskActive)
this.needsSwap = needsSwap
renderer.userData.mainRenderPass = undefined
return
}
const ud = renderer.userData
if (!ud) console.error('threejs is not patched. Use the @repalash/three.js-modded to this functionality.')
const useGBufferDepth = (this.renderManager.zPrepass || !this.renderManager.depthBuffer) && this.renderManager.gbufferTarget
let depthRenderBuffer: WebGLRenderbuffer | undefined = undefined
if (useGBufferDepth) {
const gbuffer = this.renderManager.gbufferTarget
if (gbuffer) {
const renderBufferProps = renderer.properties.get(gbuffer)
depthRenderBuffer = renderBufferProps.__webglDepthRenderbuffer || renderBufferProps.__webglDepthbuffer
}
if (!depthRenderBuffer) {
console.warn('No depth/gbuffer present for zPrepass.')
}
}
const lastReadBuffer = readBuffer
// if msaa we need to create a new multi sampled target to render to
// todo
readBuffer = this.renderManager.msaa ? this.opaqueTarget : readBuffer
// readBuffer = this.opaqueTarget
// if(readBuffer?.samples !== gbuffer?.samples)
// console.error('ExtendedRenderPass - readBuffer and gbuffer samples are not same', readBuffer?.samples, gbuffer?.samples)
let renderFn = ()=> {
super.render(renderer, null, readBuffer, deltaTime, maskActive, depthRenderBuffer) // read is write in super.render (RenderPass)
}
if (!this.renderManager.rgbm) {
// Opaque + Transparent
{
const curClear = this.clear
const curClearDepth = renderer.autoClearDepth
renderer.autoClearDepth = !useGBufferDepth
this.clear = true
renderer.renderWithModes({
shadowMapRender: true,
backgroundRender: this.renderBackground,
opaqueRender: true,
transparentRender: true,
transmissionRender: false,
}, renderFn)
this.clear = curClear
renderer.autoClearDepth = curClearDepth
}
// Transmissive
{
const source = !readBuffer ? undefined : readBuffer.texture
// todo: first check if any transmissive object is there to use this buffer
this.renderManager.blit(writeBuffer, {clear: true, source})
// viewer.renderer.blit(writeBuffer.texture as any, readBuffer as any, {})
// super.render(renderer, undefined as any, writeBuffer, deltaTime, maskActive); // copy read to write buffer
const curClear = this.clear
this.clear = false
// don't need this clear is already false
// const curClearDepth = renderer.autoClearDepth
// renderer.autoClearDepth = false
ud.transmissionRenderTarget = writeBuffer
ud.blurTransmissionTarget = this.blurTransmissionTarget && ud.transmissionRenderTarget.samples === 0 // todo: not working with msaa target. its fine now because writeBuffer is never multi sampled
renderer.renderWithModes({
shadowMapRender: false,
backgroundRender: false,
opaqueRender: false,
transparentRender: false,
transmissionRender: true,
}, renderFn)
ud.blurTransmissionTarget = undefined
ud.transmissionRenderTarget = undefined
// renderer.autoClearDepth = curClearDepth
this.clear = curClear
}
needsSwap = false
} else if (this.renderManager.rgbm) {
needsSwap = false
const renderToScreen = this.renderToScreen
if (renderToScreen && !writeBuffer) {
console.error('ExtendedRenderPass: renderToScreen is true but writeBuffer is not set, which is required for rgbm')
}
this.renderToScreen = false // for super RenderPass.render
if (renderer.info && !renderer.info.autoReset)
throw 'renderer.info.autoReset must be true' // it is required to check if any object is rendered in the scene, also frame count is maintained separately in the render manager, use that.
// Opaque
{
const curClearDepth = renderer.autoClearDepth
renderer.autoClearDepth = !useGBufferDepth
renderer.renderWithModes({
shadowMapRender: true,
backgroundRender: this.renderBackground,
opaqueRender: true,
transparentRender: false,
transmissionRender: false,
}, renderFn) // render to readBuffer
renderer.autoClearDepth = curClearDepth
}
if (!useGBufferDepth && readBuffer) {
const renderBufferProps2 = renderer.properties.get(readBuffer)
depthRenderBuffer = renderBufferProps2.__webglDepthRenderbuffer || renderBufferProps2.__webglDepthbuffer
}
// readBuffer has data
renderFn = ()=> {
super.render(renderer, null, this.transparentTarget as any, deltaTime, maskActive, depthRenderBuffer)
}
// Transparent
{
const curClear = this.clear
const curClearDepth = renderer.autoClearDepth
renderer.autoClearDepth = false
this.clear = true
renderer.renderWithModes({
shadowMapRender: false,
backgroundRender: false,
opaqueRender: false,
transparentRender: true,
transmissionRender: false,
}, renderFn) // render to transparentTarget
this.clear = curClear
renderer.autoClearDepth = curClearDepth
}
if (!renderer.info || renderer.info.render.calls > 0) {
// writeBuffer = transparentTarget + readBuffer
this._blendPass.uniforms.tDiffuse2.value = this.transparentTarget.texture
this._blendPass.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive)
needsSwap = true // writeBuffer has the data now.
}
// if needsSwap, writeBuffer has data, else readBuffer
// Transmission
{
const curClear = this.clear
this.clear = false // it is cleared in transparent pass above even if no object is rendered
// const curClearDepth = renderer.autoClearDepth
// renderer.autoClearDepth = false
// if needsSwap, writeBuffer has current data, else readBuffer
ud.transmissionRenderTarget = needsSwap ? writeBuffer : readBuffer
ud.blurTransmissionTarget = this.blurTransmissionTarget && ud.transmissionRenderTarget.samples === 0 // todo: not working with msaa
renderer.renderWithModes({
shadowMapRender: false,
backgroundRender: false,
opaqueRender: false,
transparentRender: false,
transmissionRender: true,
}, renderFn) // render to transparentTarget
ud.blurTransmissionTarget = undefined
ud.transmissionRenderTarget = undefined
// renderer.autoClearDepth = curClearDepth
this.clear = curClear
}
// console.log(renderer.info.render.calls)
if (!renderer.info || renderer.info.render.calls > 0) {
// console.log('missive blit', renderer.info.render.frame)
// writeBuffer = transparentTarget + readBuffer. opaque will overwrite opaque pixels again
this._blendPass.uniforms.tDiffuse2.value = this.transparentTarget.texture
this._blendPass.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive)
needsSwap = true // writeBuffer has the data now.
}
// if needsSwap, writeBuffer has data, else readBuffer
if (renderToScreen) {
this.renderToScreen = true
const tex = needsSwap ? writeBuffer?.texture : readBuffer?.texture
const source = Array.isArray(tex) ? tex[0] : tex
source && this.renderManager.blit(undefined, {
source, respectColorSpace: true,
})
// needsSwap = false
}
}
// todo no need to do this if renderToScreen is true
// resolve msaa
if (!needsSwap && lastReadBuffer !== readBuffer && readBuffer) {
// copy from readBuffer to lastReadBuffer
const source = Array.isArray(readBuffer.texture) ? readBuffer.texture[0] : readBuffer.texture
source && this.renderManager.blit(lastReadBuffer, {
source: source, clear: true,
})
readBuffer = lastReadBuffer
}
if (!this.preserveTransparentTarget)
this._releaseTransparentTarget()
if (!this.preserveOpaqueTarget)
this._releaseOpaqueTarget()
this.needsSwap = needsSwap
renderer.userData.mainRenderPass = undefined
}
public onDirty: (()=>void)[] = []
dispose() {
this._releaseTransparentTarget()
this.onDirty = []
this.scene = undefined
this.camera = undefined
super.dispose?.()
}
setDirty() {
this.onDirty.forEach(v=>v())
}
beforeRender(scene: IScene, camera: ICamera, _: IRenderManager): void {
this.scene = scene
this.camera = camera
}
uiConfig: UiObjectConfig = {
label: 'Render Pass',
type: 'folder',
children: generateUiConfig(this),
}
// legacy
/**
* @deprecated renamed to {@link isExtendedRenderPass}
*/
get isRenderPass2() {
console.error('isRenderPass2 is deprecated, use isExtendedRenderPass instead')
return true
}
}
/**
* @deprecated renamed to {@link ExtendedRenderPass}
*/
export class RenderPass2 extends ExtendedRenderPass {
constructor(...args: ConstructorParameters<typeof ExtendedRenderPass>) {
console.error('RenderPass2 is deprecated, use ExtendedRenderPass instead')
super(...args)
}
}