threepipe
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PhysicalMaterial.ts 13KB

1 年之前
1 年之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import {generateUiConfig, UiObjectConfig} from 'uiconfig.js'
  2. import {
  3. BufferGeometry,
  4. Camera,
  5. Color,
  6. IUniform,
  7. Material,
  8. MeshPhysicalMaterial,
  9. MeshPhysicalMaterialParameters,
  10. Object3D,
  11. Scene,
  12. Shader,
  13. TangentSpaceNormalMap,
  14. Vector2,
  15. WebGLRenderer,
  16. } from 'three'
  17. import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils'
  18. import {
  19. IMaterial,
  20. IMaterialEvent,
  21. IMaterialEventTypes,
  22. IMaterialGenerator,
  23. IMaterialParameters,
  24. IMaterialTemplate,
  25. IMaterialUserData,
  26. } from '../IMaterial'
  27. import {MaterialExtension} from '../../materials'
  28. import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons'
  29. import {IObject3D} from '../IObject'
  30. import {ITexture} from '../ITexture'
  31. import {iMaterialUI} from './IMaterialUi'
  32. export type PhysicalMaterialEventTypes = IMaterialEventTypes | ''
  33. /**
  34. * And extension of three.js MeshPhysicalMaterial that can be assigned to objects, and support threepipe features, uiconfig, and serialization.
  35. *
  36. * @category Materials
  37. */
  38. export class PhysicalMaterial extends MeshPhysicalMaterial<IMaterialEvent, PhysicalMaterialEventTypes> implements IMaterial<IMaterialEvent, PhysicalMaterialEventTypes> {
  39. declare ['constructor']: typeof PhysicalMaterial
  40. public static readonly TypeSlug = 'pmat'
  41. public static readonly TYPE = 'PhysicalMaterial' // not using .type because it is used by three.js
  42. assetType = 'material' as const
  43. declare userData: IMaterialUserData
  44. public readonly isPhysicalMaterial = true
  45. readonly appliedMeshes: Set<IObject3D> = new Set()
  46. readonly setDirty = iMaterialCommons.setDirty
  47. dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)}
  48. clone(): this {return iMaterialCommons.clone(super.clone).call(this)}
  49. dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)}
  50. generator?: IMaterialGenerator
  51. map: ITexture | null = null
  52. alphaMap: ITexture | null = null
  53. roughnessMap: ITexture | null = null
  54. metalnessMap: ITexture | null = null
  55. normalMap: ITexture | null = null
  56. bumpMap: ITexture | null = null
  57. displacementMap: ITexture | null = null
  58. constructor({customMaterialExtensions, ...parameters}: MeshPhysicalMaterialParameters & IMaterialParameters = {}) {
  59. super()
  60. this.fog = false
  61. this.attenuationDistance = 0 // infinite distance (for Ui)
  62. this.setDirty = this.setDirty.bind(this)
  63. if (customMaterialExtensions) this.registerMaterialExtensions(customMaterialExtensions)
  64. iMaterialCommons.upgradeMaterial.call(this)
  65. this.setValues(parameters)
  66. }
  67. // region Material Extension
  68. materialExtensions: MaterialExtension[] = []
  69. extraUniformsToUpload: Record<string, IUniform> = {}
  70. registerMaterialExtensions = iMaterialCommons.registerMaterialExtensions
  71. unregisterMaterialExtensions = iMaterialCommons.unregisterMaterialExtensions
  72. customProgramCacheKey(): string {
  73. return super.customProgramCacheKey() + iMaterialCommons.customProgramCacheKey.call(this)
  74. }
  75. onBeforeCompile(shader: Shader&{defines: any}, renderer: WebGLRenderer): void { // shader is not Shader but WebglUniforms.getParameters return value type so includes defines
  76. const f = [
  77. ['vec3 totalDiffuse = ', 'afterModulation'],
  78. ['#include <aomap_fragment>', 'beforeModulation'],
  79. ['#include <lights_physical_fragment>', 'beforeAccumulation'],
  80. ['#include <clipping_planes_fragment>', 'mainStart'],
  81. ]
  82. const v = [
  83. ['#include <uv_vertex>', 'mainStart'],
  84. ]
  85. for (const vElement of v) shader.vertexShader = shaderReplaceString(shader.vertexShader, vElement[0], '#glMarker ' + vElement[1] + '\n' + vElement[0])
  86. for (const fElement of f) shader.fragmentShader = shaderReplaceString(shader.fragmentShader, fElement[0], '#glMarker ' + fElement[1] + '\n' + fElement[0])
  87. // for NaN. todo do the same in Unlit and line materials?
  88. shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#include <opaque_fragment>', 'gl_FragColor = clamp(gl_FragColor, 0.0, 1000.0);\n', {append: true})
  89. iMaterialCommons.onBeforeCompile.call(this, shader, renderer)
  90. shader.defines && (shader.defines.INVERSE_ALPHAMAP = this.userData.inverseAlphaMap ? 1 : 0)
  91. super.onBeforeCompile(shader, renderer)
  92. }
  93. onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D): void {
  94. super.onBeforeRender(renderer, scene, camera, geometry, object)
  95. iMaterialCommons.onBeforeRender.call(this, renderer, scene, camera, geometry, object)
  96. const t = this.userData.inverseAlphaMap ? 1 : 0
  97. if (t !== this.defines.INVERSE_ALPHAMAP) {
  98. this.defines.INVERSE_ALPHAMAP = t
  99. this.needsUpdate = true
  100. }
  101. }
  102. onAfterRender = iMaterialCommons.onAfterRenderOverride(super.onAfterRender)
  103. // endregion
  104. // region UI Config
  105. // todo dispose ui config
  106. uiConfig: UiObjectConfig = {
  107. type: 'folder',
  108. label: 'Physical Material',
  109. uuid: 'MPM2_' + this.uuid,
  110. expanded: true,
  111. onChange: (ev)=>{
  112. if (!ev.config || ev.config.onChange) return
  113. // todo frameFade
  114. // todo set needsUpdate true only for properties that require it like maps.
  115. this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last})
  116. },
  117. children: [
  118. ...iMaterialUI.base(this),
  119. ...generateUiConfig(this),
  120. iMaterialUI.blending(this),
  121. iMaterialUI.roughMetal(this),
  122. iMaterialUI.bumpNormal(this),
  123. iMaterialUI.emission(this),
  124. iMaterialUI.transmission(this),
  125. iMaterialUI.environment(this),
  126. iMaterialUI.aoLightMap(this),
  127. iMaterialUI.clearcoat(this),
  128. iMaterialUI.iridescence(this),
  129. iMaterialUI.sheen(this),
  130. iMaterialUI.polygonOffset(this),
  131. ...iMaterialUI.misc(this),
  132. ],
  133. }
  134. // endregion UI Config
  135. // region Serialization
  136. /**
  137. * Sets the values of this material based on the values of the passed material or an object with material properties
  138. * The input is expected to be a valid material or a deserialized material parameters object(including the deserialized userdata)
  139. * @param parameters - material or material parameters object
  140. * @param allowInvalidType - if true, the type of the oldMaterial is not checked. Objects without type are always allowed.
  141. * @param clearCurrentUserData - if undefined, then depends on material.isMaterial. if true, the current userdata is cleared before setting the new values, because it can have data which wont be overwritten if not present in the new material.
  142. */
  143. setValues(parameters: Material|(MeshPhysicalMaterialParameters&{type?:string}), allowInvalidType = true, clearCurrentUserData: boolean|undefined = undefined): this {
  144. if (!parameters) return this
  145. if (parameters.type && !allowInvalidType && !['MeshPhysicalMaterial', 'MeshStandardMaterial', 'MeshStandardMaterial2', this.constructor.TYPE].includes(parameters.type)) {
  146. console.error('Material type is not supported:', parameters.type)
  147. return this
  148. }
  149. // Blender exporter used to export a scalar. See three.js:#7459
  150. if (typeof (<any>parameters).normalScale === 'number') {
  151. (<any>parameters).normalScale = [(<any>parameters).normalScale, (<any>parameters).normalScale]
  152. }
  153. if (clearCurrentUserData === undefined) clearCurrentUserData = (<Material>parameters).isMaterial
  154. if (clearCurrentUserData) this.userData = {}
  155. iMaterialCommons.setValues(super.setValues).call(this, parameters)
  156. if (!isFinite(this.attenuationDistance)) this.attenuationDistance = 0 // hack for ui
  157. this.userData.uuid = this.uuid
  158. return this
  159. }
  160. copy(source: Material|any): this {
  161. return this.setValues(source, false)
  162. }
  163. /**
  164. * Serializes this material to JSON.
  165. * @param meta - metadata for serialization
  166. * @param _internal - Calls only super.toJSON, does internal three.js serialization and `@serialize` tags. Set it to true only if you know what you are doing. This is used in Serialization->serializer->material
  167. */
  168. toJSON(meta?: SerializationMetaType, _internal = false): any {
  169. if (_internal) return {
  170. ...super.toJSON(meta),
  171. ...ThreeSerialization.Serialize(this, meta, true), // this will serialize the properties of this class(like defined with @serialize and @serialize attribute)
  172. }
  173. return ThreeSerialization.Serialize(this, meta, false) // this will call toJSON again, but with baseOnly=true, that's why we set isThis to false.
  174. }
  175. /**
  176. * Deserializes the material from JSON.
  177. * Note: some properties that are not serialized in Material.toJSON when they are default values (like side, alphaTest, blending, maps), they wont be reverted back if not present in JSON
  178. * If _internal = true, Textures should be loaded and in meta.textures before calling this method.
  179. * @param data
  180. * @param meta
  181. * @param _internal
  182. */
  183. fromJSON(data: any, meta?: SerializationMetaType, _internal = false): this | null {
  184. if (_internal) {
  185. ThreeSerialization.Deserialize(data, this, meta, true)
  186. return this.setValues(data)
  187. }
  188. this.dispatchEvent({type: 'beforeDeserialize', data, meta, bubbleToObject: true, bubbleToParent: true})
  189. return this
  190. }
  191. // endregion
  192. // used for serialization
  193. static readonly MaterialProperties = {
  194. // keep updated with properties in MeshStandardMaterial.js
  195. ...threeMaterialPropList,
  196. color: new Color(0xffffff),
  197. roughness: 1,
  198. metalness: 0,
  199. map: null,
  200. lightMap: null,
  201. lightMapIntensity: 1,
  202. aoMap: null,
  203. aoMapIntensity: 1,
  204. emissive: '#000000',
  205. emissiveIntensity: 1,
  206. emissiveMap: null,
  207. bumpMap: null,
  208. bumpScale: 1,
  209. normalMap: null,
  210. normalMapType: TangentSpaceNormalMap,
  211. normalScale: new Vector2(1, 1),
  212. displacementMap: null,
  213. displacementScale: 1,
  214. displacementBias: 0,
  215. roughnessMap: null,
  216. metalnessMap: null,
  217. alphaMap: null,
  218. envMap: null,
  219. envMapIntensity: 1,
  220. // refractionRatio: 0,
  221. wireframe: false,
  222. wireframeLinewidth: 1,
  223. wireframeLinecap: 'round',
  224. wireframeLinejoin: 'round',
  225. flatShading: false,
  226. fog: true,
  227. // skinning: false,
  228. // vertexTangents: false, //removed from threejs
  229. // morphTargets: false,
  230. // morphNormals: false,
  231. // GLTF Extensions // todo: supported anywhere?
  232. // glossiness: 0,
  233. // glossinessMap: null,
  234. // specularColor: new Color(0),
  235. // specularColorMap: null,
  236. // keep updated with properties in MeshPhysicalMaterial.js
  237. clearcoat: 0,
  238. clearcoatMap: null,
  239. clearcoatRoughness: 0,
  240. clearcoatRoughnessMap: null,
  241. clearcoatNormalScale: new Vector2(1, 1),
  242. clearcoatNormalMap: null,
  243. reflectivity: 0.5, // because this is used in Material.js->toJSON and fromJSON instead of ior
  244. iridescence: 0,
  245. iridescenceMap: null,
  246. iridescenceIOR: 1.3,
  247. iridescenceThicknessRange: [100, 400],
  248. iridescenceThicknessMap: null,
  249. sheen: 0,
  250. sheenColor: new Color(0x000000),
  251. sheenColorMap: null,
  252. sheenRoughness: 1.0,
  253. sheenRoughnessMap: null,
  254. transmission: 0,
  255. transmissionMap: null,
  256. thickness: 0,
  257. thicknessMap: null,
  258. attenuationDistance: Infinity,
  259. attenuationColor: new Color(1, 1, 1),
  260. specularIntensity: 1.0,
  261. specularIntensityMap: null,
  262. specularColor: new Color(1, 1, 1),
  263. specularColorMap: null,
  264. }
  265. static MaterialTemplate: IMaterialTemplate<PhysicalMaterial, Partial<typeof PhysicalMaterial.MaterialProperties>> = {
  266. materialType: PhysicalMaterial.TYPE,
  267. name: 'physical',
  268. typeSlug: PhysicalMaterial.TypeSlug,
  269. alias: ['standard', 'physical', PhysicalMaterial.TYPE, PhysicalMaterial.TypeSlug, 'MeshStandardMaterial', 'MeshStandardMaterial2', 'MeshPhysicalMaterial'],
  270. params: {
  271. color: new Color(1, 1, 1),
  272. },
  273. generator: (params) => {
  274. return new PhysicalMaterial(params)
  275. },
  276. }
  277. }
  278. export class MeshStandardMaterial2 extends PhysicalMaterial {
  279. constructor(parameters?: MeshPhysicalMaterialParameters) {
  280. super(parameters)
  281. console.error('MeshStandardMaterial2 is deprecated, use UnlitMaterial instead')
  282. }
  283. }