threepipe
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

ClearcoatTintPlugin.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import {Color} from 'three'
  2. import {AViewerPluginSync, ThreeViewer} from '../../viewer'
  3. import {uiFolderContainer, UiObjectConfig, uiToggle} from 'uiconfig.js'
  4. import {glsl, serialize} from 'ts-browser-helpers'
  5. import {IMaterialUserData, PhysicalMaterial} from '../../core'
  6. import {MaterialExtension, updateMaterialDefines} from '../../materials'
  7. import {shaderReplaceString, ThreeSerialization} from '../../utils'
  8. import {GLTFLoader2, GLTFWriter2} from '../../assetmanager'
  9. import type {GLTFLoaderPlugin, GLTFParser} from 'three/examples/jsm/loaders/GLTFLoader'
  10. /**
  11. * Clearcoat Tint Plugin
  12. * Adds a material extension to PhysicalMaterial which adds tint and thickness to the built-in clearcoat properties.
  13. * It also adds a UI to the material to edit the settings.
  14. * It uses WEBGI_materials_clearcoat_tint glTF extension to save the settings in glTF files.
  15. * @category Plugins
  16. */
  17. @uiFolderContainer('Clearcoat Tint (MatExt)')
  18. export class ClearcoatTintPlugin extends AViewerPluginSync<''> {
  19. static readonly PluginType = 'ClearcoatTintPlugin'
  20. @uiToggle('Enabled', (that: ClearcoatTintPlugin)=>({onChange: that.setDirty}))
  21. @serialize() enabled = true
  22. // private _defines: any = {
  23. // // eslint-disable-next-line @typescript-eslint/naming-convention
  24. // CLEARCOAT_TINT_DEBUG: false,
  25. // }
  26. private _uniforms: any = {
  27. ccTintColor: {value: new Color()},
  28. ccThickness: {value: 0.},
  29. ccIor: {value: 0.},
  30. }
  31. static AddClearcoatTint(material: PhysicalMaterial, params?: IMaterialUserData['_clearcoatTint']): IMaterialUserData['_clearcoatTint']|null {
  32. const ud = material?.userData
  33. if (!ud) return null
  34. if (!ud._clearcoatTint) ud._clearcoatTint = {}
  35. const tf = ud._clearcoatTint!
  36. tf.enableTint = true
  37. if (tf.tintColor === undefined) tf.tintColor = '#ffffff'
  38. if (tf.thickness === undefined) tf.thickness = 0.1
  39. if (tf.ior === undefined) tf.ior = 1.5
  40. params && Object.assign(tf, params)
  41. if (material.setDirty) material.setDirty()
  42. return tf
  43. }
  44. // private _multiplyPass?: MultiplyPass
  45. readonly materialExtension: MaterialExtension = {
  46. parsFragmentSnippet: (_, material: PhysicalMaterial)=>{
  47. if (this.isDisabled() || !material?.userData._clearcoatTint?.enableTint || !(material.clearcoat > 0)) return ''
  48. return glsl`
  49. uniform vec3 ccTintColor;
  50. uniform float ccThickness;
  51. uniform float ccIor;
  52. vec3 clearcoatTint(const in float dotNV, const in float dotNL, const in float clearcoat) {
  53. vec3 tint = ( ccThickness > 0. ? 1. - ccTintColor : ccTintColor); // Set thickness < 0 for glow.
  54. tint = exp(tint * -(ccThickness * ((dotNL + dotNV) / max(dotNL * dotNV, 1e-3)))); // beer's law
  55. return mix(vec3(1.0), tint, clearcoat);
  56. }
  57. `
  58. },
  59. shaderExtender: (shader, material: PhysicalMaterial) => {
  60. if (this.isDisabled() || !material?.userData._clearcoatTint?.enableTint || !(material.clearcoat > 0)) return
  61. // Note: clearcoat only considers specular, not diffuse
  62. shader.fragmentShader = shaderReplaceString(shader.fragmentShader,
  63. 'float dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );',
  64. 'float dotNVcc = saturate( dot( geometry.clearcoatNormal, -refract(geometry.viewDir, geometry.clearcoatNormal, 1./ccIor) ) );')
  65. // todo: we are considering all light is coming from env map, but we should consider light coming from light sources by seperating light and env map attenuation
  66. shader.fragmentShader = shaderReplaceString(shader.fragmentShader,
  67. 'outgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;',
  68. 'outgoingLight *= clearcoatTint(dotNVcc, dotNVcc, material.clearcoat);\n', {prepend: true})
  69. shader.defines.USE_UV = ''
  70. },
  71. onObjectRender: (_, material) => {
  72. const tfUd = material.userData._clearcoatTint
  73. if (!tfUd?.enableTint) return
  74. this._uniforms.ccTintColor.value.set(tfUd.tintColor) // could be number or string also, apart from Color
  75. this._uniforms.ccThickness.value = tfUd.thickness
  76. this._uniforms.ccIor.value = tfUd.ior
  77. updateMaterialDefines({
  78. // ...this._defines,
  79. ['CLEARCOAT_TINT_ENABLED']: +!this.isDisabled(),
  80. }, material)
  81. },
  82. extraUniforms: {
  83. ...this._uniforms,
  84. },
  85. computeCacheKey: (material1: PhysicalMaterial) => {
  86. return (this.isDisabled() ? '0' : '1') + (material1.userData._clearcoatTint?.enableTint ? '1' : '0') + (material1.clearcoat > 0 ? '1' : '0')
  87. },
  88. isCompatible: (material1: PhysicalMaterial) => {
  89. return material1.isPhysicalMaterial
  90. },
  91. getUiConfig: (material: PhysicalMaterial) => {
  92. const viewer = this._viewer!
  93. if (material.userData._clearcoatTint === undefined) material.userData._clearcoatTint = {}
  94. const state = material.userData._clearcoatTint
  95. const config: UiObjectConfig = {
  96. type: 'folder',
  97. label: 'Clearcoat Tint',
  98. onChange: (ev)=>{
  99. if (!ev.config) return
  100. this.setDirty()
  101. },
  102. children: [
  103. {
  104. type: 'checkbox',
  105. label: 'Enabled',
  106. get value() {
  107. return state.enableTint || false
  108. },
  109. set value(v) {
  110. if (v === state.enableTint) return
  111. if (v) {
  112. if (!ClearcoatTintPlugin.AddClearcoatTint(material))
  113. viewer.dialog.alert('Cannot add clearcoat tint.')
  114. } else {
  115. state.enableTint = false
  116. if (material.setDirty) material.setDirty()
  117. }
  118. config.uiRefresh?.(true, 'postFrame')
  119. },
  120. },
  121. {
  122. type: 'color',
  123. label: 'Tint color',
  124. hidden: () => !state.enableTint,
  125. property: [state, 'tintColor'],
  126. },
  127. {
  128. type: 'input',
  129. label: 'Thickness',
  130. hidden: () => !state.enableTint,
  131. property: [state, 'thickness'],
  132. },
  133. {
  134. type: 'slider',
  135. bounds: [0.8, 2.5],
  136. label: 'IOR',
  137. hidden: () => !state.enableTint,
  138. property: [state, 'ior'],
  139. },
  140. ],
  141. }
  142. return config
  143. },
  144. }
  145. setDirty = (): void => {
  146. this.materialExtension.setDirty?.()
  147. this._viewer?.setDirty()
  148. }
  149. private _loaderCreate({loader}: {loader: GLTFLoader2}) {
  150. if (!loader.isGLTFLoader2) return
  151. loader.register((p) => new GLTFMaterialsClearcoatTintExtensionImport(p))
  152. }
  153. constructor() {
  154. super()
  155. this._loaderCreate = this._loaderCreate.bind(this)
  156. }
  157. onAdded(v: ThreeViewer) {
  158. super.onAdded(v)
  159. // v.addEventListener('preRender', this._preRender)
  160. v.assetManager.materials.registerMaterialExtension(this.materialExtension)
  161. v.assetManager.importer.addEventListener('loaderCreate', this._loaderCreate as any)
  162. v.assetManager.exporter.getExporter('gltf', 'glb')?.extensions?.push(glTFMaterialsClearcoatTintExtensionExport)
  163. }
  164. onRemove(v: ThreeViewer) {
  165. v.assetManager.materials?.unregisterMaterialExtension(this.materialExtension)
  166. v.assetManager.importer?.removeEventListener('loaderCreate', this._loaderCreate as any)
  167. const exporter = v.assetManager.exporter.getExporter('gltf', 'glb')
  168. if (exporter) {
  169. const index = exporter.extensions?.indexOf(glTFMaterialsClearcoatTintExtensionExport)
  170. if (index !== undefined && index >= 0) exporter.extensions?.splice(index, 1)
  171. }
  172. return super.onRemove(v)
  173. }
  174. public static readonly CLEARCOAT_TINT_GLTF_EXTENSION = 'WEBGI_materials_clearcoat_tint'
  175. }
  176. declare module '../../core/IMaterial' {
  177. interface IMaterialUserData {
  178. _clearcoatTint?: {
  179. enableTint?: boolean
  180. tintColor?: Color|number|string
  181. thickness?: number
  182. ior?: number
  183. }
  184. }
  185. }
  186. /**
  187. * ClearcoatTint Materials Extension
  188. *
  189. * Specification: https://webgi.xyz/docs/gltf-extensions/WEBGI_materials_clearcoat_tint.html
  190. */
  191. class GLTFMaterialsClearcoatTintExtensionImport implements GLTFLoaderPlugin {
  192. public name: string
  193. public parser: GLTFParser
  194. constructor(parser: GLTFParser) {
  195. this.parser = parser
  196. this.name = ClearcoatTintPlugin.CLEARCOAT_TINT_GLTF_EXTENSION
  197. }
  198. async extendMaterialParams(materialIndex: number, materialParams: any) {
  199. const parser = this.parser
  200. const materialDef = parser.json.materials[materialIndex]
  201. if (!materialDef.extensions || !materialDef.extensions[this.name]) return
  202. const extension = materialDef.extensions[this.name]
  203. if (!materialParams.userData) materialParams.userData = {}
  204. ClearcoatTintPlugin.AddClearcoatTint(materialParams)
  205. ThreeSerialization.Deserialize(extension, materialParams.userData._clearcoatTint)
  206. }
  207. }
  208. const glTFMaterialsClearcoatTintExtensionExport = (w: GLTFWriter2)=> ({
  209. writeMaterial: (material: any, materialDef: any) => {
  210. if (!material.isMeshStandardMaterial || !material.userData._clearcoatTint?.enableTint) return
  211. materialDef.extensions = materialDef.extensions || {}
  212. const extensionDef: any = ThreeSerialization.Serialize(material.userData._clearcoatTint)
  213. materialDef.extensions[ ClearcoatTintPlugin.CLEARCOAT_TINT_GLTF_EXTENSION ] = extensionDef
  214. w.extensionsUsed[ ClearcoatTintPlugin.CLEARCOAT_TINT_GLTF_EXTENSION ] = true
  215. },
  216. })