| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- import {IMaterial, IMaterialUserData} from '../core'
- import {getOrCall} from 'ts-browser-helpers'
- import {shaderReplaceString} from '../utils/shader-helpers'
- import {Object3D, Shader, WebGLRenderer} from 'three'
- import {MaterialExtension} from './MaterialExtension'
- import {generateUUID} from '../three/utils/misc'
-
- export class MaterialExtender {
- static VoidMain = 'void main()'
-
- static ApplyMaterialExtensions(material: IMaterial, shader: Shader, materialExtensions: MaterialExtension[], renderer: WebGLRenderer) {
- for (const materialExtension of materialExtensions) {
- this.ApplyMaterialExtension(material, shader, materialExtension, renderer)
- }
- }
-
- static ApplyMaterialExtension(material: IMaterial, shader: Shader, materialExtension: MaterialExtension, renderer: WebGLRenderer) {
- // Add parsFragmentSnippet just before void main in fragment shader
- let a = getOrCall(materialExtension.parsFragmentSnippet, renderer, material) ?? ''
- if (a.length) {
- shader.fragmentShader = shaderReplaceString(shader.fragmentShader, this.VoidMain, '\n' + a + '\n', {prepend: true})
- }
- // Add parsVertexSnippet just before void main in vertex shader
- a = getOrCall(materialExtension.parsVertexSnippet, renderer, material) ?? ''
- if (a.length) {
- shader.vertexShader = shaderReplaceString(shader.vertexShader, this.VoidMain, '\n' + a + '\n', {prepend: true})
- }
- // Add extra uniforms
- if (materialExtension.extraUniforms) {
- shader.uniforms = Object.assign(shader.uniforms, materialExtension.extraUniforms)
- }
- // Add extra defines and set needsUpdate to true if needed
- if (materialExtension.extraDefines)
- updateMaterialDefines(materialExtension.extraDefines, material)
-
- // Call shaderExtender if defined
- materialExtension.shaderExtender?.(shader, material, renderer)
- // Save last shader so that it can be used to check if shader has changed in extensions
- material.lastShader = shader
- }
-
- static CacheKeyForExtensions(material: IMaterial, materialExtensions: MaterialExtension[]): string {
- let r = ''
- for (const materialExtension of materialExtensions) {
- r += this.CacheKeyForExtension(material, materialExtension)
- }
- return r
- }
-
- static CacheKeyForExtension(material: IMaterial, materialExtension: MaterialExtension): string {
- let r = ''
- if (materialExtension.computeCacheKey) r += getOrCall(materialExtension.computeCacheKey, material)
- if (materialExtension.extraDefines) r += Object.values(materialExtension.extraDefines).join('')
- return r
- }
-
- static RegisterExtensions(material: IMaterial, customMaterialExtensions?: MaterialExtension[]): MaterialExtension[] {
- const exts = []
- if (!Array.isArray(material.materialExtensions)) material.materialExtensions = []
- if (customMaterialExtensions)
- for (const ext of customMaterialExtensions) {
- if (!ext.isCompatible || !ext.isCompatible(material) || material.materialExtensions.includes(ext)) continue
- else exts.push(ext)
- if (!ext.uuid) ext.uuid = generateUUID()
- if (!ext.__setDirty) ext.__setDirty = ()=>{
- if (!ext.updateVersion) ext.updateVersion = 0
- ext.updateVersion++
- }
- if (!ext.setDirty) ext.setDirty = ext.__setDirty
- }
-
- material.materialExtensions = [...material.materialExtensions || [], ...exts]
-
- if (!(material as any).__ext_beforeRenderListen) {
- (material as any).__ext_beforeRenderListen = true
- material.addEventListener('beforeRender', materialBeforeRender)
- }
- if (!(material as any).__ext_afterRenderListen) {
- (material as any).__ext_afterRenderListen = true
- material.addEventListener('afterRender', materialAfterRender)
- }
- return exts
- }
-
- static UnregisterExtensions(material: IMaterial, customMaterialExtensions?: MaterialExtension[]) {
- if (customMaterialExtensions) {
- material.materialExtensions = material.materialExtensions?.filter((v)=>!customMaterialExtensions.includes(v)) || []
- }
- if (!material.materialExtensions?.length) {
- material.removeEventListener('beforeRender', materialBeforeRender)
- material.removeEventListener('afterRender', materialAfterRender)
- ;(material as any).__ext_beforeRenderListen = false
- ;(material as any).__ext_afterRenderListen = false
- }
- }
- }
-
- function updateMaterialDefines(defines: any, material: IMaterial) {
- if (!material.defines) {
- console.warn('Material does not have defines', material) // todo: check when material.defines is undefined
- material.defines = {}
- }
- let flag = false
- const entries = Object.entries(defines)
- for (const [key, val] of entries) {
- if (val === undefined) {
- if (material.defines[key] !== undefined) {
- delete material.defines[key]
- flag = true
- }
- } else if (material.defines[key] !== val) {
- material.defines[key] = val
- flag = true
- }
- }
- if (flag) material.needsUpdate = true
- }
-
- function materialBeforeRender({target, object, renderer}:{object?: Object3D, renderer?: WebGLRenderer, target: IMaterial}) {
- const material = target
- if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer')
- if (!material.materialExtensions) return
- for (const value of material.materialExtensions) {
- value.onObjectRender?.(object, material, renderer)
-
- if ((material as any).lastShader) {
- const updater = getOrCall(value.updaters) || []
- for (const v2 of updater) v2 && v2.updateShaderProperties((material as any).lastShader)
- }
- const udVersion: keyof IMaterialUserData = '_' + value.uuid + '_version' as any
- if (value.updateVersion !== material.userData[udVersion]) {
- material.userData[udVersion] = value.updateVersion
- material.needsUpdate = true
- }
- }
- }
-
- function materialAfterRender({target, object, renderer}:{object?: Object3D, renderer?: WebGLRenderer, target: IMaterial}) {
- const material = target
- if (!material || !object || !renderer) throw new Error('Invalid material, object or renderer')
- if (!material.materialExtensions) return
- for (const value of material.materialExtensions) {
- value.onAfterRender?.(object, material, renderer)
- }
- }
|