Home Reference Source

src/effects/GodRaysEffect.js

  1. import {
  2. BasicDepthPacking,
  3. Color,
  4. DepthTexture,
  5. Matrix4,
  6. Scene,
  7. SRGBColorSpace,
  8. Uniform,
  9. Vector2,
  10. Vector3,
  11. WebGLRenderTarget
  12. } from "three";
  13.  
  14. import { Resolution } from "../core/Resolution.js";
  15. import { BlendFunction } from "../enums/BlendFunction.js";
  16. import { EffectAttribute } from "../enums/EffectAttribute.js";
  17. import { KernelSize } from "../enums/KernelSize.js";
  18. import { DepthMaskMaterial } from "../materials/DepthMaskMaterial.js";
  19. import { GodRaysMaterial } from "../materials/GodRaysMaterial.js";
  20. import { KawaseBlurPass } from "../passes/KawaseBlurPass.js";
  21. import { ClearPass } from "../passes/ClearPass.js";
  22. import { RenderPass } from "../passes/RenderPass.js";
  23. import { ShaderPass } from "../passes/ShaderPass.js";
  24. import { Effect } from "./Effect.js";
  25.  
  26. import fragmentShader from "./glsl/god-rays.frag";
  27.  
  28. const v = /* @__PURE__ */ new Vector3();
  29. const m = /* @__PURE__ */ new Matrix4();
  30.  
  31. /**
  32. * A god rays effect.
  33. */
  34.  
  35. export class GodRaysEffect extends Effect {
  36.  
  37. /**
  38. * Constructs a new god rays effect.
  39. *
  40. * @param {Camera} [camera] - The main camera.
  41. * @param {Mesh|Points} [lightSource] - The light source. Must not write depth and has to be flagged as transparent.
  42. * @param {Object} [options] - The options.
  43. * @param {BlendFunction} [options.blendFunction=BlendFunction.SCREEN] - The blend function of this effect.
  44. * @param {Number} [options.samples=60.0] - The number of samples per pixel.
  45. * @param {Number} [options.density=0.96] - The density of the light rays.
  46. * @param {Number} [options.decay=0.9] - An illumination decay factor.
  47. * @param {Number} [options.weight=0.4] - A light ray weight factor.
  48. * @param {Number} [options.exposure=0.6] - A constant attenuation coefficient.
  49. * @param {Number} [options.clampMax=1.0] - An upper bound for the saturation of the overall effect.
  50. * @param {Number} [options.resolutionScale=0.5] - The resolution scale.
  51. * @param {Number} [options.resolutionX=Resolution.AUTO_SIZE] - The horizontal resolution.
  52. * @param {Number} [options.resolutionY=Resolution.AUTO_SIZE] - The vertical resolution.
  53. * @param {Number} [options.width=Resolution.AUTO_SIZE] - Deprecated. Use resolutionX instead.
  54. * @param {Number} [options.height=Resolution.AUTO_SIZE] - Deprecated. Use resolutionY instead.
  55. * @param {KernelSize} [options.kernelSize=KernelSize.SMALL] - The blur kernel size. Has no effect if blur is disabled.
  56. * @param {Boolean} [options.blur=true] - Whether the god rays should be blurred to reduce artifacts.
  57. */
  58.  
  59. constructor(camera, lightSource, {
  60. blendFunction = BlendFunction.SCREEN,
  61. samples = 60.0,
  62. density = 0.96,
  63. decay = 0.9,
  64. weight = 0.4,
  65. exposure = 0.6,
  66. clampMax = 1.0,
  67. blur = true,
  68. kernelSize = KernelSize.SMALL,
  69. resolutionScale = 0.5,
  70. width = Resolution.AUTO_SIZE,
  71. height = Resolution.AUTO_SIZE,
  72. resolutionX = width,
  73. resolutionY = height
  74. } = {}) {
  75.  
  76. super("GodRaysEffect", fragmentShader, {
  77. blendFunction,
  78. attributes: EffectAttribute.DEPTH,
  79. uniforms: new Map([
  80. ["map", new Uniform(null)]
  81. ])
  82. });
  83.  
  84. /**
  85. * The main camera.
  86. *
  87. * @type {Camera}
  88. * @private
  89. */
  90.  
  91. this.camera = camera;
  92.  
  93. /**
  94. * The light source.
  95. *
  96. * @type {Mesh|Points}
  97. * @private
  98. */
  99.  
  100. this._lightSource = lightSource;
  101. this.lightSource = lightSource;
  102.  
  103. /**
  104. * A scene for the light source.
  105. *
  106. * @type {Scene}
  107. * @private
  108. */
  109.  
  110. this.lightScene = new Scene();
  111.  
  112. /**
  113. * The light position in screen space.
  114. *
  115. * @type {Vector2}
  116. * @private
  117. */
  118.  
  119. this.screenPosition = new Vector2();
  120.  
  121. /**
  122. * A render target.
  123. *
  124. * @type {WebGLRenderTarget}
  125. * @private
  126. */
  127.  
  128. this.renderTargetA = new WebGLRenderTarget(1, 1, { depthBuffer: false });
  129. this.renderTargetA.texture.name = "GodRays.Target.A";
  130.  
  131. /**
  132. * A render target.
  133. *
  134. * @type {WebGLRenderTarget}
  135. * @private
  136. */
  137.  
  138. this.renderTargetB = this.renderTargetA.clone();
  139. this.renderTargetB.texture.name = "GodRays.Target.B";
  140. this.uniforms.get("map").value = this.renderTargetB.texture;
  141.  
  142. /**
  143. * A render target for the light scene.
  144. *
  145. * @type {WebGLRenderTarget}
  146. * @private
  147. */
  148.  
  149. this.renderTargetLight = new WebGLRenderTarget(1, 1);
  150. this.renderTargetLight.texture.name = "GodRays.Light";
  151. this.renderTargetLight.depthTexture = new DepthTexture();
  152.  
  153. /**
  154. * A pass that renders the light source.
  155. *
  156. * @type {RenderPass}
  157. * @private
  158. */
  159.  
  160. this.renderPassLight = new RenderPass(this.lightScene, camera);
  161. this.renderPassLight.clearPass.overrideClearColor = new Color(0x000000);
  162.  
  163. /**
  164. * A clear pass.
  165. *
  166. * @type {ClearPass}
  167. * @private
  168. */
  169.  
  170. this.clearPass = new ClearPass(true, false, false);
  171. this.clearPass.overrideClearColor = new Color(0x000000);
  172.  
  173. /**
  174. * A blur pass that reduces aliasing artifacts to make the light softer.
  175. *
  176. * This pass can be disabled to improve performance.
  177. *
  178. * @type {KawaseBlurPass}
  179. * @readonly
  180. */
  181.  
  182. this.blurPass = new KawaseBlurPass({ kernelSize });
  183. this.blurPass.enabled = blur;
  184.  
  185. /**
  186. * A depth mask pass.
  187. *
  188. * @type {ShaderPass}
  189. * @private
  190. */
  191.  
  192. this.depthMaskPass = new ShaderPass(new DepthMaskMaterial());
  193. const depthMaskMaterial = this.depthMaskMaterial;
  194. depthMaskMaterial.depthBuffer1 = this.renderTargetLight.depthTexture;
  195. depthMaskMaterial.copyCameraSettings(camera);
  196.  
  197. /**
  198. * A god rays blur pass.
  199. *
  200. * @type {ShaderPass}
  201. * @private
  202. */
  203.  
  204. this.godRaysPass = new ShaderPass(new GodRaysMaterial(this.screenPosition));
  205. const godRaysMaterial = this.godRaysMaterial;
  206. godRaysMaterial.density = density;
  207. godRaysMaterial.decay = decay;
  208. godRaysMaterial.weight = weight;
  209. godRaysMaterial.exposure = exposure;
  210. godRaysMaterial.maxIntensity = clampMax;
  211. godRaysMaterial.samples = samples;
  212.  
  213. /**
  214. * The render resolution.
  215. *
  216. * @type {Resolution}
  217. * @readonly
  218. */
  219.  
  220. const resolution = this.resolution = new Resolution(this, resolutionX, resolutionY, resolutionScale);
  221. resolution.addEventListener("change", (e) => this.setSize(resolution.baseWidth, resolution.baseHeight));
  222.  
  223. }
  224.  
  225. set mainCamera(value) {
  226.  
  227. this.camera = value;
  228. this.renderPassLight.mainCamera = value;
  229. this.depthMaskMaterial.copyCameraSettings(value);
  230.  
  231. }
  232.  
  233. /**
  234. * Sets the light source.
  235. *
  236. * @type {Mesh|Points}
  237. */
  238.  
  239. get lightSource() {
  240.  
  241. return this._lightSource;
  242.  
  243. }
  244.  
  245. set lightSource(value) {
  246.  
  247. this._lightSource = value;
  248.  
  249. if(value !== null) {
  250.  
  251. value.material.depthWrite = false;
  252. value.material.transparent = true;
  253.  
  254. }
  255.  
  256. }
  257.  
  258. /**
  259. * Returns the blur pass that reduces aliasing artifacts and makes the light softer.
  260. *
  261. * @deprecated Use blurPass instead.
  262. * @return {KawaseBlurPass} The blur pass.
  263. */
  264.  
  265. getBlurPass() {
  266.  
  267. return this.blurPass;
  268.  
  269. }
  270.  
  271. /**
  272. * A texture that contains the intermediate result of this effect.
  273. *
  274. * @type {Texture}
  275. */
  276.  
  277. get texture() {
  278.  
  279. return this.renderTargetB.texture;
  280.  
  281. }
  282.  
  283. /**
  284. * Returns the god rays texture.
  285. *
  286. * @deprecated Use texture instead.
  287. * @return {Texture} The texture.
  288. */
  289.  
  290. getTexture() {
  291.  
  292. return this.texture;
  293.  
  294. }
  295.  
  296. /**
  297. * The depth mask material.
  298. *
  299. * @type {DepthMaskMaterial}
  300. * @private
  301. */
  302.  
  303. get depthMaskMaterial() {
  304.  
  305. return this.depthMaskPass.fullscreenMaterial;
  306.  
  307. }
  308.  
  309. /**
  310. * The internal god rays material.
  311. *
  312. * @type {GodRaysMaterial}
  313. */
  314.  
  315. get godRaysMaterial() {
  316.  
  317. return this.godRaysPass.fullscreenMaterial;
  318.  
  319. }
  320.  
  321. /**
  322. * Returns the god rays material.
  323. *
  324. * @deprecated Use godRaysMaterial instead.
  325. * @return {GodRaysMaterial} The material.
  326. */
  327.  
  328. getGodRaysMaterial() {
  329.  
  330. return this.godRaysMaterial;
  331.  
  332. }
  333.  
  334. /**
  335. * Returns the resolution of this effect.
  336. *
  337. * @deprecated Use resolution instead.
  338. * @return {GodRaysMaterial} The material.
  339. */
  340.  
  341. getResolution() {
  342.  
  343. return this.resolution;
  344.  
  345. }
  346.  
  347. /**
  348. * The current width of the internal render targets.
  349. *
  350. * @type {Number}
  351. * @deprecated Use resolution.width instead.
  352. */
  353.  
  354. get width() {
  355.  
  356. return this.resolution.width;
  357.  
  358. }
  359.  
  360. set width(value) {
  361.  
  362. this.resolution.preferredWidth = value;
  363.  
  364. }
  365.  
  366. /**
  367. * The current height of the internal render targets.
  368. *
  369. * @type {Number}
  370. * @deprecated Use resolution.height instead.
  371. */
  372.  
  373. get height() {
  374.  
  375. return this.resolution.height;
  376.  
  377. }
  378.  
  379. set height(value) {
  380.  
  381. this.resolution.preferredHeight = value;
  382.  
  383. }
  384.  
  385. /**
  386. * Indicates whether dithering is enabled.
  387. *
  388. * @type {Boolean}
  389. * @deprecated
  390. */
  391.  
  392. get dithering() {
  393.  
  394. return this.godRaysMaterial.dithering;
  395.  
  396. }
  397.  
  398. set dithering(value) {
  399.  
  400. const material = this.godRaysMaterial;
  401. material.dithering = value;
  402. material.needsUpdate = true;
  403.  
  404. }
  405.  
  406. /**
  407. * Indicates whether the god rays should be blurred to reduce artifacts.
  408. *
  409. * @type {Boolean}
  410. * @deprecated Use blurPass.enabled instead.
  411. */
  412.  
  413. get blur() {
  414.  
  415. return this.blurPass.enabled;
  416.  
  417. }
  418.  
  419. set blur(value) {
  420.  
  421. this.blurPass.enabled = value;
  422.  
  423. }
  424.  
  425. /**
  426. * The blur kernel size.
  427. *
  428. * @type {KernelSize}
  429. * @deprecated Use blurPass.kernelSize instead.
  430. */
  431.  
  432. get kernelSize() {
  433.  
  434. return this.blurPass.kernelSize;
  435.  
  436. }
  437.  
  438. set kernelSize(value) {
  439.  
  440. this.blurPass.kernelSize = value;
  441.  
  442. }
  443.  
  444. /**
  445. * Returns the current resolution scale.
  446. *
  447. * @return {Number} The resolution scale.
  448. * @deprecated Use resolution instead.
  449. */
  450.  
  451. getResolutionScale() {
  452.  
  453. return this.resolution.scale;
  454.  
  455. }
  456.  
  457. /**
  458. * Sets the resolution scale.
  459. *
  460. * @param {Number} scale - The new resolution scale.
  461. * @deprecated Use resolution instead.
  462. */
  463.  
  464. setResolutionScale(scale) {
  465.  
  466. this.resolution.scale = scale;
  467.  
  468. }
  469.  
  470. /**
  471. * The number of samples per pixel.
  472. *
  473. * @type {Number}
  474. * @deprecated Use godRaysMaterial.samples instead.
  475. */
  476.  
  477. get samples() {
  478.  
  479. return this.godRaysMaterial.samples;
  480.  
  481. }
  482.  
  483. /**
  484. * A higher sample count improves quality at the cost of performance.
  485. *
  486. * @type {Number}
  487. * @deprecated Use godRaysMaterial.samples instead.
  488. */
  489.  
  490. set samples(value) {
  491.  
  492. this.godRaysMaterial.samples = value;
  493.  
  494. }
  495.  
  496. /**
  497. * Sets the depth texture.
  498. *
  499. * @param {Texture} depthTexture - A depth texture.
  500. * @param {Number} [depthPacking=BasicDepthPacking] - The depth packing.
  501. */
  502.  
  503. setDepthTexture(depthTexture, depthPacking = BasicDepthPacking) {
  504.  
  505. this.depthMaskPass.fullscreenMaterial.depthBuffer0 = depthTexture;
  506. this.depthMaskPass.fullscreenMaterial.depthPacking0 = depthPacking;
  507.  
  508. }
  509.  
  510. /**
  511. * Updates this effect.
  512. *
  513. * @param {WebGLRenderer} renderer - The renderer.
  514. * @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass.
  515. * @param {Number} [deltaTime] - The time between the last frame and the current one in seconds.
  516. */
  517.  
  518. update(renderer, inputBuffer, deltaTime) {
  519.  
  520. const lightSource = this.lightSource;
  521. const parent = lightSource.parent;
  522. const matrixAutoUpdate = lightSource.matrixAutoUpdate;
  523.  
  524. const renderTargetA = this.renderTargetA;
  525. const renderTargetLight = this.renderTargetLight;
  526.  
  527. // Enable depth write for the light scene render pass.
  528. lightSource.material.depthWrite = true;
  529.  
  530. // Update the world matrix.
  531. lightSource.matrixAutoUpdate = false;
  532. lightSource.updateWorldMatrix(true, false);
  533.  
  534. if(parent !== null) {
  535.  
  536. if(!matrixAutoUpdate) {
  537.  
  538. // Remember the local transformation to restore it later.
  539. m.copy(lightSource.matrix);
  540.  
  541. }
  542.  
  543. // Apply parent transformations.
  544. lightSource.matrix.copy(lightSource.matrixWorld);
  545.  
  546. }
  547.  
  548. // Render the light source and mask it based on depth.
  549. this.lightScene.add(lightSource);
  550. this.renderPassLight.render(renderer, renderTargetLight);
  551. this.clearPass.render(renderer, renderTargetA);
  552. this.depthMaskPass.render(renderer, renderTargetLight, renderTargetA);
  553.  
  554. // Restore the original values.
  555. lightSource.material.depthWrite = false;
  556. lightSource.matrixAutoUpdate = matrixAutoUpdate;
  557.  
  558. if(parent !== null) {
  559.  
  560. if(!matrixAutoUpdate) {
  561.  
  562. lightSource.matrix.copy(m);
  563.  
  564. }
  565.  
  566. parent.add(lightSource);
  567.  
  568. }
  569.  
  570. // Calculate the screen light position.
  571. v.setFromMatrixPosition(lightSource.matrixWorld).project(this.camera);
  572.  
  573. // Translate to [0.0, 1.0] and clamp to screen with a bias of 1.0.
  574. this.screenPosition.set(
  575. Math.min(Math.max((v.x + 1.0) * 0.5, -1.0), 2.0),
  576. Math.min(Math.max((v.y + 1.0) * 0.5, -1.0), 2.0)
  577. );
  578.  
  579. if(this.blurPass.enabled) {
  580.  
  581. // Blur the masked scene to reduce artifacts.
  582. this.blurPass.render(renderer, renderTargetA, renderTargetA);
  583.  
  584. }
  585.  
  586. // Blur the masked scene along radial lines towards the light source.
  587. this.godRaysPass.render(renderer, renderTargetA, this.renderTargetB);
  588.  
  589. }
  590.  
  591. /**
  592. * Updates the size of internal render targets.
  593. *
  594. * @param {Number} width - The width.
  595. * @param {Number} height - The height.
  596. */
  597.  
  598. setSize(width, height) {
  599.  
  600. const resolution = this.resolution;
  601. resolution.setBaseSize(width, height);
  602. const w = resolution.width, h = resolution.height;
  603.  
  604. this.renderTargetA.setSize(w, h);
  605. this.renderTargetB.setSize(w, h);
  606. this.renderTargetLight.setSize(w, h);
  607. this.blurPass.resolution.copy(resolution);
  608.  
  609. }
  610.  
  611. /**
  612. * Performs initialization tasks.
  613. *
  614. * @param {WebGLRenderer} renderer - The renderer.
  615. * @param {Boolean} alpha - Whether the renderer uses the alpha channel or not.
  616. * @param {Number} frameBufferType - The type of the main frame buffers.
  617. */
  618.  
  619. initialize(renderer, alpha, frameBufferType) {
  620.  
  621. this.blurPass.initialize(renderer, alpha, frameBufferType);
  622. this.renderPassLight.initialize(renderer, alpha, frameBufferType);
  623. this.depthMaskPass.initialize(renderer, alpha, frameBufferType);
  624. this.godRaysPass.initialize(renderer, alpha, frameBufferType);
  625.  
  626. if(frameBufferType !== undefined) {
  627.  
  628. this.renderTargetA.texture.type = frameBufferType;
  629. this.renderTargetB.texture.type = frameBufferType;
  630. this.renderTargetLight.texture.type = frameBufferType;
  631.  
  632. if(renderer !== null && renderer.outputColorSpace === SRGBColorSpace) {
  633.  
  634. this.renderTargetA.texture.colorSpace = SRGBColorSpace;
  635. this.renderTargetB.texture.colorSpace = SRGBColorSpace;
  636. this.renderTargetLight.texture.colorSpace = SRGBColorSpace;
  637.  
  638. }
  639.  
  640. }
  641.  
  642. }
  643.  
  644. }