GTAOShader.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import {
  2. DataTexture,
  3. Matrix4,
  4. RepeatWrapping,
  5. Vector2,
  6. Vector3,
  7. } from 'three';
  8. /**
  9. * @module GTAOShader
  10. * @three_import import { GTAOShader } from 'three/addons/shaders/GTAOShader.js';
  11. */
  12. /**
  13. * GTAO shader. Use by {@link GTAOPass}.
  14. *
  15. * References:
  16. * - [Practical Realtime Strategies for Accurate Indirect Occlusion]{@link https://iryoku.com/downloads/Practical-Realtime-Strategies-for-Accurate-Indirect-Occlusion.pdf}.
  17. * - [Horizon-Based Indirect Lighting (HBIL)]{@link https://github.com/Patapom/GodComplex/blob/master/Tests/TestHBIL/2018%20Mayaux%20-%20Horizon-Based%20Indirect%20Lighting%20(HBIL).pdf}
  18. *
  19. * @constant
  20. * @type {ShaderMaterial~Shader}
  21. */
  22. const GTAOShader = {
  23. name: 'GTAOShader',
  24. defines: {
  25. PERSPECTIVE_CAMERA: 1,
  26. SAMPLES: 16,
  27. NORMAL_VECTOR_TYPE: 1,
  28. DEPTH_SWIZZLING: 'x',
  29. SCREEN_SPACE_RADIUS: 0,
  30. SCREEN_SPACE_RADIUS_SCALE: 100.0,
  31. SCENE_CLIP_BOX: 0,
  32. },
  33. uniforms: {
  34. tNormal: { value: null },
  35. tDepth: { value: null },
  36. tNoise: { value: null },
  37. resolution: { value: new Vector2() },
  38. cameraNear: { value: null },
  39. cameraFar: { value: null },
  40. cameraProjectionMatrix: { value: new Matrix4() },
  41. cameraProjectionMatrixInverse: { value: new Matrix4() },
  42. cameraWorldMatrix: { value: new Matrix4() },
  43. radius: { value: 0.25 },
  44. distanceExponent: { value: 1. },
  45. thickness: { value: 1. },
  46. distanceFallOff: { value: 1. },
  47. scale: { value: 1. },
  48. sceneBoxMin: { value: new Vector3( - 1, - 1, - 1 ) },
  49. sceneBoxMax: { value: new Vector3( 1, 1, 1 ) },
  50. },
  51. vertexShader: /* glsl */`
  52. varying vec2 vUv;
  53. void main() {
  54. vUv = uv;
  55. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  56. }`,
  57. fragmentShader: /* glsl */`
  58. varying vec2 vUv;
  59. uniform highp sampler2D tNormal;
  60. uniform highp sampler2D tDepth;
  61. uniform sampler2D tNoise;
  62. uniform vec2 resolution;
  63. uniform float cameraNear;
  64. uniform float cameraFar;
  65. uniform mat4 cameraProjectionMatrix;
  66. uniform mat4 cameraProjectionMatrixInverse;
  67. uniform mat4 cameraWorldMatrix;
  68. uniform float radius;
  69. uniform float distanceExponent;
  70. uniform float thickness;
  71. uniform float distanceFallOff;
  72. uniform float scale;
  73. #if SCENE_CLIP_BOX == 1
  74. uniform vec3 sceneBoxMin;
  75. uniform vec3 sceneBoxMax;
  76. #endif
  77. #include <common>
  78. #include <packing>
  79. #ifndef FRAGMENT_OUTPUT
  80. #define FRAGMENT_OUTPUT vec4(vec3(ao), 1.)
  81. #endif
  82. vec3 getViewPosition(const in vec2 screenPosition, const in float depth) {
  83. vec4 clipSpacePosition = vec4(vec3(screenPosition, depth) * 2.0 - 1.0, 1.0);
  84. vec4 viewSpacePosition = cameraProjectionMatrixInverse * clipSpacePosition;
  85. return viewSpacePosition.xyz / viewSpacePosition.w;
  86. }
  87. float getDepth(const vec2 uv) {
  88. return textureLod(tDepth, uv.xy, 0.0).DEPTH_SWIZZLING;
  89. }
  90. float fetchDepth(const ivec2 uv) {
  91. return texelFetch(tDepth, uv.xy, 0).DEPTH_SWIZZLING;
  92. }
  93. float getViewZ(const in float depth) {
  94. #if PERSPECTIVE_CAMERA == 1
  95. return perspectiveDepthToViewZ(depth, cameraNear, cameraFar);
  96. #else
  97. return orthographicDepthToViewZ(depth, cameraNear, cameraFar);
  98. #endif
  99. }
  100. vec3 computeNormalFromDepth(const vec2 uv) {
  101. vec2 size = vec2(textureSize(tDepth, 0));
  102. ivec2 p = ivec2(uv * size);
  103. float c0 = fetchDepth(p);
  104. float l2 = fetchDepth(p - ivec2(2, 0));
  105. float l1 = fetchDepth(p - ivec2(1, 0));
  106. float r1 = fetchDepth(p + ivec2(1, 0));
  107. float r2 = fetchDepth(p + ivec2(2, 0));
  108. float b2 = fetchDepth(p - ivec2(0, 2));
  109. float b1 = fetchDepth(p - ivec2(0, 1));
  110. float t1 = fetchDepth(p + ivec2(0, 1));
  111. float t2 = fetchDepth(p + ivec2(0, 2));
  112. float dl = abs((2.0 * l1 - l2) - c0);
  113. float dr = abs((2.0 * r1 - r2) - c0);
  114. float db = abs((2.0 * b1 - b2) - c0);
  115. float dt = abs((2.0 * t1 - t2) - c0);
  116. vec3 ce = getViewPosition(uv, c0).xyz;
  117. vec3 dpdx = (dl < dr) ? ce - getViewPosition((uv - vec2(1.0 / size.x, 0.0)), l1).xyz : -ce + getViewPosition((uv + vec2(1.0 / size.x, 0.0)), r1).xyz;
  118. vec3 dpdy = (db < dt) ? ce - getViewPosition((uv - vec2(0.0, 1.0 / size.y)), b1).xyz : -ce + getViewPosition((uv + vec2(0.0, 1.0 / size.y)), t1).xyz;
  119. return normalize(cross(dpdx, dpdy));
  120. }
  121. vec3 getViewNormal(const vec2 uv) {
  122. #if NORMAL_VECTOR_TYPE == 2
  123. return normalize(textureLod(tNormal, uv, 0.).rgb);
  124. #elif NORMAL_VECTOR_TYPE == 1
  125. return unpackRGBToNormal(textureLod(tNormal, uv, 0.).rgb);
  126. #else
  127. return computeNormalFromDepth(uv);
  128. #endif
  129. }
  130. vec3 getSceneUvAndDepth(vec3 sampleViewPos) {
  131. vec4 sampleClipPos = cameraProjectionMatrix * vec4(sampleViewPos, 1.);
  132. vec2 sampleUv = sampleClipPos.xy / sampleClipPos.w * 0.5 + 0.5;
  133. float sampleSceneDepth = getDepth(sampleUv);
  134. return vec3(sampleUv, sampleSceneDepth);
  135. }
  136. void main() {
  137. float depth = getDepth(vUv.xy);
  138. if (depth >= 1.0) {
  139. discard;
  140. return;
  141. }
  142. vec3 viewPos = getViewPosition(vUv, depth);
  143. vec3 viewNormal = getViewNormal(vUv);
  144. float radiusToUse = radius;
  145. float distanceFalloffToUse = thickness;
  146. #if SCREEN_SPACE_RADIUS == 1
  147. float radiusScale = getViewPosition(vec2(0.5 + float(SCREEN_SPACE_RADIUS_SCALE) / resolution.x, 0.0), depth).x;
  148. radiusToUse *= radiusScale;
  149. distanceFalloffToUse *= radiusScale;
  150. #endif
  151. #if SCENE_CLIP_BOX == 1
  152. vec3 worldPos = (cameraWorldMatrix * vec4(viewPos, 1.0)).xyz;
  153. float boxDistance = length(max(vec3(0.0), max(sceneBoxMin - worldPos, worldPos - sceneBoxMax)));
  154. if (boxDistance > radiusToUse) {
  155. discard;
  156. return;
  157. }
  158. #endif
  159. vec2 noiseResolution = vec2(textureSize(tNoise, 0));
  160. vec2 noiseUv = vUv * resolution / noiseResolution;
  161. vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0);
  162. vec3 randomVec = noiseTexel.xyz * 2.0 - 1.0;
  163. vec3 tangent = normalize(vec3(randomVec.xy, 0.));
  164. vec3 bitangent = vec3(-tangent.y, tangent.x, 0.);
  165. mat3 kernelMatrix = mat3(tangent, bitangent, vec3(0., 0., 1.));
  166. const int DIRECTIONS = SAMPLES < 30 ? 3 : 5;
  167. const int STEPS = (SAMPLES + DIRECTIONS - 1) / DIRECTIONS;
  168. float ao = 0.0;
  169. for (int i = 0; i < DIRECTIONS; ++i) {
  170. float angle = float(i) / float(DIRECTIONS) * PI;
  171. vec4 sampleDir = vec4(cos(angle), sin(angle), 0., 0.5 + 0.5 * noiseTexel.w);
  172. sampleDir.xyz = normalize(kernelMatrix * sampleDir.xyz);
  173. vec3 viewDir = normalize(-viewPos.xyz);
  174. vec3 sliceBitangent = normalize(cross(sampleDir.xyz, viewDir));
  175. vec3 sliceTangent = cross(sliceBitangent, viewDir);
  176. vec3 normalInSlice = normalize(viewNormal - sliceBitangent * dot(viewNormal, sliceBitangent));
  177. vec3 tangentToNormalInSlice = cross(normalInSlice, sliceBitangent);
  178. vec2 cosHorizons = vec2(dot(viewDir, tangentToNormalInSlice), dot(viewDir, -tangentToNormalInSlice));
  179. for (int j = 0; j < STEPS; ++j) {
  180. vec3 sampleViewOffset = sampleDir.xyz * radiusToUse * sampleDir.w * pow(float(j + 1) / float(STEPS), distanceExponent);
  181. vec3 sampleSceneUvDepth = getSceneUvAndDepth(viewPos + sampleViewOffset);
  182. vec3 sampleSceneViewPos = getViewPosition(sampleSceneUvDepth.xy, sampleSceneUvDepth.z);
  183. vec3 viewDelta = sampleSceneViewPos - viewPos;
  184. if (abs(viewDelta.z) < thickness) {
  185. float sampleCosHorizon = dot(viewDir, normalize(viewDelta));
  186. cosHorizons.x += max(0., (sampleCosHorizon - cosHorizons.x) * mix(1., 2. / float(j + 2), distanceFallOff));
  187. }
  188. sampleSceneUvDepth = getSceneUvAndDepth(viewPos - sampleViewOffset);
  189. sampleSceneViewPos = getViewPosition(sampleSceneUvDepth.xy, sampleSceneUvDepth.z);
  190. viewDelta = sampleSceneViewPos - viewPos;
  191. if (abs(viewDelta.z) < thickness) {
  192. float sampleCosHorizon = dot(viewDir, normalize(viewDelta));
  193. cosHorizons.y += max(0., (sampleCosHorizon - cosHorizons.y) * mix(1., 2. / float(j + 2), distanceFallOff));
  194. }
  195. }
  196. vec2 sinHorizons = sqrt(1. - cosHorizons * cosHorizons);
  197. float nx = dot(normalInSlice, sliceTangent);
  198. float ny = dot(normalInSlice, viewDir);
  199. float nxb = 1. / 2. * (acos(cosHorizons.y) - acos(cosHorizons.x) + sinHorizons.x * cosHorizons.x - sinHorizons.y * cosHorizons.y);
  200. float nyb = 1. / 2. * (2. - cosHorizons.x * cosHorizons.x - cosHorizons.y * cosHorizons.y);
  201. float occlusion = nx * nxb + ny * nyb;
  202. ao += occlusion;
  203. }
  204. ao = clamp(ao / float(DIRECTIONS), 0., 1.);
  205. #if SCENE_CLIP_BOX == 1
  206. ao = mix(ao, 1., smoothstep(0., radiusToUse, boxDistance));
  207. #endif
  208. ao = pow(ao, scale);
  209. gl_FragColor = FRAGMENT_OUTPUT;
  210. }`
  211. };
  212. /**
  213. * GTAO depth shader. Use by {@link GTAOPass}.
  214. *
  215. * @constant
  216. * @type {Object}
  217. */
  218. const GTAODepthShader = {
  219. name: 'GTAODepthShader',
  220. defines: {
  221. PERSPECTIVE_CAMERA: 1
  222. },
  223. uniforms: {
  224. tDepth: { value: null },
  225. cameraNear: { value: null },
  226. cameraFar: { value: null },
  227. },
  228. vertexShader: /* glsl */`
  229. varying vec2 vUv;
  230. void main() {
  231. vUv = uv;
  232. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  233. }`,
  234. fragmentShader: /* glsl */`
  235. uniform sampler2D tDepth;
  236. uniform float cameraNear;
  237. uniform float cameraFar;
  238. varying vec2 vUv;
  239. #include <packing>
  240. float getLinearDepth( const in vec2 screenPosition ) {
  241. #if PERSPECTIVE_CAMERA == 1
  242. float fragCoordZ = texture2D( tDepth, screenPosition ).x;
  243. float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
  244. return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
  245. #else
  246. return texture2D( tDepth, screenPosition ).x;
  247. #endif
  248. }
  249. void main() {
  250. float depth = getLinearDepth( vUv );
  251. gl_FragColor = vec4( vec3( 1.0 - depth ), 1.0 );
  252. }`
  253. };
  254. /**
  255. * GTAO blend shader. Use by {@link GTAOPass}.
  256. *
  257. * @constant
  258. * @type {Object}
  259. */
  260. const GTAOBlendShader = {
  261. name: 'GTAOBlendShader',
  262. uniforms: {
  263. tDiffuse: { value: null },
  264. intensity: { value: 1.0 }
  265. },
  266. vertexShader: /* glsl */`
  267. varying vec2 vUv;
  268. void main() {
  269. vUv = uv;
  270. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  271. }`,
  272. fragmentShader: /* glsl */`
  273. uniform float intensity;
  274. uniform sampler2D tDiffuse;
  275. varying vec2 vUv;
  276. void main() {
  277. vec4 texel = texture2D( tDiffuse, vUv );
  278. gl_FragColor = vec4(mix(vec3(1.), texel.rgb, intensity), texel.a);
  279. }`
  280. };
  281. function generateMagicSquareNoise( size = 5 ) {
  282. const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size );
  283. const magicSquare = generateMagicSquare( noiseSize );
  284. const noiseSquareSize = magicSquare.length;
  285. const data = new Uint8Array( noiseSquareSize * 4 );
  286. for ( let inx = 0; inx < noiseSquareSize; ++ inx ) {
  287. const iAng = magicSquare[ inx ];
  288. const angle = ( 2 * Math.PI * iAng ) / noiseSquareSize;
  289. const randomVec = new Vector3(
  290. Math.cos( angle ),
  291. Math.sin( angle ),
  292. 0
  293. ).normalize();
  294. data[ inx * 4 ] = ( randomVec.x * 0.5 + 0.5 ) * 255;
  295. data[ inx * 4 + 1 ] = ( randomVec.y * 0.5 + 0.5 ) * 255;
  296. data[ inx * 4 + 2 ] = 127;
  297. data[ inx * 4 + 3 ] = 255;
  298. }
  299. const noiseTexture = new DataTexture( data, noiseSize, noiseSize );
  300. noiseTexture.wrapS = RepeatWrapping;
  301. noiseTexture.wrapT = RepeatWrapping;
  302. noiseTexture.needsUpdate = true;
  303. return noiseTexture;
  304. }
  305. function generateMagicSquare( size ) {
  306. const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size );
  307. const noiseSquareSize = noiseSize * noiseSize;
  308. const magicSquare = Array( noiseSquareSize ).fill( 0 );
  309. let i = Math.floor( noiseSize / 2 );
  310. let j = noiseSize - 1;
  311. for ( let num = 1; num <= noiseSquareSize; ) {
  312. if ( i === - 1 && j === noiseSize ) {
  313. j = noiseSize - 2;
  314. i = 0;
  315. } else {
  316. if ( j === noiseSize ) {
  317. j = 0;
  318. }
  319. if ( i < 0 ) {
  320. i = noiseSize - 1;
  321. }
  322. }
  323. if ( magicSquare[ i * noiseSize + j ] !== 0 ) {
  324. j -= 2;
  325. i ++;
  326. continue;
  327. } else {
  328. magicSquare[ i * noiseSize + j ] = num ++;
  329. }
  330. j ++;
  331. i --;
  332. }
  333. return magicSquare;
  334. }
  335. export { generateMagicSquareNoise, GTAOShader, GTAODepthShader, GTAOBlendShader };