postprocessing.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. /**
  2. * Copyright (c) 2019-2021 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. */
  7. import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
  8. import { TextureSpec, Values, UniformSpec, DefineSpec } from '../../mol-gl/renderable/schema';
  9. import { ShaderCode } from '../../mol-gl/shader-code';
  10. import { WebGLContext } from '../../mol-gl/webgl/context';
  11. import { Texture } from '../../mol-gl/webgl/texture';
  12. import { ValueCell } from '../../mol-util';
  13. import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
  14. import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
  15. import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
  16. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  17. import { RenderTarget } from '../../mol-gl/webgl/render-target';
  18. import { DrawPass } from './draw';
  19. import { ICamera } from '../../mol-canvas3d/camera';
  20. import { quad_vert } from '../../mol-gl/shader/quad.vert';
  21. import { outlines_frag } from '../../mol-gl/shader/outlines.frag';
  22. import { ssao_frag } from '../../mol-gl/shader/ssao.frag';
  23. import { ssaoBlur_frag } from '../../mol-gl/shader/ssao-blur.frag';
  24. import { postprocessing_frag } from '../../mol-gl/shader/postprocessing.frag';
  25. import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
  26. import { Color } from '../../mol-util/color';
  27. import { FxaaParams, FxaaPass } from './fxaa';
  28. import { SmaaParams, SmaaPass } from './smaa';
  29. const OutlinesSchema = {
  30. ...QuadSchema,
  31. tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  32. uTexSize: UniformSpec('v2'),
  33. dOrthographic: DefineSpec('number'),
  34. uNear: UniformSpec('f'),
  35. uFar: UniformSpec('f'),
  36. uMaxPossibleViewZDiff: UniformSpec('f'),
  37. };
  38. type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
  39. function getOutlinesRenderable(ctx: WebGLContext, depthTexture: Texture): OutlinesRenderable {
  40. const values: Values<typeof OutlinesSchema> = {
  41. ...QuadValues,
  42. tDepth: ValueCell.create(depthTexture),
  43. uTexSize: ValueCell.create(Vec2.create(depthTexture.getWidth(), depthTexture.getHeight())),
  44. dOrthographic: ValueCell.create(0),
  45. uNear: ValueCell.create(1),
  46. uFar: ValueCell.create(10000),
  47. uMaxPossibleViewZDiff: ValueCell.create(0.5),
  48. };
  49. const schema = { ...OutlinesSchema };
  50. const shaderCode = ShaderCode('outlines', quad_vert, outlines_frag);
  51. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  52. return createComputeRenderable(renderItem, values);
  53. }
  54. const SsaoSchema = {
  55. ...QuadSchema,
  56. tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  57. uSamples: UniformSpec('v3[]'),
  58. dNSamples: DefineSpec('number'),
  59. uProjection: UniformSpec('m4'),
  60. uInvProjection: UniformSpec('m4'),
  61. uBounds: UniformSpec('v4'),
  62. uTexSize: UniformSpec('v2'),
  63. uRadius: UniformSpec('f'),
  64. uBias: UniformSpec('f'),
  65. };
  66. type SsaoRenderable = ComputeRenderable<Values<typeof SsaoSchema>>
  67. function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRenderable {
  68. const values: Values<typeof SsaoSchema> = {
  69. ...QuadValues,
  70. tDepth: ValueCell.create(depthTexture),
  71. uSamples: ValueCell.create([0.0, 0.0, 1.0]),
  72. dNSamples: ValueCell.create(1),
  73. uProjection: ValueCell.create(Mat4.identity()),
  74. uInvProjection: ValueCell.create(Mat4.identity()),
  75. uBounds: ValueCell.create(Vec4()),
  76. uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
  77. uRadius: ValueCell.create(8.0),
  78. uBias: ValueCell.create(0.025),
  79. };
  80. const schema = { ...SsaoSchema };
  81. const shaderCode = ShaderCode('ssao', quad_vert, ssao_frag);
  82. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  83. return createComputeRenderable(renderItem, values);
  84. }
  85. const SsaoBlurSchema = {
  86. ...QuadSchema,
  87. tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  88. uTexSize: UniformSpec('v2'),
  89. uKernel: UniformSpec('f[]'),
  90. dOcclusionKernelSize: DefineSpec('number'),
  91. uBlurDirectionX: UniformSpec('f'),
  92. uBlurDirectionY: UniformSpec('f'),
  93. uMaxPossibleViewZDiff: UniformSpec('f'),
  94. uNear: UniformSpec('f'),
  95. uFar: UniformSpec('f'),
  96. uBounds: UniformSpec('v4'),
  97. dOrthographic: DefineSpec('number'),
  98. };
  99. type SsaoBlurRenderable = ComputeRenderable<Values<typeof SsaoBlurSchema>>
  100. function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, direction: 'horizontal' | 'vertical'): SsaoBlurRenderable {
  101. const values: Values<typeof SsaoBlurSchema> = {
  102. ...QuadValues,
  103. tSsaoDepth: ValueCell.create(ssaoDepthTexture),
  104. uTexSize: ValueCell.create(Vec2.create(ssaoDepthTexture.getWidth(), ssaoDepthTexture.getHeight())),
  105. uKernel: ValueCell.create([0.0]),
  106. dOcclusionKernelSize: ValueCell.create(1),
  107. uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0),
  108. uBlurDirectionY: ValueCell.create(direction === 'vertical' ? 1 : 0),
  109. uMaxPossibleViewZDiff: ValueCell.create(0.5),
  110. uNear: ValueCell.create(0.0),
  111. uFar: ValueCell.create(10000.0),
  112. uBounds: ValueCell.create(Vec4()),
  113. dOrthographic: ValueCell.create(0),
  114. };
  115. const schema = { ...SsaoBlurSchema };
  116. const shaderCode = ShaderCode('ssao_blur', quad_vert, ssaoBlur_frag);
  117. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  118. return createComputeRenderable(renderItem, values);
  119. }
  120. function getBlurKernel(kernelSize: number): number[] {
  121. let sigma = kernelSize / 3.0;
  122. let halfKernelSize = Math.floor((kernelSize + 1) / 2);
  123. let kernel = [];
  124. for (let x = 0; x < halfKernelSize; x++) {
  125. kernel.push((1.0 / ((Math.sqrt(2 * Math.PI)) * sigma)) * Math.exp(-x * x / (2 * sigma * sigma)));
  126. }
  127. return kernel;
  128. }
  129. function getSamples(vectorSamples: Vec3[], nSamples: number): number[] {
  130. let samples = [];
  131. for (let i = 0; i < nSamples; i++) {
  132. let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples);
  133. scale = 0.1 + scale * (1.0 - 0.1);
  134. samples.push(vectorSamples[i][0] * scale);
  135. samples.push(vectorSamples[i][1] * scale);
  136. samples.push(vectorSamples[i][2] * scale);
  137. }
  138. return samples;
  139. }
  140. const PostprocessingSchema = {
  141. ...QuadSchema,
  142. tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  143. tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  144. tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  145. tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  146. uTexSize: UniformSpec('v2'),
  147. dOrthographic: DefineSpec('number'),
  148. uNear: UniformSpec('f'),
  149. uFar: UniformSpec('f'),
  150. uFogNear: UniformSpec('f'),
  151. uFogFar: UniformSpec('f'),
  152. uFogColor: UniformSpec('v3'),
  153. uTransparentBackground: UniformSpec('b'),
  154. uMaxPossibleViewZDiff: UniformSpec('f'),
  155. dOcclusionEnable: DefineSpec('boolean'),
  156. dOutlineEnable: DefineSpec('boolean'),
  157. dOutlineScale: DefineSpec('number'),
  158. uOutlineThreshold: UniformSpec('f'),
  159. };
  160. type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
  161. function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
  162. const values: Values<typeof PostprocessingSchema> = {
  163. ...QuadValues,
  164. tSsaoDepth: ValueCell.create(ssaoDepthTexture),
  165. tColor: ValueCell.create(colorTexture),
  166. tDepth: ValueCell.create(depthTexture),
  167. tOutlines: ValueCell.create(outlinesTexture),
  168. uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
  169. dOrthographic: ValueCell.create(0),
  170. uNear: ValueCell.create(1),
  171. uFar: ValueCell.create(10000),
  172. uFogNear: ValueCell.create(10000),
  173. uFogFar: ValueCell.create(10000),
  174. uFogColor: ValueCell.create(Vec3.create(1, 1, 1)),
  175. uTransparentBackground: ValueCell.create(false),
  176. uMaxPossibleViewZDiff: ValueCell.create(0.5),
  177. dOcclusionEnable: ValueCell.create(false),
  178. dOutlineEnable: ValueCell.create(false),
  179. dOutlineScale: ValueCell.create(1),
  180. uOutlineThreshold: ValueCell.create(0.33),
  181. };
  182. const schema = { ...PostprocessingSchema };
  183. const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag);
  184. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  185. return createComputeRenderable(renderItem, values);
  186. }
  187. export const PostprocessingParams = {
  188. occlusion: PD.MappedStatic('on', {
  189. on: PD.Group({
  190. samples: PD.Numeric(32, {min: 1, max: 256, step: 1}),
  191. radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
  192. bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
  193. blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
  194. }),
  195. off: PD.Group({})
  196. }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
  197. outline: PD.MappedStatic('off', {
  198. on: PD.Group({
  199. scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
  200. threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
  201. }),
  202. off: PD.Group({})
  203. }, { cycle: true, description: 'Draw outline around 3D objects' }),
  204. antialiasing: PD.MappedStatic('smaa', {
  205. fxaa: PD.Group(FxaaParams),
  206. smaa: PD.Group(SmaaParams),
  207. off: PD.Group({})
  208. }, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
  209. };
  210. export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
  211. export class PostprocessingPass {
  212. static isEnabled(props: PostprocessingProps) {
  213. return props.occlusion.name === 'on' || props.outline.name === 'on';
  214. }
  215. readonly target: RenderTarget
  216. private readonly outlinesTarget: RenderTarget
  217. private readonly outlinesRenderable: OutlinesRenderable
  218. private readonly randomHemisphereVector: Vec3[]
  219. private readonly ssaoFramebuffer: Framebuffer
  220. private readonly ssaoBlurFirstPassFramebuffer: Framebuffer
  221. private readonly ssaoBlurSecondPassFramebuffer: Framebuffer
  222. private readonly ssaoDepthTexture: Texture
  223. private readonly ssaoDepthBlurProxyTexture: Texture
  224. private readonly ssaoRenderable: SsaoRenderable
  225. private readonly ssaoBlurFirstPassRenderable: SsaoBlurRenderable
  226. private readonly ssaoBlurSecondPassRenderable: SsaoBlurRenderable
  227. private nSamples: number
  228. private blurKernelSize: number
  229. private readonly renderable: PostprocessingRenderable
  230. private ssaoScale: number
  231. private calcSsaoScale() {
  232. // downscale ssao for high pixel-ratios
  233. return Math.min(1, 1 / this.webgl.pixelRatio);
  234. }
  235. constructor(private webgl: WebGLContext, drawPass: DrawPass) {
  236. this.ssaoScale = this.calcSsaoScale();
  237. const { colorTarget, depthTexture } = drawPass;
  238. const width = colorTarget.getWidth();
  239. const height = colorTarget.getHeight();
  240. this.nSamples = 1;
  241. this.blurKernelSize = 1;
  242. // needs to be linear for anti-aliasing pass
  243. this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
  244. this.outlinesTarget = webgl.createRenderTarget(width, height, false);
  245. this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
  246. this.randomHemisphereVector = [];
  247. for (let i = 0; i < 256; i++) {
  248. let v = Vec3();
  249. v[0] = Math.random() * 2.0 - 1.0;
  250. v[1] = Math.random() * 2.0 - 1.0;
  251. v[2] = Math.random();
  252. Vec3.normalize(v, v);
  253. Vec3.scale(v, v, Math.random());
  254. this.randomHemisphereVector.push(v);
  255. }
  256. this.ssaoFramebuffer = webgl.resources.framebuffer();
  257. this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
  258. this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
  259. const sw = Math.floor(width * this.ssaoScale);
  260. const sh = Math.floor(height * this.ssaoScale);
  261. this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
  262. this.ssaoDepthTexture.define(sw, sh);
  263. this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
  264. this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
  265. this.ssaoDepthBlurProxyTexture.define(sw, sh);
  266. this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
  267. this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
  268. this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture);
  269. this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
  270. this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
  271. this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
  272. }
  273. setSize(width: number, height: number) {
  274. const [w, h] = this.renderable.values.uTexSize.ref.value;
  275. const ssaoScale = this.calcSsaoScale();
  276. if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
  277. this.ssaoScale = ssaoScale;
  278. const sw = Math.floor(width * this.ssaoScale);
  279. const sh = Math.floor(height * this.ssaoScale);
  280. this.target.setSize(width, height);
  281. this.outlinesTarget.setSize(width, height);
  282. this.ssaoDepthTexture.define(sw, sh);
  283. this.ssaoDepthBlurProxyTexture.define(sw, sh);
  284. ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
  285. ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
  286. ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
  287. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
  288. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
  289. }
  290. }
  291. private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
  292. let needsUpdateMain = false;
  293. let needsUpdateSsao = false;
  294. let needsUpdateSsaoBlur = false;
  295. const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
  296. const outlinesEnabled = props.outline.name === 'on';
  297. const occlusionEnabled = props.occlusion.name === 'on';
  298. let invProjection = Mat4.identity();
  299. Mat4.invert(invProjection, camera.projection);
  300. if (props.occlusion.name === 'on') {
  301. ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
  302. ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
  303. const [w, h] = this.renderable.values.uTexSize.ref.value;
  304. const b = this.ssaoRenderable.values.uBounds;
  305. const v = camera.viewport;
  306. const s = this.ssaoScale;
  307. Vec4.set(b.ref.value,
  308. Math.floor(v.x * s) / (w * s),
  309. Math.floor(v.y * s) / (h * s),
  310. Math.ceil((v.x + v.width) * s) / (w * s),
  311. Math.ceil((v.y + v.height) * s) / (h * s)
  312. );
  313. ValueCell.update(b, b.ref.value);
  314. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uBounds, b.ref.value);
  315. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uBounds, b.ref.value);
  316. ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near);
  317. ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near);
  318. ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
  319. ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
  320. if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
  321. needsUpdateSsaoBlur = true;
  322. }
  323. ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
  324. ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
  325. if (this.nSamples !== props.occlusion.params.samples) {
  326. needsUpdateSsao = true;
  327. this.nSamples = props.occlusion.params.samples;
  328. ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
  329. ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
  330. }
  331. ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
  332. ValueCell.updateIfChanged(this.ssaoRenderable.values.uBias, props.occlusion.params.bias);
  333. if (this.blurKernelSize !== props.occlusion.params.blurKernelSize) {
  334. needsUpdateSsaoBlur = true;
  335. this.blurKernelSize = props.occlusion.params.blurKernelSize;
  336. const kernel = getBlurKernel(this.blurKernelSize);
  337. ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
  338. ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
  339. ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
  340. ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
  341. }
  342. }
  343. if (props.outline.name === 'on') {
  344. let { threshold } = props.outline.params;
  345. // orthographic needs lower threshold
  346. if (camera.state.mode === 'orthographic') threshold /= 5;
  347. const factor = Math.pow(1000, threshold) / 1000;
  348. // use radiusMax for stable outlines when zooming
  349. const maxPossibleViewZDiff = factor * camera.state.radiusMax;
  350. const outlineScale = props.outline.params.scale - 1;
  351. ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
  352. ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
  353. ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
  354. ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
  355. if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
  356. ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
  357. }
  358. ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
  359. ValueCell.updateIfChanged(this.renderable.values.uNear, camera.near);
  360. ValueCell.updateIfChanged(this.renderable.values.uFogFar, camera.fogFar);
  361. ValueCell.updateIfChanged(this.renderable.values.uFogNear, camera.fogNear);
  362. ValueCell.update(this.renderable.values.uFogColor, Color.toVec3Normalized(this.renderable.values.uFogColor.ref.value, backgroundColor));
  363. ValueCell.updateIfChanged(this.renderable.values.uTransparentBackground, transparentBackground);
  364. if (this.renderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateMain = true; }
  365. ValueCell.updateIfChanged(this.renderable.values.dOrthographic, orthographic);
  366. if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) { needsUpdateMain = true; }
  367. ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, outlinesEnabled);
  368. if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
  369. ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
  370. if (needsUpdateSsao) {
  371. this.ssaoRenderable.update();
  372. }
  373. if (needsUpdateSsaoBlur) {
  374. this.ssaoBlurFirstPassRenderable.update();
  375. this.ssaoBlurSecondPassRenderable.update();
  376. }
  377. if (needsUpdateMain) {
  378. this.renderable.update();
  379. }
  380. const { gl, state } = this.webgl;
  381. state.enable(gl.SCISSOR_TEST);
  382. state.disable(gl.BLEND);
  383. state.disable(gl.DEPTH_TEST);
  384. state.depthMask(false);
  385. const { x, y, width, height } = camera.viewport;
  386. gl.viewport(x, y, width, height);
  387. gl.scissor(x, y, width, height);
  388. }
  389. render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
  390. this.updateState(camera, transparentBackground, backgroundColor, props);
  391. if (props.outline.name === 'on') {
  392. this.outlinesTarget.bind();
  393. this.outlinesRenderable.render();
  394. }
  395. if (props.occlusion.name === 'on') {
  396. const { x, y, width, height } = camera.viewport;
  397. const sx = Math.floor(x * this.ssaoScale);
  398. const sy = Math.floor(y * this.ssaoScale);
  399. const sw = Math.ceil(width * this.ssaoScale);
  400. const sh = Math.ceil(height * this.ssaoScale);
  401. this.webgl.gl.viewport(sx, sy, sw, sh);
  402. this.webgl.gl.scissor(sx, sy, sw, sh);
  403. this.ssaoFramebuffer.bind();
  404. this.ssaoRenderable.render();
  405. this.ssaoBlurFirstPassFramebuffer.bind();
  406. this.ssaoBlurFirstPassRenderable.render();
  407. this.ssaoBlurSecondPassFramebuffer.bind();
  408. this.ssaoBlurSecondPassRenderable.render();
  409. this.webgl.gl.viewport(x, y, width, height);
  410. this.webgl.gl.scissor(x, y, width, height);
  411. }
  412. if (toDrawingBuffer) {
  413. this.webgl.unbindFramebuffer();
  414. } else {
  415. this.target.bind();
  416. }
  417. const { gl, state } = this.webgl;
  418. state.clearColor(0, 0, 0, 1);
  419. gl.clear(gl.COLOR_BUFFER_BIT);
  420. this.renderable.render();
  421. }
  422. }
  423. export class AntialiasingPass {
  424. static isEnabled(props: PostprocessingProps) {
  425. return props.antialiasing.name !== 'off';
  426. }
  427. readonly target: RenderTarget
  428. private readonly fxaa: FxaaPass
  429. private readonly smaa: SmaaPass
  430. constructor(webgl: WebGLContext, private drawPass: DrawPass) {
  431. const { colorTarget } = drawPass;
  432. const width = colorTarget.getWidth();
  433. const height = colorTarget.getHeight();
  434. this.target = webgl.createRenderTarget(width, height, false);
  435. this.fxaa = new FxaaPass(webgl, this.target.texture);
  436. this.smaa = new SmaaPass(webgl, this.target.texture);
  437. }
  438. setSize(width: number, height: number) {
  439. const w = this.target.texture.getWidth();
  440. const h = this.target.texture.getHeight();
  441. if (width !== w || height !== h) {
  442. this.target.setSize(width, height);
  443. this.fxaa.setSize(width, height);
  444. if (this.smaa.supported) this.smaa.setSize(width, height);
  445. }
  446. }
  447. private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
  448. if (props.antialiasing.name !== 'fxaa') return;
  449. const input = PostprocessingPass.isEnabled(props)
  450. ? this.drawPass.postprocessing.target.texture
  451. : this.drawPass.colorTarget.texture;
  452. this.fxaa.update(input, props.antialiasing.params);
  453. this.fxaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
  454. }
  455. private _renderSmaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
  456. if (props.antialiasing.name !== 'smaa') return;
  457. const input = PostprocessingPass.isEnabled(props)
  458. ? this.drawPass.postprocessing.target.texture
  459. : this.drawPass.colorTarget.texture;
  460. this.smaa.update(input, props.antialiasing.params);
  461. this.smaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
  462. }
  463. render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
  464. if (props.antialiasing.name === 'off') return;
  465. if (props.antialiasing.name === 'fxaa') {
  466. this._renderFxaa(camera, toDrawingBuffer, props);
  467. } else if (props.antialiasing.name === 'smaa') {
  468. if (!this.smaa.supported) {
  469. throw new Error('SMAA not supported, missing "HTMLImageElement"');
  470. }
  471. this._renderSmaa(camera, toDrawingBuffer, props);
  472. }
  473. }
  474. }