RenderPixelatedPass.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import {
  2. WebGLRenderTarget,
  3. MeshNormalMaterial,
  4. ShaderMaterial,
  5. Vector2,
  6. Vector4,
  7. DepthTexture,
  8. NearestFilter,
  9. HalfFloatType
  10. } from 'three';
  11. import { Pass, FullScreenQuad } from './Pass.js';
  12. /**
  13. * A special type of render pass that produces a pixelated beauty pass.
  14. *
  15. * ```js
  16. * const renderPixelatedPass = new RenderPixelatedPass( 6, scene, camera );
  17. * composer.addPass( renderPixelatedPass );
  18. * ```
  19. *
  20. * @augments Pass
  21. * @three_import import { RenderPixelatedPass } from 'three/addons/postprocessing/RenderPixelatedPass.js';
  22. */
  23. class RenderPixelatedPass extends Pass {
  24. /**
  25. * Constructs a new render pixelated pass.
  26. *
  27. * @param {number} pixelSize - The effect's pixel size.
  28. * @param {Scene} scene - The scene to render.
  29. * @param {Camera} camera - The camera.
  30. * @param {{normalEdgeStrength:number,depthEdgeStrength:number}} options - The pass options.
  31. */
  32. constructor( pixelSize, scene, camera, options = {} ) {
  33. super();
  34. /**
  35. * The effect's pixel size.
  36. *
  37. * @type {number}
  38. */
  39. this.pixelSize = pixelSize;
  40. /**
  41. * The scene to render.
  42. *
  43. * @type {Scene}
  44. */
  45. this.scene = scene;
  46. /**
  47. * The camera.
  48. *
  49. * @type {Camera}
  50. */
  51. this.camera = camera;
  52. /**
  53. * The normal edge strength.
  54. *
  55. * @type {number}
  56. * @default 0.3
  57. */
  58. this.normalEdgeStrength = options.normalEdgeStrength || 0.3;
  59. /**
  60. * The normal edge strength.
  61. *
  62. * @type {number}
  63. * @default 0.4
  64. */
  65. this.depthEdgeStrength = options.depthEdgeStrength || 0.4;
  66. /**
  67. * The pixelated material.
  68. *
  69. * @type {ShaderMaterial}
  70. */
  71. this.pixelatedMaterial = this._createPixelatedMaterial();
  72. // internals
  73. this._resolution = new Vector2();
  74. this._renderResolution = new Vector2();
  75. this._normalMaterial = new MeshNormalMaterial();
  76. this._beautyRenderTarget = new WebGLRenderTarget();
  77. this._beautyRenderTarget.texture.minFilter = NearestFilter;
  78. this._beautyRenderTarget.texture.magFilter = NearestFilter;
  79. this._beautyRenderTarget.texture.type = HalfFloatType;
  80. this._beautyRenderTarget.depthTexture = new DepthTexture();
  81. this._normalRenderTarget = new WebGLRenderTarget();
  82. this._normalRenderTarget.texture.minFilter = NearestFilter;
  83. this._normalRenderTarget.texture.magFilter = NearestFilter;
  84. this._normalRenderTarget.texture.type = HalfFloatType;
  85. this._fsQuad = new FullScreenQuad( this.pixelatedMaterial );
  86. }
  87. /**
  88. * Frees the GPU-related resources allocated by this instance. Call this
  89. * method whenever the pass is no longer used in your app.
  90. */
  91. dispose() {
  92. this._beautyRenderTarget.dispose();
  93. this._normalRenderTarget.dispose();
  94. this.pixelatedMaterial.dispose();
  95. this._normalMaterial.dispose();
  96. this._fsQuad.dispose();
  97. }
  98. /**
  99. * Sets the size of the pass.
  100. *
  101. * @param {number} width - The width to set.
  102. * @param {number} height - The width to set.
  103. */
  104. setSize( width, height ) {
  105. this._resolution.set( width, height );
  106. this._renderResolution.set( ( width / this.pixelSize ) | 0, ( height / this.pixelSize ) | 0 );
  107. const { x, y } = this._renderResolution;
  108. this._beautyRenderTarget.setSize( x, y );
  109. this._normalRenderTarget.setSize( x, y );
  110. this._fsQuad.material.uniforms.resolution.value.set( x, y, 1 / x, 1 / y );
  111. }
  112. /**
  113. * Sets the effect's pixel size.
  114. *
  115. * @param {number} pixelSize - The pixel size to set.
  116. */
  117. setPixelSize( pixelSize ) {
  118. this.pixelSize = pixelSize;
  119. this.setSize( this._resolution.x, this._resolution.y );
  120. }
  121. /**
  122. * Performs the pixelation pass.
  123. *
  124. * @param {WebGLRenderer} renderer - The renderer.
  125. * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
  126. * destination for the pass.
  127. * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
  128. * previous pass from this buffer.
  129. * @param {number} deltaTime - The delta time in seconds.
  130. * @param {boolean} maskActive - Whether masking is active or not.
  131. */
  132. render( renderer, writeBuffer/*, readBuffer , deltaTime, maskActive */ ) {
  133. const uniforms = this._fsQuad.material.uniforms;
  134. uniforms.normalEdgeStrength.value = this.normalEdgeStrength;
  135. uniforms.depthEdgeStrength.value = this.depthEdgeStrength;
  136. renderer.setRenderTarget( this._beautyRenderTarget );
  137. renderer.render( this.scene, this.camera );
  138. const overrideMaterial_old = this.scene.overrideMaterial;
  139. renderer.setRenderTarget( this._normalRenderTarget );
  140. this.scene.overrideMaterial = this._normalMaterial;
  141. renderer.render( this.scene, this.camera );
  142. this.scene.overrideMaterial = overrideMaterial_old;
  143. uniforms.tDiffuse.value = this._beautyRenderTarget.texture;
  144. uniforms.tDepth.value = this._beautyRenderTarget.depthTexture;
  145. uniforms.tNormal.value = this._normalRenderTarget.texture;
  146. if ( this.renderToScreen ) {
  147. renderer.setRenderTarget( null );
  148. } else {
  149. renderer.setRenderTarget( writeBuffer );
  150. if ( this.clear ) renderer.clear();
  151. }
  152. this._fsQuad.render( renderer );
  153. }
  154. // internals
  155. _createPixelatedMaterial() {
  156. return new ShaderMaterial( {
  157. uniforms: {
  158. tDiffuse: { value: null },
  159. tDepth: { value: null },
  160. tNormal: { value: null },
  161. resolution: { value: new Vector4() },
  162. normalEdgeStrength: { value: 0 },
  163. depthEdgeStrength: { value: 0 }
  164. },
  165. vertexShader: /* glsl */`
  166. varying vec2 vUv;
  167. void main() {
  168. vUv = uv;
  169. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  170. }
  171. `,
  172. fragmentShader: /* glsl */`
  173. uniform sampler2D tDiffuse;
  174. uniform sampler2D tDepth;
  175. uniform sampler2D tNormal;
  176. uniform vec4 resolution;
  177. uniform float normalEdgeStrength;
  178. uniform float depthEdgeStrength;
  179. varying vec2 vUv;
  180. float getDepth(int x, int y) {
  181. return texture2D( tDepth, vUv + vec2(x, y) * resolution.zw ).r;
  182. }
  183. vec3 getNormal(int x, int y) {
  184. return texture2D( tNormal, vUv + vec2(x, y) * resolution.zw ).rgb * 2.0 - 1.0;
  185. }
  186. float depthEdgeIndicator(float depth, vec3 normal) {
  187. float diff = 0.0;
  188. diff += clamp(getDepth(1, 0) - depth, 0.0, 1.0);
  189. diff += clamp(getDepth(-1, 0) - depth, 0.0, 1.0);
  190. diff += clamp(getDepth(0, 1) - depth, 0.0, 1.0);
  191. diff += clamp(getDepth(0, -1) - depth, 0.0, 1.0);
  192. return floor(smoothstep(0.01, 0.02, diff) * 2.) / 2.;
  193. }
  194. float neighborNormalEdgeIndicator(int x, int y, float depth, vec3 normal) {
  195. float depthDiff = getDepth(x, y) - depth;
  196. vec3 neighborNormal = getNormal(x, y);
  197. // Edge pixels should yield to faces who's normals are closer to the bias normal.
  198. vec3 normalEdgeBias = vec3(1., 1., 1.); // This should probably be a parameter.
  199. float normalDiff = dot(normal - neighborNormal, normalEdgeBias);
  200. float normalIndicator = clamp(smoothstep(-.01, .01, normalDiff), 0.0, 1.0);
  201. // Only the shallower pixel should detect the normal edge.
  202. float depthIndicator = clamp(sign(depthDiff * .25 + .0025), 0.0, 1.0);
  203. return (1.0 - dot(normal, neighborNormal)) * depthIndicator * normalIndicator;
  204. }
  205. float normalEdgeIndicator(float depth, vec3 normal) {
  206. float indicator = 0.0;
  207. indicator += neighborNormalEdgeIndicator(0, -1, depth, normal);
  208. indicator += neighborNormalEdgeIndicator(0, 1, depth, normal);
  209. indicator += neighborNormalEdgeIndicator(-1, 0, depth, normal);
  210. indicator += neighborNormalEdgeIndicator(1, 0, depth, normal);
  211. return step(0.1, indicator);
  212. }
  213. void main() {
  214. vec4 texel = texture2D( tDiffuse, vUv );
  215. float depth = 0.0;
  216. vec3 normal = vec3(0.0);
  217. if (depthEdgeStrength > 0.0 || normalEdgeStrength > 0.0) {
  218. depth = getDepth(0, 0);
  219. normal = getNormal(0, 0);
  220. }
  221. float dei = 0.0;
  222. if (depthEdgeStrength > 0.0)
  223. dei = depthEdgeIndicator(depth, normal);
  224. float nei = 0.0;
  225. if (normalEdgeStrength > 0.0)
  226. nei = normalEdgeIndicator(depth, normal);
  227. float Strength = dei > 0.0 ? (1.0 - depthEdgeStrength * dei) : (1.0 + normalEdgeStrength * nei);
  228. gl_FragColor = texel * Strength;
  229. }
  230. `
  231. } );
  232. }
  233. }
  234. export { RenderPixelatedPass };