reflectionTextureBaseBlock.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. import { NodeMaterialBlock } from '../../nodeMaterialBlock';
  2. import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
  3. import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
  4. import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
  5. import { BaseTexture } from '../../../Textures/baseTexture';
  6. import { AbstractMesh } from '../../../../Meshes/abstractMesh';
  7. import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
  8. import { Effect } from '../../../effect';
  9. import { Mesh } from '../../../../Meshes/mesh';
  10. import { Nullable } from '../../../../types';
  11. import { _TypeStore } from '../../../../Misc/typeStore';
  12. import { Scene } from '../../../../scene';
  13. import { InputBlock } from '../Input/inputBlock';
  14. import { NodeMaterialSystemValues } from '../../Enums/nodeMaterialSystemValues';
  15. import { Constants } from '../../../../Engines/constants';
  16. import "../../../../Shaders/ShadersInclude/reflectionFunction";
  17. import { CubeTexture } from '../../../Textures/cubeTexture';
  18. import { Texture } from '../../../Textures/texture';
  19. import { Engine } from "../../../../Engines/engine";
  20. /**
  21. * Base block used to read a reflection texture from a sampler
  22. */
  23. export abstract class ReflectionTextureBaseBlock extends NodeMaterialBlock {
  24. /** @hidden */
  25. public _define3DName: string;
  26. /** @hidden */
  27. public _defineCubicName: string;
  28. /** @hidden */
  29. public _defineExplicitName: string;
  30. /** @hidden */
  31. public _defineProjectionName: string;
  32. /** @hidden */
  33. public _defineLocalCubicName: string;
  34. /** @hidden */
  35. public _defineSphericalName: string;
  36. /** @hidden */
  37. public _definePlanarName: string;
  38. /** @hidden */
  39. public _defineEquirectangularName: string;
  40. /** @hidden */
  41. public _defineMirroredEquirectangularFixedName: string;
  42. /** @hidden */
  43. public _defineEquirectangularFixedName: string;
  44. /** @hidden */
  45. public _defineSkyboxName: string;
  46. /** @hidden */
  47. public _defineOppositeZ: string;
  48. /** @hidden */
  49. public _cubeSamplerName: string;
  50. /** @hidden */
  51. public _2DSamplerName: string;
  52. protected _positionUVWName: string;
  53. protected _directionWName: string;
  54. protected _reflectionVectorName: string;
  55. /** @hidden */
  56. public _reflectionCoordsName: string;
  57. /** @hidden */
  58. public _reflectionMatrixName: string;
  59. protected _reflectionColorName: string;
  60. protected _texture: Nullable<BaseTexture>;
  61. /**
  62. * Gets or sets the texture associated with the node
  63. */
  64. public get texture(): Nullable<BaseTexture> {
  65. return this._texture;
  66. }
  67. public set texture(texture: Nullable<BaseTexture>) {
  68. if (this._texture === texture) {
  69. return;
  70. }
  71. const scene = texture?.getScene() ?? Engine.LastCreatedScene;
  72. if (!texture && scene) {
  73. scene.markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag, (mat) => {
  74. return mat.hasTexture(this._texture!);
  75. });
  76. }
  77. this._texture = texture;
  78. if (texture && scene) {
  79. scene.markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag, (mat) => {
  80. return mat.hasTexture(texture);
  81. });
  82. }
  83. }
  84. /**
  85. * Create a new ReflectionTextureBaseBlock
  86. * @param name defines the block name
  87. */
  88. public constructor(name: string) {
  89. super(name, NodeMaterialBlockTargets.VertexAndFragment);
  90. }
  91. /**
  92. * Gets the current class name
  93. * @returns the class name
  94. */
  95. public getClassName() {
  96. return "ReflectionTextureBaseBlock";
  97. }
  98. /**
  99. * Gets the world position input component
  100. */
  101. public abstract get position(): NodeMaterialConnectionPoint;
  102. /**
  103. * Gets the world position input component
  104. */
  105. public abstract get worldPosition(): NodeMaterialConnectionPoint;
  106. /**
  107. * Gets the world normal input component
  108. */
  109. public abstract get worldNormal(): NodeMaterialConnectionPoint;
  110. /**
  111. * Gets the world input component
  112. */
  113. public abstract get world(): NodeMaterialConnectionPoint;
  114. /**
  115. * Gets the camera (or eye) position component
  116. */
  117. public abstract get cameraPosition(): NodeMaterialConnectionPoint;
  118. /**
  119. * Gets the view input component
  120. */
  121. public abstract get view(): NodeMaterialConnectionPoint;
  122. protected _getTexture(): Nullable<BaseTexture> {
  123. return this.texture;
  124. }
  125. public autoConfigure(material: NodeMaterial) {
  126. if (!this.position.isConnected) {
  127. let positionInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "position");
  128. if (!positionInput) {
  129. positionInput = new InputBlock("position");
  130. positionInput.setAsAttribute();
  131. }
  132. positionInput.output.connectTo(this.position);
  133. }
  134. if (!this.world.isConnected) {
  135. let worldInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.World);
  136. if (!worldInput) {
  137. worldInput = new InputBlock("world");
  138. worldInput.setAsSystemValue(NodeMaterialSystemValues.World);
  139. }
  140. worldInput.output.connectTo(this.world);
  141. }
  142. if (this.view && !this.view.isConnected) {
  143. let viewInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.View);
  144. if (!viewInput) {
  145. viewInput = new InputBlock("view");
  146. viewInput.setAsSystemValue(NodeMaterialSystemValues.View);
  147. }
  148. viewInput.output.connectTo(this.view);
  149. }
  150. }
  151. public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
  152. if (!defines._areTexturesDirty) {
  153. return;
  154. }
  155. const texture = this._getTexture();
  156. if (!texture || !texture.getTextureMatrix) {
  157. return;
  158. }
  159. defines.setValue(this._define3DName, texture.isCube, true);
  160. defines.setValue(this._defineLocalCubicName, (<any>texture).boundingBoxSize ? true : false, true);
  161. defines.setValue(this._defineExplicitName, texture.coordinatesMode === Constants.TEXTURE_EXPLICIT_MODE, true);
  162. defines.setValue(this._defineSkyboxName, texture.coordinatesMode === Constants.TEXTURE_SKYBOX_MODE, true);
  163. defines.setValue(this._defineCubicName, texture.coordinatesMode === Constants.TEXTURE_CUBIC_MODE || texture.coordinatesMode === Constants.TEXTURE_INVCUBIC_MODE, true);
  164. defines.setValue("INVERTCUBICMAP", texture.coordinatesMode === Constants.TEXTURE_INVCUBIC_MODE, true);
  165. defines.setValue(this._defineSphericalName, texture.coordinatesMode === Constants.TEXTURE_SPHERICAL_MODE, true);
  166. defines.setValue(this._definePlanarName, texture.coordinatesMode === Constants.TEXTURE_PLANAR_MODE, true);
  167. defines.setValue(this._defineProjectionName, texture.coordinatesMode === Constants.TEXTURE_PROJECTION_MODE, true);
  168. defines.setValue(this._defineEquirectangularName, texture.coordinatesMode === Constants.TEXTURE_EQUIRECTANGULAR_MODE, true);
  169. defines.setValue(this._defineEquirectangularFixedName, texture.coordinatesMode === Constants.TEXTURE_FIXED_EQUIRECTANGULAR_MODE, true);
  170. defines.setValue(this._defineMirroredEquirectangularFixedName, texture.coordinatesMode === Constants.TEXTURE_FIXED_EQUIRECTANGULAR_MIRRORED_MODE, true);
  171. }
  172. public isReady() {
  173. const texture = this._getTexture();
  174. if (texture && !texture.isReadyOrNotBlocking()) {
  175. return false;
  176. }
  177. return true;
  178. }
  179. public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
  180. const texture = this._getTexture();
  181. if (!mesh || !texture) {
  182. return;
  183. }
  184. effect.setMatrix(this._reflectionMatrixName, texture.getReflectionTextureMatrix());
  185. if (texture.isCube) {
  186. effect.setTexture(this._cubeSamplerName, texture);
  187. } else {
  188. effect.setTexture(this._2DSamplerName, texture);
  189. }
  190. }
  191. /**
  192. * Gets the code to inject in the vertex shader
  193. * @param state current state of the node material building
  194. * @returns the shader code
  195. */
  196. public handleVertexSide(state: NodeMaterialBuildState): string {
  197. this._define3DName = state._getFreeDefineName("REFLECTIONMAP_3D");
  198. this._defineCubicName = state._getFreeDefineName("REFLECTIONMAP_CUBIC");
  199. this._defineSphericalName = state._getFreeDefineName("REFLECTIONMAP_SPHERICAL");
  200. this._definePlanarName = state._getFreeDefineName("REFLECTIONMAP_PLANAR");
  201. this._defineProjectionName = state._getFreeDefineName("REFLECTIONMAP_PROJECTION");
  202. this._defineExplicitName = state._getFreeDefineName("REFLECTIONMAP_EXPLICIT");
  203. this._defineEquirectangularName = state._getFreeDefineName("REFLECTIONMAP_EQUIRECTANGULAR");
  204. this._defineLocalCubicName = state._getFreeDefineName("USE_LOCAL_REFLECTIONMAP_CUBIC");
  205. this._defineMirroredEquirectangularFixedName = state._getFreeDefineName("REFLECTIONMAP_MIRROREDEQUIRECTANGULAR_FIXED");
  206. this._defineEquirectangularFixedName = state._getFreeDefineName("REFLECTIONMAP_EQUIRECTANGULAR_FIXED");
  207. this._defineSkyboxName = state._getFreeDefineName("REFLECTIONMAP_SKYBOX");
  208. this._defineOppositeZ = state._getFreeDefineName("REFLECTIONMAP_OPPOSITEZ");
  209. this._reflectionMatrixName = state._getFreeVariableName("reflectionMatrix");
  210. state._emitUniformFromString(this._reflectionMatrixName, "mat4");
  211. let code = "";
  212. let worldPosVaryingName = "v_" + this.worldPosition.associatedVariableName;
  213. if (state._emitVaryingFromString(worldPosVaryingName, "vec4")) {
  214. code += `${worldPosVaryingName} = ${this.worldPosition.associatedVariableName};\r\n`;
  215. }
  216. this._positionUVWName = state._getFreeVariableName("positionUVW");
  217. this._directionWName = state._getFreeVariableName("directionW");
  218. if (state._emitVaryingFromString(this._positionUVWName, "vec3", this._defineSkyboxName)) {
  219. code += `#ifdef ${this._defineSkyboxName}\r\n`;
  220. code += `${this._positionUVWName} = ${this.position.associatedVariableName}.xyz;\r\n`;
  221. code += `#endif\r\n`;
  222. }
  223. if (state._emitVaryingFromString(this._directionWName, "vec3", `defined(${this._defineEquirectangularFixedName}) || defined(${this._defineMirroredEquirectangularFixedName})`)) {
  224. code += `#if defined(${this._defineEquirectangularFixedName}) || defined(${this._defineMirroredEquirectangularFixedName})\r\n`;
  225. code += `${this._directionWName} = normalize(vec3(${this.world.associatedVariableName} * vec4(${this.position.associatedVariableName}.xyz, 0.0)));\r\n`;
  226. code += `#endif\r\n`;
  227. }
  228. return code;
  229. }
  230. /**
  231. * Handles the inits for the fragment code path
  232. * @param state node material build state
  233. */
  234. public handleFragmentSideInits(state: NodeMaterialBuildState) {
  235. state.sharedData.blockingBlocks.push(this);
  236. state.sharedData.textureBlocks.push(this);
  237. // Samplers
  238. this._cubeSamplerName = state._getFreeVariableName(this.name + "CubeSampler");
  239. state.samplers.push(this._cubeSamplerName);
  240. this._2DSamplerName = state._getFreeVariableName(this.name + "2DSampler");
  241. state.samplers.push(this._2DSamplerName);
  242. state._samplerDeclaration += `#ifdef ${this._define3DName}\r\n`;
  243. state._samplerDeclaration += `uniform samplerCube ${this._cubeSamplerName};\r\n`;
  244. state._samplerDeclaration += `#else\r\n`;
  245. state._samplerDeclaration += `uniform sampler2D ${this._2DSamplerName};\r\n`;
  246. state._samplerDeclaration += `#endif\r\n`;
  247. // Fragment
  248. state.sharedData.blocksWithDefines.push(this);
  249. state.sharedData.bindableBlocks.push(this);
  250. let comments = `//${this.name}`;
  251. state._emitFunction("ReciprocalPI", "#define RECIPROCAL_PI2 0.15915494", "");
  252. state._emitFunctionFromInclude("reflectionFunction", comments, {
  253. replaceStrings: [
  254. { search: /vec3 computeReflectionCoords/g, replace: "void DUMMYFUNC" }
  255. ]
  256. });
  257. this._reflectionColorName = state._getFreeVariableName("reflectionColor");
  258. this._reflectionVectorName = state._getFreeVariableName("reflectionUVW");
  259. this._reflectionCoordsName = state._getFreeVariableName("reflectionCoords");
  260. }
  261. /**
  262. * Generates the reflection coords code for the fragment code path
  263. * @param worldNormalVarName name of the world normal variable
  264. * @param worldPos name of the world position variable. If not provided, will use the world position connected to this block
  265. * @param onlyReflectionVector if true, generates code only for the reflection vector computation, not for the reflection coordinates
  266. * @returns the shader code
  267. */
  268. public handleFragmentSideCodeReflectionCoords(worldNormalVarName: string, worldPos?: string, onlyReflectionVector = false): string {
  269. if (!worldPos) {
  270. worldPos = `v_${this.worldPosition.associatedVariableName}`;
  271. }
  272. let reflectionMatrix = this._reflectionMatrixName;
  273. let direction = `normalize(${this._directionWName})`;
  274. let positionUVW = `${this._positionUVWName}`;
  275. let vEyePosition = `${this.cameraPosition.associatedVariableName}`;
  276. let view = `${this.view.associatedVariableName}`;
  277. worldNormalVarName += ".xyz";
  278. let code = `
  279. #ifdef ${this._defineMirroredEquirectangularFixedName}
  280. vec3 ${this._reflectionVectorName} = computeMirroredFixedEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${direction});
  281. #endif
  282. #ifdef ${this._defineEquirectangularFixedName}
  283. vec3 ${this._reflectionVectorName} = computeFixedEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${direction});
  284. #endif
  285. #ifdef ${this._defineEquirectangularName}
  286. vec3 ${this._reflectionVectorName} = computeEquirectangularCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix});
  287. #endif
  288. #ifdef ${this._defineSphericalName}
  289. vec3 ${this._reflectionVectorName} = computeSphericalCoords(${worldPos}, ${worldNormalVarName}, ${view}, ${reflectionMatrix});
  290. #endif
  291. #ifdef ${this._definePlanarName}
  292. vec3 ${this._reflectionVectorName} = computePlanarCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix});
  293. #endif
  294. #ifdef ${this._defineCubicName}
  295. #ifdef ${this._defineLocalCubicName}
  296. vec3 ${this._reflectionVectorName} = computeCubicLocalCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix}, vReflectionSize, vReflectionPosition);
  297. #else
  298. vec3 ${this._reflectionVectorName} = computeCubicCoords(${worldPos}, ${worldNormalVarName}, ${vEyePosition}.xyz, ${reflectionMatrix});
  299. #endif
  300. #endif
  301. #ifdef ${this._defineProjectionName}
  302. vec3 ${this._reflectionVectorName} = computeProjectionCoords(${worldPos}, ${view}, ${reflectionMatrix});
  303. #endif
  304. #ifdef ${this._defineSkyboxName}
  305. vec3 ${this._reflectionVectorName} = computeSkyBoxCoords(${positionUVW}, ${reflectionMatrix});
  306. #endif
  307. #ifdef ${this._defineExplicitName}
  308. vec3 ${this._reflectionVectorName} = vec3(0, 0, 0);
  309. #endif
  310. #ifdef ${this._defineOppositeZ}
  311. ${this._reflectionVectorName}.z *= -1.0;
  312. #endif\r\n`;
  313. if (!onlyReflectionVector) {
  314. code += `
  315. #ifdef ${this._define3DName}
  316. vec3 ${this._reflectionCoordsName} = ${this._reflectionVectorName};
  317. #else
  318. vec2 ${this._reflectionCoordsName} = ${this._reflectionVectorName}.xy;
  319. #ifdef ${this._defineProjectionName}
  320. ${this._reflectionCoordsName} /= ${this._reflectionVectorName}.z;
  321. #endif
  322. ${this._reflectionCoordsName}.y = 1.0 - ${this._reflectionCoordsName}.y;
  323. #endif\r\n`;
  324. }
  325. return code;
  326. }
  327. /**
  328. * Generates the reflection color code for the fragment code path
  329. * @param lodVarName name of the lod variable
  330. * @param swizzleLookupTexture swizzle to use for the final color variable
  331. * @returns the shader code
  332. */
  333. public handleFragmentSideCodeReflectionColor(lodVarName?: string, swizzleLookupTexture = ".rgb"): string {
  334. const colorType = "vec" + (swizzleLookupTexture.length === 0 ? "4" : (swizzleLookupTexture.length - 1));
  335. let code = `${colorType} ${this._reflectionColorName};
  336. #ifdef ${this._define3DName}\r\n`;
  337. if (lodVarName) {
  338. code += `${this._reflectionColorName} = textureCubeLodEXT(${this._cubeSamplerName}, ${this._reflectionVectorName}, ${lodVarName})${swizzleLookupTexture};\r\n`;
  339. } else {
  340. code += `${this._reflectionColorName} = textureCube(${this._cubeSamplerName}, ${this._reflectionVectorName})${swizzleLookupTexture};\r\n`;
  341. }
  342. code += `
  343. #else\r\n`;
  344. if (lodVarName) {
  345. code += `${this._reflectionColorName} = texture2DLodEXT(${this._2DSamplerName}, ${this._reflectionCoordsName}, ${lodVarName})${swizzleLookupTexture};\r\n`;
  346. } else {
  347. code += `${this._reflectionColorName} = texture2D(${this._2DSamplerName}, ${this._reflectionCoordsName})${swizzleLookupTexture};\r\n`;
  348. }
  349. code += `#endif\r\n`;
  350. return code;
  351. }
  352. /**
  353. * Generates the code corresponding to the connected output points
  354. * @param state node material build state
  355. * @param varName name of the variable to output
  356. * @returns the shader code
  357. */
  358. public writeOutputs(state: NodeMaterialBuildState, varName: string): string {
  359. let code = "";
  360. if (state.target === NodeMaterialBlockTargets.Fragment) {
  361. for (var output of this._outputs) {
  362. if (output.hasEndpoints) {
  363. code += `${this._declareOutput(output, state)} = ${varName}.${output.name};\r\n`;
  364. }
  365. }
  366. }
  367. return code;
  368. }
  369. protected _buildBlock(state: NodeMaterialBuildState) {
  370. super._buildBlock(state);
  371. return this;
  372. }
  373. protected _dumpPropertiesCode() {
  374. if (!this.texture) {
  375. return "";
  376. }
  377. let codeString: string;
  378. if (this.texture.isCube) {
  379. const forcedExtension = (this.texture as CubeTexture).forcedExtension;
  380. codeString = `${this._codeVariableName}.texture = new BABYLON.CubeTexture("${this.texture.name}", undefined, undefined, ${this.texture.noMipmap}, null, undefined, undefined, undefined, ${this.texture._prefiltered}, ${forcedExtension ? "\"" + forcedExtension + "\"" : "null"});\r\n`;
  381. } else {
  382. codeString = `${this._codeVariableName}.texture = new BABYLON.Texture("${this.texture.name}", null);\r\n`;
  383. }
  384. codeString += `${this._codeVariableName}.texture.coordinatesMode = ${this.texture.coordinatesMode};\r\n`;
  385. return codeString;
  386. }
  387. public serialize(): any {
  388. let serializationObject = super.serialize();
  389. if (this.texture && !this.texture.isRenderTarget) {
  390. serializationObject.texture = this.texture.serialize();
  391. }
  392. return serializationObject;
  393. }
  394. public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
  395. super._deserialize(serializationObject, scene, rootUrl);
  396. if (serializationObject.texture) {
  397. rootUrl = serializationObject.texture.url.indexOf("data:") === 0 ? "" : rootUrl;
  398. if (serializationObject.texture.isCube) {
  399. this.texture = CubeTexture.Parse(serializationObject.texture, scene, rootUrl);
  400. } else {
  401. this.texture = Texture.Parse(serializationObject.texture, scene, rootUrl);
  402. }
  403. }
  404. }
  405. }
  406. _TypeStore.RegisteredTypes["BABYLON.ReflectionTextureBaseBlock"] = ReflectionTextureBaseBlock;