marking.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /**
  2. * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
  7. import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
  8. import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
  9. import { ShaderCode } from '../../mol-gl/shader-code';
  10. import { WebGLContext } from '../../mol-gl/webgl/context';
  11. import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
  12. import { Texture } from '../../mol-gl/webgl/texture';
  13. import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
  14. import { ValueCell } from '../../mol-util';
  15. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  16. import { quad_vert } from '../../mol-gl/shader/quad.vert';
  17. import { overlay_frag } from '../../mol-gl/shader/marking/overlay.frag';
  18. import { Viewport } from '../camera/util';
  19. import { RenderTarget } from '../../mol-gl/webgl/render-target';
  20. import { Color } from '../../mol-util/color';
  21. import { edge_frag } from '../../mol-gl/shader/marking/edge.frag';
  22. import { isTimingMode } from '../../mol-util/debug';
  23. export const MarkingParams = {
  24. enabled: PD.Boolean(true),
  25. highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)),
  26. selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)),
  27. edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }),
  28. ghostEdgeStrength: PD.Numeric(0.3, { min: 0, max: 1, step: 0.1 }, { description: 'Opacity of the hidden edges that are covered by other geometry. When set to 1, one less geometry render pass is done.' }),
  29. innerEdgeFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }, { description: 'Factor to multiply the inner edge color with - for added contrast.' }),
  30. };
  31. export type MarkingProps = PD.Values<typeof MarkingParams>
  32. export class MarkingPass {
  33. static isEnabled(props: MarkingProps) {
  34. return props.enabled;
  35. }
  36. readonly depthTarget: RenderTarget;
  37. readonly maskTarget: RenderTarget;
  38. private readonly edgesTarget: RenderTarget;
  39. private readonly edge: EdgeRenderable;
  40. private readonly overlay: OverlayRenderable;
  41. constructor(private webgl: WebGLContext, width: number, height: number) {
  42. this.depthTarget = webgl.createRenderTarget(width, height);
  43. this.maskTarget = webgl.createRenderTarget(width, height);
  44. this.edgesTarget = webgl.createRenderTarget(width, height);
  45. this.edge = getEdgeRenderable(webgl, this.maskTarget.texture);
  46. this.overlay = getOverlayRenderable(webgl, this.edgesTarget.texture);
  47. }
  48. private setEdgeState(viewport: Viewport) {
  49. const { gl, state } = this.webgl;
  50. state.enable(gl.SCISSOR_TEST);
  51. state.enable(gl.BLEND);
  52. state.blendFunc(gl.ONE, gl.ONE);
  53. state.blendEquation(gl.FUNC_ADD);
  54. state.disable(gl.DEPTH_TEST);
  55. state.depthMask(false);
  56. const { x, y, width, height } = viewport;
  57. state.viewport(x, y, width, height);
  58. state.scissor(x, y, width, height);
  59. state.clearColor(0, 0, 0, 0);
  60. gl.clear(gl.COLOR_BUFFER_BIT);
  61. }
  62. private setOverlayState(viewport: Viewport) {
  63. const { gl, state } = this.webgl;
  64. state.enable(gl.SCISSOR_TEST);
  65. state.enable(gl.BLEND);
  66. state.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  67. state.blendEquation(gl.FUNC_ADD);
  68. state.disable(gl.DEPTH_TEST);
  69. state.depthMask(false);
  70. const { x, y, width, height } = viewport;
  71. state.viewport(x, y, width, height);
  72. state.scissor(x, y, width, height);
  73. }
  74. setSize(width: number, height: number) {
  75. const w = this.depthTarget.getWidth();
  76. const h = this.depthTarget.getHeight();
  77. if (width !== w || height !== h) {
  78. this.depthTarget.setSize(width, height);
  79. this.maskTarget.setSize(width, height);
  80. this.edgesTarget.setSize(width, height);
  81. ValueCell.update(this.edge.values.uTexSizeInv, Vec2.set(this.edge.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
  82. ValueCell.update(this.overlay.values.uTexSizeInv, Vec2.set(this.overlay.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
  83. }
  84. }
  85. update(props: MarkingProps) {
  86. const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength } = props;
  87. const { values: edgeValues } = this.edge;
  88. const _edgeScale = Math.round(edgeScale * this.webgl.pixelRatio);
  89. if (edgeValues.dEdgeScale.ref.value !== _edgeScale) {
  90. ValueCell.update(edgeValues.dEdgeScale, _edgeScale);
  91. this.edge.update();
  92. }
  93. const { values: overlayValues } = this.overlay;
  94. ValueCell.update(overlayValues.uHighlightEdgeColor, Color.toVec3Normalized(overlayValues.uHighlightEdgeColor.ref.value, highlightEdgeColor));
  95. ValueCell.update(overlayValues.uSelectEdgeColor, Color.toVec3Normalized(overlayValues.uSelectEdgeColor.ref.value, selectEdgeColor));
  96. ValueCell.update(overlayValues.uInnerEdgeFactor, innerEdgeFactor);
  97. ValueCell.update(overlayValues.uGhostEdgeStrength, ghostEdgeStrength);
  98. }
  99. render(viewport: Viewport, target: RenderTarget | undefined) {
  100. if (isTimingMode) this.webgl.timer.mark('MarkingPass.render');
  101. this.edgesTarget.bind();
  102. this.setEdgeState(viewport);
  103. this.edge.render();
  104. if (target) {
  105. target.bind();
  106. } else {
  107. this.webgl.unbindFramebuffer();
  108. }
  109. this.setOverlayState(viewport);
  110. this.overlay.render();
  111. if (isTimingMode) this.webgl.timer.markEnd('MarkingPass.render');
  112. }
  113. }
  114. //
  115. const EdgeSchema = {
  116. ...QuadSchema,
  117. tMaskTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
  118. uTexSizeInv: UniformSpec('v2'),
  119. dEdgeScale: DefineSpec('number'),
  120. };
  121. const EdgeShaderCode = ShaderCode('edge', quad_vert, edge_frag);
  122. type EdgeRenderable = ComputeRenderable<Values<typeof EdgeSchema>>
  123. function getEdgeRenderable(ctx: WebGLContext, maskTexture: Texture): EdgeRenderable {
  124. const width = maskTexture.getWidth();
  125. const height = maskTexture.getHeight();
  126. const values: Values<typeof EdgeSchema> = {
  127. ...QuadValues,
  128. tMaskTexture: ValueCell.create(maskTexture),
  129. uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
  130. dEdgeScale: ValueCell.create(1),
  131. };
  132. const schema = { ...EdgeSchema };
  133. const renderItem = createComputeRenderItem(ctx, 'triangles', EdgeShaderCode, schema, values);
  134. return createComputeRenderable(renderItem, values);
  135. }
  136. //
  137. const OverlaySchema = {
  138. ...QuadSchema,
  139. tEdgeTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
  140. uTexSizeInv: UniformSpec('v2'),
  141. uHighlightEdgeColor: UniformSpec('v3'),
  142. uSelectEdgeColor: UniformSpec('v3'),
  143. uGhostEdgeStrength: UniformSpec('f'),
  144. uInnerEdgeFactor: UniformSpec('f'),
  145. };
  146. const OverlayShaderCode = ShaderCode('overlay', quad_vert, overlay_frag);
  147. type OverlayRenderable = ComputeRenderable<Values<typeof OverlaySchema>>
  148. function getOverlayRenderable(ctx: WebGLContext, edgeTexture: Texture): OverlayRenderable {
  149. const width = edgeTexture.getWidth();
  150. const height = edgeTexture.getHeight();
  151. const values: Values<typeof OverlaySchema> = {
  152. ...QuadValues,
  153. tEdgeTexture: ValueCell.create(edgeTexture),
  154. uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
  155. uHighlightEdgeColor: ValueCell.create(Vec3()),
  156. uSelectEdgeColor: ValueCell.create(Vec3()),
  157. uGhostEdgeStrength: ValueCell.create(0),
  158. uInnerEdgeFactor: ValueCell.create(0),
  159. };
  160. const schema = { ...OverlaySchema };
  161. const renderItem = createComputeRenderItem(ctx, 'triangles', OverlayShaderCode, schema, values);
  162. return createComputeRenderable(renderItem, values);
  163. }