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.
236 lines (196 loc) • 9.54 kB
text/typescript
import type {ICamera, IMaterial, IMaterialParameters, IObject3D, PhysicalMaterial} from '../../core'
import {
BufferGeometry,
Camera,
DoubleSide,
GLSL1,
GLSL3, Group,
NormalMapTypes,
Object3D,
Scene,
ShaderMaterialParameters,
TangentSpaceNormalMap,
Texture,
UniformsLib,
UniformsUtils,
Vector2,
Vector4,
WebGLRenderer,
} from 'three'
import GBufferMatVert from './shaders/GBufferPlugin.mat.vert.glsl'
import GBufferMatFrag from './shaders/GBufferPlugin.mat.frag.glsl'
import {updateMaterialDefines} from '../../materials'
import {ShaderMaterial2} from '../../core/material/ShaderMaterial2'
export interface GBufferUpdaterContext {
material: IMaterial, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D
}
export interface GBufferUpdater {
updateGBufferFlags: (data: Vector4, context: GBufferUpdaterContext) => void
}
/**
* Renders DepthNormal to a texture and flags to another
*/
export class GBufferMaterial extends ShaderMaterial2 {
constructor(multipleRT = true, parameters?: ShaderMaterialParameters & IMaterialParameters) {
super({
vertexShader: GBufferMatVert,
fragmentShader: GBufferMatFrag,
uniforms: UniformsUtils.merge([
UniformsLib.common,
UniformsLib.bumpmap,
UniformsLib.normalmap,
UniformsLib.displacementmap,
{
cameraNearFar: {value: new Vector2(0.1, 1000)}, // this has to be set from outside
flags: {value: new Vector4(255, 255, 255, 255)},
},
]),
defines: {
// eslint-disable-next-line @typescript-eslint/naming-convention
IS_GLSL3: multipleRT ? '1' : '0',
},
glslVersion: multipleRT ? GLSL3 : GLSL1,
...parameters,
})
this.reset()
}
flagUpdaters: Map<string, GBufferUpdater['updateGBufferFlags']> = new Map()
normalMapType: NormalMapTypes = TangentSpaceNormalMap
flatShading = false
onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D, group: Group) {
super.onBeforeRender(renderer, scene, camera, geometry, object, group)
let isOverridden = false
let material = (object as any).material as IMaterial & Partial<PhysicalMaterial>
if (Array.isArray(material)) { // todo: add support for multi materials.
material = material[0]
}
if (material === this as any) {
material = (object as IObject3D).currentMaterial as IMaterial & Partial<PhysicalMaterial>
isOverridden = true
}
if (Array.isArray(material)) { // todo: add support for multi materials.
material = material[0]
}
if (!material) return
if (isOverridden) {
updateMaterialDefines({
['FORCED_LINEAR_DEPTH']: material.userData.forcedLinearDepth ?? undefined, // todo add to DepthBufferPlugin as well.
}, this)
} else {
const setMap = (key: keyof IMaterial) => {
const map = material[key]
if (!map) return
this.uniforms[key].value = map
if (!this.uniforms[key + 'Transform']) console.error('GBufferMaterial: ' + key + 'Transform is not defined in uniform')
else {
if ((map as Texture).isTexture)
renderer.materials.refreshTransformUniform((map as Texture), this.uniforms[key + 'Transform'])
}
}
setMap('map')
if (material.side !== undefined) this.side = material.side ?? DoubleSide
setMap('alphaMap')
if (material.alphaTest !== undefined) this.alphaTest = material.alphaTest < 1e-4 ? 1e-4 : material.alphaTest
if (material.alphaHash !== undefined) this.alphaHash = material.alphaHash
setMap('bumpMap')
if (material.bumpScale !== undefined) this.uniforms.bumpScale.value = material.bumpScale
setMap('normalMap')
if (material.normalScale !== undefined) this.uniforms.normalScale.value.copy(material.normalScale)
if (material.normalMapType !== undefined) this.normalMapType = material.normalMapType
if (material.flatShading !== undefined) this.flatShading = material.flatShading
setMap('displacementMap')
if (material.displacementScale !== undefined) this.uniforms.displacementScale.value = material.displacementScale
if (material.displacementBias !== undefined) this.uniforms.displacementBias.value = material.displacementBias
if (material.wireframe !== undefined) this.wireframe = material.wireframe
if (material.wireframeLinewidth !== undefined) this.wireframeLinewidth = material.wireframeLinewidth
updateMaterialDefines({
// ['USE_ALPHAMAP']: this.uniforms.alphaMap.value ? 1 : undefined,
['ALPHAMAP_UV']: this.uniforms.alphaMap.value ? 'uv' : undefined, // todo use getChannel, see WebGLPrograms.js
['USE_DISPLACEMENTMAP']: this.uniforms.displacementMap.value ? 1 : undefined,
['DISPLACEMENTMAP_UV']: this.uniforms.displacementMap.value ? 'uv' : undefined, // todo use getChannel, see WebGLPrograms.js
['ALPHA_I_RGBA_PACKING']: material.userData.ALPHA_I_RGBA_PACKING ? 1 : undefined,
['FORCED_LINEAR_DEPTH']: material.userData.forcedLinearDepth ?? undefined, // todo add to DepthBufferPlugin as well.
}, this)
}
const flags = this.uniforms.flags.value
this._updateFlagsUniform(flags, material, renderer, scene, camera, geometry, object)
;(camera as ICamera).updateShaderProperties(this) // for cameraNearFar
this.uniformsNeedUpdate = true
// todo: do the same in DepthBufferPlugin and NormalBufferPlugin
// what about the material extension settings in the userData of the source materials?
// wont this be very expensive? todo we should expect devs to expose their own gbuffer material instance if they want to use some extension
if (material.materialExtensions?.length) {
this.registerMaterialExtensions(material.materialExtensions)
}
// this.transparent = true
this.needsUpdate = true
// @ts-expect-error todo add to type
renderer.resetCurrentMaterial && renderer.resetCurrentMaterial()
}
protected _updateFlagsUniform(flags: Vector4, material: IMaterial & Partial<PhysicalMaterial>, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D) {
/*
GBuffer Flags has the following data
1st Rendertarget has Depth and Normal buffers
2nd Render Target::
x : Empty
y : first 3 bits lut index, second 5 bits bevel radius
z : material id (userData.gBufferData?.materialId, userData.matId)
w : this field is for setting bits - lutEnable-0, tonemap-1, bloom-2, ssao(cast)-3, dof-4, diamondMask-5
*/
flags.set(255, 255, 255, 255)
const materialId = material.userData.gBufferData?.materialId ?? material.userData.matId // matId for backward compatibility
flags.z = materialId || 0
this.flagUpdaters.forEach((updater) => updater(flags, {
material,
renderer,
scene,
camera,
geometry,
object,
}))
flags.x /= 255
flags.y /= 255
flags.z /= 255
flags.w /= 255
}
onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D, group: Group) {
super.onAfterRender(renderer, scene, camera, geometry, object, group)
let material = (object as any).material as IMaterial & Partial<PhysicalMaterial>
if (Array.isArray(material)) { // todo: add support for multi materials.
material = material[0]
}
if (!material || material === this as any) return
if (material.materialExtensions?.length) {
this.unregisterMaterialExtensions(material.materialExtensions)
}
this.reset()
}
reset() {
this.uniforms.map.value = null
this.side = DoubleSide
this.uniforms.alphaMap.value = null
this.alphaTest = 0.001
this.alphaHash = false
this.uniforms.bumpMap.value = null
this.uniforms.bumpScale.value = 1
this.uniforms.normalMap.value = null
this.uniforms.normalScale.value.set(1, 1)
this.normalMapType = TangentSpaceNormalMap
this.flatShading = false
this.uniforms.displacementMap.value = null
this.uniforms.displacementScale.value = 1
this.uniforms.displacementBias.value = 0
this.uniforms.flags.value.set(255, 255, 255, 255)
this.wireframe = false
this.wireframeLinewidth = 1
}
}
/**
* @deprecated use GBufferMaterial instead
*/
export class DepthNormalMaterial extends GBufferMaterial {
constructor(multipleRT: boolean, parameters?: ShaderMaterialParameters & IMaterialParameters) {
super(multipleRT, parameters)
console.warn('DepthNormalMaterial is deprecated, use GBufferMaterial instead')
}
}