postprocessing.ts 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051
  1. /**
  2. * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
  6. * @author Ludovic Autin <ludovic.autin@gmail.com>
  7. */
  8. import { CopyRenderable, createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
  9. import { TextureSpec, Values, UniformSpec, DefineSpec } from '../../mol-gl/renderable/schema';
  10. import { ShaderCode } from '../../mol-gl/shader-code';
  11. import { WebGLContext } from '../../mol-gl/webgl/context';
  12. import { Texture } from '../../mol-gl/webgl/texture';
  13. import { deepEqual, ValueCell } from '../../mol-util';
  14. import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
  15. import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
  16. import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
  17. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  18. import { RenderTarget } from '../../mol-gl/webgl/render-target';
  19. import { DrawPass } from './draw';
  20. import { ICamera } from '../../mol-canvas3d/camera';
  21. import { quad_vert } from '../../mol-gl/shader/quad.vert';
  22. import { outlines_frag } from '../../mol-gl/shader/outlines.frag';
  23. import { ssao_frag } from '../../mol-gl/shader/ssao.frag';
  24. import { ssaoBlur_frag } from '../../mol-gl/shader/ssao-blur.frag';
  25. import { postprocessing_frag } from '../../mol-gl/shader/postprocessing.frag';
  26. import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
  27. import { Color } from '../../mol-util/color';
  28. import { FxaaParams, FxaaPass } from './fxaa';
  29. import { SmaaParams, SmaaPass } from './smaa';
  30. import { isTimingMode } from '../../mol-util/debug';
  31. import { BackgroundParams, BackgroundPass } from './background';
  32. import { AssetManager } from '../../mol-util/assets';
  33. import { Light } from '../../mol-gl/renderer';
  34. import { shadows_frag } from '../../mol-gl/shader/shadows.frag';
  35. import { CasParams, CasPass } from './cas';
  36. const OutlinesSchema = {
  37. ...QuadSchema,
  38. tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  39. tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  40. uTexSize: UniformSpec('v2'),
  41. dOrthographic: DefineSpec('number'),
  42. uNear: UniformSpec('f'),
  43. uFar: UniformSpec('f'),
  44. uInvProjection: UniformSpec('m4'),
  45. uOutlineThreshold: UniformSpec('f'),
  46. dTransparentOutline: DefineSpec('boolean'),
  47. };
  48. type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
  49. function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture, transparentOutline: boolean): OutlinesRenderable {
  50. const width = depthTextureOpaque.getWidth();
  51. const height = depthTextureOpaque.getHeight();
  52. const values: Values<typeof OutlinesSchema> = {
  53. ...QuadValues,
  54. tDepthOpaque: ValueCell.create(depthTextureOpaque),
  55. tDepthTransparent: ValueCell.create(depthTextureTransparent),
  56. uTexSize: ValueCell.create(Vec2.create(width, height)),
  57. dOrthographic: ValueCell.create(0),
  58. uNear: ValueCell.create(1),
  59. uFar: ValueCell.create(10000),
  60. uInvProjection: ValueCell.create(Mat4.identity()),
  61. uOutlineThreshold: ValueCell.create(0.33),
  62. dTransparentOutline: ValueCell.create(transparentOutline),
  63. };
  64. const schema = { ...OutlinesSchema };
  65. const shaderCode = ShaderCode('outlines', quad_vert, outlines_frag);
  66. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  67. return createComputeRenderable(renderItem, values);
  68. }
  69. const ShadowsSchema = {
  70. ...QuadSchema,
  71. tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  72. uTexSize: UniformSpec('v2'),
  73. uProjection: UniformSpec('m4'),
  74. uInvProjection: UniformSpec('m4'),
  75. uBounds: UniformSpec('v4'),
  76. dOrthographic: DefineSpec('number'),
  77. uNear: UniformSpec('f'),
  78. uFar: UniformSpec('f'),
  79. dSteps: DefineSpec('number'),
  80. uMaxDistance: UniformSpec('f'),
  81. uTolerance: UniformSpec('f'),
  82. uBias: UniformSpec('f'),
  83. uLightDirection: UniformSpec('v3[]'),
  84. uLightColor: UniformSpec('v3[]'),
  85. dLightCount: DefineSpec('number'),
  86. };
  87. type ShadowsRenderable = ComputeRenderable<Values<typeof ShadowsSchema>>
  88. function getShadowsRenderable(ctx: WebGLContext, depthTexture: Texture): ShadowsRenderable {
  89. const width = depthTexture.getWidth();
  90. const height = depthTexture.getHeight();
  91. const values: Values<typeof ShadowsSchema> = {
  92. ...QuadValues,
  93. tDepth: ValueCell.create(depthTexture),
  94. uTexSize: ValueCell.create(Vec2.create(width, height)),
  95. uProjection: ValueCell.create(Mat4.identity()),
  96. uInvProjection: ValueCell.create(Mat4.identity()),
  97. uBounds: ValueCell.create(Vec4()),
  98. dOrthographic: ValueCell.create(0),
  99. uNear: ValueCell.create(1),
  100. uFar: ValueCell.create(10000),
  101. dSteps: ValueCell.create(1),
  102. uMaxDistance: ValueCell.create(3.0),
  103. uTolerance: ValueCell.create(1.0),
  104. uBias: ValueCell.create(0.6),
  105. uLightDirection: ValueCell.create([]),
  106. uLightColor: ValueCell.create([]),
  107. dLightCount: ValueCell.create(0),
  108. };
  109. const schema = { ...ShadowsSchema };
  110. const shaderCode = ShaderCode('shadows', quad_vert, shadows_frag);
  111. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  112. return createComputeRenderable(renderItem, values);
  113. }
  114. const SsaoSchema = {
  115. ...QuadSchema,
  116. tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  117. tDepthHalf: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  118. tDepthQuarter: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  119. uSamples: UniformSpec('v3[]'),
  120. dNSamples: DefineSpec('number'),
  121. uProjection: UniformSpec('m4'),
  122. uInvProjection: UniformSpec('m4'),
  123. uBounds: UniformSpec('v4'),
  124. uTexSize: UniformSpec('v2'),
  125. uRadius: UniformSpec('f'),
  126. uBias: UniformSpec('f'),
  127. dMultiScale: DefineSpec('boolean'),
  128. dLevels: DefineSpec('number'),
  129. uLevelRadius: UniformSpec('f[]'),
  130. uLevelBias: UniformSpec('f[]'),
  131. uNearThreshold: UniformSpec('f'),
  132. uFarThreshold: UniformSpec('f'),
  133. };
  134. type SsaoRenderable = ComputeRenderable<Values<typeof SsaoSchema>>
  135. function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture, depthHalfTexture: Texture, depthQuarterTexture: Texture): SsaoRenderable {
  136. const values: Values<typeof SsaoSchema> = {
  137. ...QuadValues,
  138. tDepth: ValueCell.create(depthTexture),
  139. tDepthHalf: ValueCell.create(depthHalfTexture),
  140. tDepthQuarter: ValueCell.create(depthQuarterTexture),
  141. uSamples: ValueCell.create(getSamples(32)),
  142. dNSamples: ValueCell.create(32),
  143. uProjection: ValueCell.create(Mat4.identity()),
  144. uInvProjection: ValueCell.create(Mat4.identity()),
  145. uBounds: ValueCell.create(Vec4()),
  146. uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
  147. uRadius: ValueCell.create(Math.pow(2, 5)),
  148. uBias: ValueCell.create(0.8),
  149. dMultiScale: ValueCell.create(false),
  150. dLevels: ValueCell.create(3),
  151. uLevelRadius: ValueCell.create([Math.pow(2, 2), Math.pow(2, 5), Math.pow(2, 8)]),
  152. uLevelBias: ValueCell.create([0.8, 0.8, 0.8]),
  153. uNearThreshold: ValueCell.create(10.0),
  154. uFarThreshold: ValueCell.create(1500.0),
  155. };
  156. const schema = { ...SsaoSchema };
  157. const shaderCode = ShaderCode('ssao', quad_vert, ssao_frag);
  158. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  159. return createComputeRenderable(renderItem, values);
  160. }
  161. const SsaoBlurSchema = {
  162. ...QuadSchema,
  163. tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  164. uTexSize: UniformSpec('v2'),
  165. uKernel: UniformSpec('f[]'),
  166. dOcclusionKernelSize: DefineSpec('number'),
  167. uBlurDirectionX: UniformSpec('f'),
  168. uBlurDirectionY: UniformSpec('f'),
  169. uInvProjection: UniformSpec('m4'),
  170. uNear: UniformSpec('f'),
  171. uFar: UniformSpec('f'),
  172. uBounds: UniformSpec('v4'),
  173. dOrthographic: DefineSpec('number'),
  174. };
  175. type SsaoBlurRenderable = ComputeRenderable<Values<typeof SsaoBlurSchema>>
  176. function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, direction: 'horizontal' | 'vertical'): SsaoBlurRenderable {
  177. const values: Values<typeof SsaoBlurSchema> = {
  178. ...QuadValues,
  179. tSsaoDepth: ValueCell.create(ssaoDepthTexture),
  180. uTexSize: ValueCell.create(Vec2.create(ssaoDepthTexture.getWidth(), ssaoDepthTexture.getHeight())),
  181. uKernel: ValueCell.create(getBlurKernel(15)),
  182. dOcclusionKernelSize: ValueCell.create(15),
  183. uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0),
  184. uBlurDirectionY: ValueCell.create(direction === 'vertical' ? 1 : 0),
  185. uInvProjection: ValueCell.create(Mat4.identity()),
  186. uNear: ValueCell.create(0.0),
  187. uFar: ValueCell.create(10000.0),
  188. uBounds: ValueCell.create(Vec4()),
  189. dOrthographic: ValueCell.create(0),
  190. };
  191. const schema = { ...SsaoBlurSchema };
  192. const shaderCode = ShaderCode('ssao_blur', quad_vert, ssaoBlur_frag);
  193. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  194. return createComputeRenderable(renderItem, values);
  195. }
  196. function getBlurKernel(kernelSize: number): number[] {
  197. const sigma = kernelSize / 3.0;
  198. const halfKernelSize = Math.floor((kernelSize + 1) / 2);
  199. const kernel = [];
  200. for (let x = 0; x < halfKernelSize; x++) {
  201. kernel.push((1.0 / ((Math.sqrt(2 * Math.PI)) * sigma)) * Math.exp(-x * x / (2 * sigma * sigma)));
  202. }
  203. return kernel;
  204. }
  205. const RandomHemisphereVector: Vec3[] = [];
  206. for (let i = 0; i < 256; i++) {
  207. const v = Vec3();
  208. v[0] = Math.random() * 2.0 - 1.0;
  209. v[1] = Math.random() * 2.0 - 1.0;
  210. v[2] = Math.random();
  211. Vec3.normalize(v, v);
  212. Vec3.scale(v, v, Math.random());
  213. RandomHemisphereVector.push(v);
  214. }
  215. function getSamples(nSamples: number): number[] {
  216. const samples = [];
  217. for (let i = 0; i < nSamples; i++) {
  218. let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples);
  219. scale = 0.1 + scale * (1.0 - 0.1);
  220. samples.push(RandomHemisphereVector[i][0] * scale);
  221. samples.push(RandomHemisphereVector[i][1] * scale);
  222. samples.push(RandomHemisphereVector[i][2] * scale);
  223. }
  224. return samples;
  225. }
  226. const PostprocessingSchema = {
  227. ...QuadSchema,
  228. tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  229. tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  230. tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  231. tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  232. tShadows: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  233. tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  234. uTexSize: UniformSpec('v2'),
  235. dOrthographic: DefineSpec('number'),
  236. uNear: UniformSpec('f'),
  237. uFar: UniformSpec('f'),
  238. uFogNear: UniformSpec('f'),
  239. uFogFar: UniformSpec('f'),
  240. uFogColor: UniformSpec('v3'),
  241. uOutlineColor: UniformSpec('v3'),
  242. uOcclusionColor: UniformSpec('v3'),
  243. uTransparentBackground: UniformSpec('b'),
  244. dOcclusionEnable: DefineSpec('boolean'),
  245. uOcclusionOffset: UniformSpec('v2'),
  246. dShadowEnable: DefineSpec('boolean'),
  247. dOutlineEnable: DefineSpec('boolean'),
  248. dOutlineScale: DefineSpec('number'),
  249. dTransparentOutline: DefineSpec('boolean'),
  250. };
  251. type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
  252. function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, shadowsTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture, transparentOutline: boolean): PostprocessingRenderable {
  253. const values: Values<typeof PostprocessingSchema> = {
  254. ...QuadValues,
  255. tSsaoDepth: ValueCell.create(ssaoDepthTexture),
  256. tColor: ValueCell.create(colorTexture),
  257. tDepthOpaque: ValueCell.create(depthTextureOpaque),
  258. tDepthTransparent: ValueCell.create(depthTextureTransparent),
  259. tShadows: ValueCell.create(shadowsTexture),
  260. tOutlines: ValueCell.create(outlinesTexture),
  261. uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
  262. dOrthographic: ValueCell.create(0),
  263. uNear: ValueCell.create(1),
  264. uFar: ValueCell.create(10000),
  265. uFogNear: ValueCell.create(10000),
  266. uFogFar: ValueCell.create(10000),
  267. uFogColor: ValueCell.create(Vec3.create(1, 1, 1)),
  268. uOutlineColor: ValueCell.create(Vec3.create(0, 0, 0)),
  269. uOcclusionColor: ValueCell.create(Vec3.create(0, 0, 0)),
  270. uTransparentBackground: ValueCell.create(false),
  271. dOcclusionEnable: ValueCell.create(true),
  272. uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
  273. dShadowEnable: ValueCell.create(false),
  274. dOutlineEnable: ValueCell.create(false),
  275. dOutlineScale: ValueCell.create(1),
  276. dTransparentOutline: ValueCell.create(transparentOutline),
  277. };
  278. const schema = { ...PostprocessingSchema };
  279. const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag);
  280. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  281. return createComputeRenderable(renderItem, values);
  282. }
  283. export const PostprocessingParams = {
  284. occlusion: PD.MappedStatic('on', {
  285. on: PD.Group({
  286. samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
  287. multiScale: PD.MappedStatic('off', {
  288. on: PD.Group({
  289. levels: PD.ObjectList({
  290. radius: PD.Numeric(5, { min: 0, max: 20, step: 0.1 }, { description: 'Final occlusion radius is 2^x' }),
  291. bias: PD.Numeric(1, { min: 0, max: 3, step: 0.1 }),
  292. }, o => `${o.radius}, ${o.bias}`, { defaultValue: [
  293. { radius: 2, bias: 1 },
  294. { radius: 5, bias: 1 },
  295. { radius: 8, bias: 1 },
  296. { radius: 11, bias: 1 },
  297. ] }),
  298. nearThreshold: PD.Numeric(10, { min: 0, max: 50, step: 1 }),
  299. farThreshold: PD.Numeric(1500, { min: 0, max: 10000, step: 100 }),
  300. }),
  301. off: PD.Group({})
  302. }, { cycle: true }),
  303. radius: PD.Numeric(5, { min: 0, max: 20, step: 0.1 }, { description: 'Final occlusion radius is 2^x', hideIf: p => p?.multiScale.name === 'on' }),
  304. bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
  305. blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
  306. resolutionScale: PD.Numeric(1, { min: 0.1, max: 1, step: 0.05 }, { description: 'Adjust resolution of occlusion calculation' }),
  307. color: PD.Color(Color(0x000000)),
  308. }),
  309. off: PD.Group({})
  310. }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
  311. shadow: PD.MappedStatic('off', {
  312. on: PD.Group({
  313. steps: PD.Numeric(1, { min: 1, max: 64, step: 1 }),
  314. bias: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
  315. maxDistance: PD.Numeric(3, { min: 0, max: 256, step: 1 }),
  316. tolerance: PD.Numeric(1.0, { min: 0.0, max: 10.0, step: 0.1 }),
  317. }),
  318. off: PD.Group({})
  319. }, { cycle: true, description: 'Simplistic shadows' }),
  320. outline: PD.MappedStatic('off', {
  321. on: PD.Group({
  322. scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
  323. threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
  324. color: PD.Color(Color(0x000000)),
  325. includeTransparent: PD.Boolean(true, { description: 'Whether to show outline for transparent objects' }),
  326. }),
  327. off: PD.Group({})
  328. }, { cycle: true, description: 'Draw outline around 3D objects' }),
  329. antialiasing: PD.MappedStatic('smaa', {
  330. fxaa: PD.Group(FxaaParams),
  331. smaa: PD.Group(SmaaParams),
  332. off: PD.Group({})
  333. }, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
  334. sharpening: PD.MappedStatic('off', {
  335. on: PD.Group(CasParams),
  336. off: PD.Group({})
  337. }, { cycle: true, description: 'Contrast Adaptive Sharpening' }),
  338. background: PD.Group(BackgroundParams, { isFlat: true }),
  339. };
  340. export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
  341. type Levels = {
  342. count: number
  343. radius: number[]
  344. bias: number[]
  345. }
  346. function getLevels(props: { radius: number, bias: number }[], levels?: Levels): Levels {
  347. const count = props.length;
  348. const { radius, bias } = levels || {
  349. radius: (new Array(count * 3)).fill(0),
  350. bias: (new Array(count * 3)).fill(0),
  351. };
  352. props = props.slice().sort((a, b) => a.radius - b.radius);
  353. for (let i = 0; i < count; ++i) {
  354. const p = props[i];
  355. radius[i] = Math.pow(2, p.radius);
  356. bias[i] = p.bias;
  357. }
  358. return { count, radius, bias };
  359. }
  360. export class PostprocessingPass {
  361. static isEnabled(props: PostprocessingProps) {
  362. return props.occlusion.name === 'on' || props.shadow.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
  363. }
  364. static isTransparentOutlineEnabled(props: PostprocessingProps) {
  365. return props.outline.name === 'on' && props.outline.params.includeTransparent;
  366. }
  367. readonly target: RenderTarget;
  368. private readonly outlinesTarget: RenderTarget;
  369. private readonly outlinesRenderable: OutlinesRenderable;
  370. private readonly shadowsTarget: RenderTarget;
  371. private readonly shadowsRenderable: ShadowsRenderable;
  372. private readonly ssaoFramebuffer: Framebuffer;
  373. private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
  374. private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
  375. private readonly downsampledDepthTarget: RenderTarget;
  376. private readonly downsampleDepthRenderable: CopyRenderable;
  377. private readonly depthHalfTarget: RenderTarget;
  378. private readonly depthHalfRenderable: CopyRenderable;
  379. private readonly depthQuarterTarget: RenderTarget;
  380. private readonly depthQuarterRenderable: CopyRenderable;
  381. private readonly ssaoDepthTexture: Texture;
  382. private readonly ssaoDepthBlurProxyTexture: Texture;
  383. private readonly ssaoRenderable: SsaoRenderable;
  384. private readonly ssaoBlurFirstPassRenderable: SsaoBlurRenderable;
  385. private readonly ssaoBlurSecondPassRenderable: SsaoBlurRenderable;
  386. private nSamples: number;
  387. private blurKernelSize: number;
  388. private readonly renderable: PostprocessingRenderable;
  389. private ssaoScale: number;
  390. private calcSsaoScale(resolutionScale: number) {
  391. // downscale ssao for high pixel-ratios
  392. return Math.min(1, 1 / this.webgl.pixelRatio) * resolutionScale;
  393. }
  394. private levels: { radius: number, bias: number }[];
  395. private readonly bgColor = Vec3();
  396. readonly background: BackgroundPass;
  397. constructor(private readonly webgl: WebGLContext, assetManager: AssetManager, private readonly drawPass: DrawPass) {
  398. const { colorTarget, depthTextureTransparent, depthTextureOpaque } = drawPass;
  399. const width = colorTarget.getWidth();
  400. const height = colorTarget.getHeight();
  401. this.nSamples = 1;
  402. this.blurKernelSize = 1;
  403. this.ssaoScale = this.calcSsaoScale(1);
  404. this.levels = [];
  405. // needs to be linear for anti-aliasing pass
  406. this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
  407. this.outlinesTarget = webgl.createRenderTarget(width, height, false);
  408. this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent, true);
  409. this.shadowsTarget = webgl.createRenderTarget(width, height, false);
  410. this.shadowsRenderable = getShadowsRenderable(webgl, depthTextureOpaque);
  411. this.ssaoFramebuffer = webgl.resources.framebuffer();
  412. this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
  413. this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
  414. const sw = Math.floor(width * this.ssaoScale);
  415. const sh = Math.floor(height * this.ssaoScale);
  416. const hw = Math.max(1, Math.floor(sw * 0.5));
  417. const hh = Math.max(1, Math.floor(sh * 0.5));
  418. const qw = Math.max(1, Math.floor(sw * 0.25));
  419. const qh = Math.max(1, Math.floor(sh * 0.25));
  420. this.downsampledDepthTarget = drawPass.packedDepth
  421. ? webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear', 'rgba')
  422. : webgl.createRenderTarget(sw, sh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
  423. this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTextureOpaque);
  424. const depthTexture = this.ssaoScale === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture;
  425. this.depthHalfTarget = drawPass.packedDepth
  426. ? webgl.createRenderTarget(hw, hh, false, 'uint8', 'linear', 'rgba')
  427. : webgl.createRenderTarget(hw, hh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
  428. this.depthHalfRenderable = createCopyRenderable(webgl, depthTexture);
  429. this.depthQuarterTarget = drawPass.packedDepth
  430. ? webgl.createRenderTarget(qw, qh, false, 'uint8', 'linear', 'rgba')
  431. : webgl.createRenderTarget(qw, qh, false, 'float32', 'linear', webgl.isWebGL2 ? 'alpha' : 'rgba');
  432. this.depthQuarterRenderable = createCopyRenderable(webgl, this.depthHalfTarget.texture);
  433. this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
  434. this.ssaoDepthTexture.define(sw, sh);
  435. this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
  436. this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
  437. this.ssaoDepthBlurProxyTexture.define(sw, sh);
  438. this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
  439. this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
  440. this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture, this.depthHalfTarget.texture, this.depthQuarterTarget.texture);
  441. this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
  442. this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
  443. this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture, true);
  444. this.background = new BackgroundPass(webgl, assetManager, width, height);
  445. }
  446. setSize(width: number, height: number) {
  447. const [w, h] = this.renderable.values.uTexSize.ref.value;
  448. const ssaoScale = this.calcSsaoScale(1);
  449. if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
  450. this.ssaoScale = ssaoScale;
  451. this.target.setSize(width, height);
  452. this.outlinesTarget.setSize(width, height);
  453. this.shadowsTarget.setSize(width, height);
  454. const sw = Math.floor(width * this.ssaoScale);
  455. const sh = Math.floor(height * this.ssaoScale);
  456. this.downsampledDepthTarget.setSize(sw, sh);
  457. this.ssaoDepthTexture.define(sw, sh);
  458. this.ssaoDepthBlurProxyTexture.define(sw, sh);
  459. const hw = Math.max(1, Math.floor(sw * 0.5));
  460. const hh = Math.max(1, Math.floor(sh * 0.5));
  461. this.depthHalfTarget.setSize(hw, hh);
  462. const qw = Math.max(1, Math.floor(sw * 0.25));
  463. const qh = Math.max(1, Math.floor(sh * 0.25));
  464. this.depthQuarterTarget.setSize(qw, qh);
  465. ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
  466. ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
  467. ValueCell.update(this.shadowsRenderable.values.uTexSize, Vec2.set(this.shadowsRenderable.values.uTexSize.ref.value, width, height));
  468. ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
  469. ValueCell.update(this.depthHalfRenderable.values.uTexSize, Vec2.set(this.depthHalfRenderable.values.uTexSize.ref.value, hw, hh));
  470. ValueCell.update(this.depthQuarterRenderable.values.uTexSize, Vec2.set(this.depthQuarterRenderable.values.uTexSize.ref.value, qw, qh));
  471. ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
  472. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
  473. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
  474. const depthTexture = this.ssaoScale === 1 ? this.drawPass.depthTextureOpaque : this.downsampledDepthTarget.texture;
  475. ValueCell.update(this.depthHalfRenderable.values.tColor, depthTexture);
  476. ValueCell.update(this.ssaoRenderable.values.tDepth, depthTexture);
  477. this.depthHalfRenderable.update();
  478. this.ssaoRenderable.update();
  479. this.background.setSize(width, height);
  480. }
  481. }
  482. private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
  483. let needsUpdateShadows = false;
  484. let needsUpdateMain = false;
  485. let needsUpdateSsao = false;
  486. let needsUpdateSsaoBlur = false;
  487. let needsUpdateDepthHalf = false;
  488. let needsUpdateOutlines = false;
  489. const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
  490. const outlinesEnabled = props.outline.name === 'on';
  491. const shadowsEnabled = props.shadow.name === 'on';
  492. const occlusionEnabled = props.occlusion.name === 'on';
  493. const invProjection = Mat4.identity();
  494. Mat4.invert(invProjection, camera.projection);
  495. const [w, h] = this.renderable.values.uTexSize.ref.value;
  496. const v = camera.viewport;
  497. if (props.occlusion.name === 'on') {
  498. ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
  499. ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
  500. const b = this.ssaoRenderable.values.uBounds;
  501. const s = this.ssaoScale;
  502. Vec4.set(b.ref.value,
  503. Math.floor(v.x * s) / (w * s),
  504. Math.floor(v.y * s) / (h * s),
  505. Math.ceil((v.x + v.width) * s) / (w * s),
  506. Math.ceil((v.y + v.height) * s) / (h * s)
  507. );
  508. ValueCell.update(b, b.ref.value);
  509. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uBounds, b.ref.value);
  510. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uBounds, b.ref.value);
  511. ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near);
  512. ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near);
  513. ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
  514. ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
  515. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uInvProjection, invProjection);
  516. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uInvProjection, invProjection);
  517. if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
  518. needsUpdateSsaoBlur = true;
  519. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
  520. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
  521. }
  522. if (this.nSamples !== props.occlusion.params.samples) {
  523. needsUpdateSsao = true;
  524. this.nSamples = props.occlusion.params.samples;
  525. ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.nSamples));
  526. ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
  527. }
  528. const multiScale = props.occlusion.params.multiScale.name === 'on';
  529. if (this.ssaoRenderable.values.dMultiScale.ref.value !== multiScale) {
  530. needsUpdateSsao = true;
  531. ValueCell.update(this.ssaoRenderable.values.dMultiScale, multiScale);
  532. }
  533. if (props.occlusion.params.multiScale.name === 'on') {
  534. const mp = props.occlusion.params.multiScale.params;
  535. if (!deepEqual(this.levels, mp.levels)) {
  536. needsUpdateSsao = true;
  537. this.levels = mp.levels;
  538. const levels = getLevels(mp.levels);
  539. ValueCell.updateIfChanged(this.ssaoRenderable.values.dLevels, levels.count);
  540. ValueCell.update(this.ssaoRenderable.values.uLevelRadius, levels.radius);
  541. ValueCell.update(this.ssaoRenderable.values.uLevelBias, levels.bias);
  542. }
  543. ValueCell.updateIfChanged(this.ssaoRenderable.values.uNearThreshold, mp.nearThreshold);
  544. ValueCell.updateIfChanged(this.ssaoRenderable.values.uFarThreshold, mp.farThreshold);
  545. } else {
  546. ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
  547. }
  548. ValueCell.updateIfChanged(this.ssaoRenderable.values.uBias, props.occlusion.params.bias);
  549. if (this.blurKernelSize !== props.occlusion.params.blurKernelSize) {
  550. needsUpdateSsaoBlur = true;
  551. this.blurKernelSize = props.occlusion.params.blurKernelSize;
  552. const kernel = getBlurKernel(this.blurKernelSize);
  553. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
  554. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
  555. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
  556. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
  557. }
  558. const ssaoScale = this.calcSsaoScale(props.occlusion.params.resolutionScale);
  559. if (this.ssaoScale !== ssaoScale) {
  560. needsUpdateSsao = true;
  561. needsUpdateDepthHalf = true;
  562. this.ssaoScale = ssaoScale;
  563. const sw = Math.floor(w * this.ssaoScale);
  564. const sh = Math.floor(h * this.ssaoScale);
  565. this.downsampledDepthTarget.setSize(sw, sh);
  566. this.ssaoDepthTexture.define(sw, sh);
  567. this.ssaoDepthBlurProxyTexture.define(sw, sh);
  568. const hw = Math.floor(sw * 0.5);
  569. const hh = Math.floor(sh * 0.5);
  570. this.depthHalfTarget.setSize(hw, hh);
  571. const qw = Math.floor(sw * 0.25);
  572. const qh = Math.floor(sh * 0.25);
  573. this.depthQuarterTarget.setSize(qw, qh);
  574. const depthTexture = this.ssaoScale === 1 ? this.drawPass.depthTextureOpaque : this.downsampledDepthTarget.texture;
  575. ValueCell.update(this.depthHalfRenderable.values.tColor, depthTexture);
  576. ValueCell.update(this.ssaoRenderable.values.tDepth, depthTexture);
  577. ValueCell.update(this.ssaoRenderable.values.tDepthHalf, this.depthHalfTarget.texture);
  578. ValueCell.update(this.ssaoRenderable.values.tDepthQuarter, this.depthQuarterTarget.texture);
  579. ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
  580. ValueCell.update(this.depthHalfRenderable.values.uTexSize, Vec2.set(this.depthHalfRenderable.values.uTexSize.ref.value, hw, hh));
  581. ValueCell.update(this.depthQuarterRenderable.values.uTexSize, Vec2.set(this.depthQuarterRenderable.values.uTexSize.ref.value, qw, qh));
  582. ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
  583. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
  584. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
  585. }
  586. ValueCell.update(this.renderable.values.uOcclusionColor, Color.toVec3Normalized(this.renderable.values.uOcclusionColor.ref.value, props.occlusion.params.color));
  587. }
  588. if (props.shadow.name === 'on') {
  589. ValueCell.update(this.shadowsRenderable.values.uProjection, camera.projection);
  590. ValueCell.update(this.shadowsRenderable.values.uInvProjection, invProjection);
  591. Vec4.set(this.shadowsRenderable.values.uBounds.ref.value,
  592. v.x / w,
  593. v.y / h,
  594. (v.x + v.width) / w,
  595. (v.y + v.height) / h
  596. );
  597. ValueCell.update(this.shadowsRenderable.values.uBounds, this.shadowsRenderable.values.uBounds.ref.value);
  598. ValueCell.updateIfChanged(this.shadowsRenderable.values.uNear, camera.near);
  599. ValueCell.updateIfChanged(this.shadowsRenderable.values.uFar, camera.far);
  600. if (this.shadowsRenderable.values.dOrthographic.ref.value !== orthographic) {
  601. ValueCell.update(this.shadowsRenderable.values.dOrthographic, orthographic);
  602. needsUpdateShadows = true;
  603. }
  604. ValueCell.updateIfChanged(this.shadowsRenderable.values.uMaxDistance, props.shadow.params.maxDistance);
  605. ValueCell.updateIfChanged(this.shadowsRenderable.values.uTolerance, props.shadow.params.tolerance);
  606. ValueCell.updateIfChanged(this.shadowsRenderable.values.uBias, props.shadow.params.bias);
  607. if (this.shadowsRenderable.values.dSteps.ref.value !== props.shadow.params.steps) {
  608. ValueCell.update(this.shadowsRenderable.values.dSteps, props.shadow.params.steps);
  609. needsUpdateShadows = true;
  610. }
  611. ValueCell.update(this.shadowsRenderable.values.uLightDirection, light.direction);
  612. ValueCell.update(this.shadowsRenderable.values.uLightColor, light.color);
  613. if (this.shadowsRenderable.values.dLightCount.ref.value !== light.count) {
  614. ValueCell.update(this.shadowsRenderable.values.dLightCount, light.count);
  615. needsUpdateShadows = true;
  616. }
  617. }
  618. if (props.outline.name === 'on') {
  619. const transparentOutline = props.outline.params.includeTransparent ?? true;
  620. const outlineScale = Math.max(1, Math.round(props.outline.params.scale * this.webgl.pixelRatio)) - 1;
  621. const outlineThreshold = 50 * props.outline.params.threshold * this.webgl.pixelRatio;
  622. ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
  623. ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
  624. ValueCell.update(this.outlinesRenderable.values.uInvProjection, invProjection);
  625. if (this.outlinesRenderable.values.dTransparentOutline.ref.value !== transparentOutline) {
  626. needsUpdateOutlines = true;
  627. ValueCell.update(this.outlinesRenderable.values.dTransparentOutline, transparentOutline);
  628. }
  629. if (this.outlinesRenderable.values.dOrthographic.ref.value !== orthographic) {
  630. needsUpdateOutlines = true;
  631. ValueCell.update(this.outlinesRenderable.values.dOrthographic, orthographic);
  632. }
  633. ValueCell.updateIfChanged(this.outlinesRenderable.values.uOutlineThreshold, outlineThreshold);
  634. ValueCell.update(this.renderable.values.uOutlineColor, Color.toVec3Normalized(this.renderable.values.uOutlineColor.ref.value, props.outline.params.color));
  635. if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) {
  636. needsUpdateMain = true;
  637. ValueCell.update(this.renderable.values.dOutlineScale, outlineScale);
  638. }
  639. if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) {
  640. needsUpdateMain = true;
  641. ValueCell.update(this.renderable.values.dTransparentOutline, transparentOutline);
  642. }
  643. }
  644. ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
  645. ValueCell.updateIfChanged(this.renderable.values.uNear, camera.near);
  646. ValueCell.updateIfChanged(this.renderable.values.uFogFar, camera.fogFar);
  647. ValueCell.updateIfChanged(this.renderable.values.uFogNear, camera.fogNear);
  648. ValueCell.update(this.renderable.values.uFogColor, Color.toVec3Normalized(this.renderable.values.uFogColor.ref.value, backgroundColor));
  649. ValueCell.updateIfChanged(this.renderable.values.uTransparentBackground, transparentBackground);
  650. if (this.renderable.values.dOrthographic.ref.value !== orthographic) {
  651. needsUpdateMain = true;
  652. ValueCell.update(this.renderable.values.dOrthographic, orthographic);
  653. }
  654. if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) {
  655. needsUpdateMain = true;
  656. ValueCell.update(this.renderable.values.dOutlineEnable, outlinesEnabled);
  657. }
  658. if (this.renderable.values.dShadowEnable.ref.value !== shadowsEnabled) {
  659. needsUpdateMain = true;
  660. ValueCell.update(this.renderable.values.dShadowEnable, shadowsEnabled);
  661. }
  662. if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) {
  663. needsUpdateMain = true;
  664. ValueCell.update(this.renderable.values.dOcclusionEnable, occlusionEnabled);
  665. }
  666. if (needsUpdateOutlines) {
  667. this.outlinesRenderable.update();
  668. }
  669. if (needsUpdateShadows) {
  670. this.shadowsRenderable.update();
  671. }
  672. if (needsUpdateSsao) {
  673. this.ssaoRenderable.update();
  674. }
  675. if (needsUpdateSsaoBlur) {
  676. this.ssaoBlurFirstPassRenderable.update();
  677. this.ssaoBlurSecondPassRenderable.update();
  678. }
  679. if (needsUpdateDepthHalf) {
  680. this.depthHalfRenderable.update();
  681. }
  682. if (needsUpdateMain) {
  683. this.renderable.update();
  684. }
  685. const { gl, state } = this.webgl;
  686. state.enable(gl.SCISSOR_TEST);
  687. state.disable(gl.BLEND);
  688. state.disable(gl.DEPTH_TEST);
  689. state.depthMask(false);
  690. }
  691. private occlusionOffset: [x: number, y: number] = [0, 0];
  692. setOcclusionOffset(x: number, y: number) {
  693. this.occlusionOffset[0] = x;
  694. this.occlusionOffset[1] = y;
  695. ValueCell.update(this.renderable.values.uOcclusionOffset, Vec2.set(this.renderable.values.uOcclusionOffset.ref.value, x, y));
  696. }
  697. private transparentBackground = false;
  698. setTransparentBackground(value: boolean) {
  699. this.transparentBackground = value;
  700. }
  701. render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
  702. if (isTimingMode) this.webgl.timer.mark('PostprocessingPass.render');
  703. this.updateState(camera, transparentBackground, backgroundColor, props, light);
  704. const { gl, state } = this.webgl;
  705. const { x, y, width, height } = camera.viewport;
  706. // don't render occlusion if offset is given,
  707. // which will reuse the existing occlusion
  708. if (props.occlusion.name === 'on' && this.occlusionOffset[0] === 0 && this.occlusionOffset[1] === 0) {
  709. if (isTimingMode) this.webgl.timer.mark('SSAO.render');
  710. const sx = Math.floor(x * this.ssaoScale);
  711. const sy = Math.floor(y * this.ssaoScale);
  712. const sw = Math.ceil(width * this.ssaoScale);
  713. const sh = Math.ceil(height * this.ssaoScale);
  714. state.viewport(sx, sy, sw, sh);
  715. state.scissor(sx, sy, sw, sh);
  716. if (this.ssaoScale < 1) {
  717. if (isTimingMode) this.webgl.timer.mark('SSAO.downsample');
  718. this.downsampledDepthTarget.bind();
  719. this.downsampleDepthRenderable.render();
  720. if (isTimingMode) this.webgl.timer.markEnd('SSAO.downsample');
  721. }
  722. if (isTimingMode) this.webgl.timer.mark('SSAO.half');
  723. this.depthHalfTarget.bind();
  724. this.depthHalfRenderable.render();
  725. if (isTimingMode) this.webgl.timer.markEnd('SSAO.half');
  726. if (isTimingMode) this.webgl.timer.mark('SSAO.quarter');
  727. this.depthQuarterTarget.bind();
  728. this.depthQuarterRenderable.render();
  729. if (isTimingMode) this.webgl.timer.markEnd('SSAO.quarter');
  730. this.ssaoFramebuffer.bind();
  731. this.ssaoRenderable.render();
  732. this.ssaoBlurFirstPassFramebuffer.bind();
  733. this.ssaoBlurFirstPassRenderable.render();
  734. this.ssaoBlurSecondPassFramebuffer.bind();
  735. this.ssaoBlurSecondPassRenderable.render();
  736. if (isTimingMode) this.webgl.timer.markEnd('SSAO.render');
  737. }
  738. state.viewport(x, y, width, height);
  739. state.scissor(x, y, width, height);
  740. if (props.outline.name === 'on') {
  741. this.outlinesTarget.bind();
  742. this.outlinesRenderable.render();
  743. }
  744. if (props.shadow.name === 'on') {
  745. this.shadowsTarget.bind();
  746. this.shadowsRenderable.render();
  747. }
  748. if (toDrawingBuffer) {
  749. this.webgl.unbindFramebuffer();
  750. } else {
  751. this.target.bind();
  752. }
  753. this.background.update(camera, props.background);
  754. if (this.background.isEnabled(props.background)) {
  755. if (this.transparentBackground) {
  756. state.clearColor(0, 0, 0, 0);
  757. } else {
  758. Color.toVec3Normalized(this.bgColor, backgroundColor);
  759. state.clearColor(this.bgColor[0], this.bgColor[1], this.bgColor[2], 1);
  760. }
  761. gl.clear(gl.COLOR_BUFFER_BIT);
  762. state.enable(gl.BLEND);
  763. state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  764. this.background.render();
  765. } else {
  766. state.clearColor(0, 0, 0, 1);
  767. gl.clear(gl.COLOR_BUFFER_BIT);
  768. }
  769. this.renderable.render();
  770. if (isTimingMode) this.webgl.timer.markEnd('PostprocessingPass.render');
  771. }
  772. }
  773. export class AntialiasingPass {
  774. static isEnabled(props: PostprocessingProps) {
  775. return props.antialiasing.name !== 'off';
  776. }
  777. readonly target: RenderTarget;
  778. private readonly internalTarget: RenderTarget;
  779. private readonly fxaa: FxaaPass;
  780. private readonly smaa: SmaaPass;
  781. private readonly cas: CasPass;
  782. constructor(webgl: WebGLContext, private drawPass: DrawPass) {
  783. const { colorTarget } = drawPass;
  784. const width = colorTarget.getWidth();
  785. const height = colorTarget.getHeight();
  786. this.target = webgl.createRenderTarget(width, height, false);
  787. this.internalTarget = webgl.createRenderTarget(width, height, false);
  788. this.fxaa = new FxaaPass(webgl, this.target.texture);
  789. this.smaa = new SmaaPass(webgl, this.target.texture);
  790. this.cas = new CasPass(webgl, this.target.texture);
  791. }
  792. setSize(width: number, height: number) {
  793. const w = this.target.texture.getWidth();
  794. const h = this.target.texture.getHeight();
  795. if (width !== w || height !== h) {
  796. this.target.setSize(width, height);
  797. this.internalTarget.setSize(width, height);
  798. this.fxaa.setSize(width, height);
  799. if (this.smaa.supported) this.smaa.setSize(width, height);
  800. this.cas.setSize(width, height);
  801. }
  802. }
  803. private _renderFxaa(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
  804. if (props.antialiasing.name !== 'fxaa') return;
  805. const input = PostprocessingPass.isEnabled(props)
  806. ? this.drawPass.postprocessing.target.texture
  807. : this.drawPass.colorTarget.texture;
  808. this.fxaa.update(input, props.antialiasing.params);
  809. this.fxaa.render(camera.viewport, target);
  810. }
  811. private _renderSmaa(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
  812. if (props.antialiasing.name !== 'smaa') return;
  813. const input = PostprocessingPass.isEnabled(props)
  814. ? this.drawPass.postprocessing.target.texture
  815. : this.drawPass.colorTarget.texture;
  816. this.smaa.update(input, props.antialiasing.params);
  817. this.smaa.render(camera.viewport, target);
  818. }
  819. private _renderAntialiasing(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
  820. if (props.antialiasing.name === 'fxaa') {
  821. this._renderFxaa(camera, target, props);
  822. } else if (props.antialiasing.name === 'smaa') {
  823. this._renderSmaa(camera, target, props);
  824. }
  825. }
  826. private _renderCas(camera: ICamera, target: RenderTarget | undefined, props: PostprocessingProps) {
  827. if (props.sharpening.name !== 'on') return;
  828. const input = props.antialiasing.name !== 'off'
  829. ? this.internalTarget.texture
  830. : PostprocessingPass.isEnabled(props)
  831. ? this.drawPass.postprocessing.target.texture
  832. : this.drawPass.colorTarget.texture;
  833. this.cas.update(input, props.sharpening.params);
  834. this.cas.render(camera.viewport, target);
  835. }
  836. render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
  837. if (props.antialiasing.name === 'off' && props.sharpening.name === 'off') return;
  838. if (props.antialiasing.name === 'smaa' && !this.smaa.supported) {
  839. console.error('SMAA not supported, missing "HTMLImageElement"');
  840. return;
  841. }
  842. const target = toDrawingBuffer ? undefined : this.target;
  843. if (props.sharpening.name === 'off') {
  844. this._renderAntialiasing(camera, target, props);
  845. } else if (props.antialiasing.name === 'off') {
  846. this._renderCas(camera, target, props);
  847. } else {
  848. this._renderAntialiasing(camera, this.internalTarget, props);
  849. this._renderCas(camera, target, props);
  850. }
  851. }
  852. }