isosurface.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /**
  2. * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ComputeRenderable, createComputeRenderable } from '../../renderable';
  7. import { WebGLContext } from '../../webgl/context';
  8. import { createComputeRenderItem } from '../../webgl/render-item';
  9. import { Values, TextureSpec, UniformSpec, DefineSpec } from '../../renderable/schema';
  10. import { Texture } from '../../../mol-gl/webgl/texture';
  11. import { ShaderCode } from '../../../mol-gl/shader-code';
  12. import { ValueCell } from '../../../mol-util';
  13. import { Vec3, Vec2, Mat4 } from '../../../mol-math/linear-algebra';
  14. import { QuadSchema, QuadValues } from '../util';
  15. import { createHistogramPyramid, HistogramPyramid } from '../histogram-pyramid/reduction';
  16. import { getTriIndices } from './tables';
  17. import { quad_vert } from '../../../mol-gl/shader/quad.vert';
  18. import { isosurface_frag } from '../../../mol-gl/shader/marching-cubes/isosurface.frag';
  19. import { calcActiveVoxels } from './active-voxels';
  20. import { isWebGL2 } from '../../webgl/compat';
  21. import { isTimingMode } from '../../../mol-util/debug';
  22. const IsosurfaceSchema = {
  23. ...QuadSchema,
  24. tTriIndices: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
  25. tActiveVoxelsPyramid: TextureSpec('texture', 'rgba', 'float', 'nearest'),
  26. tActiveVoxelsBase: TextureSpec('texture', 'rgba', 'float', 'nearest'),
  27. tVolumeData: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  28. uIsoValue: UniformSpec('f'),
  29. uSize: UniformSpec('f'),
  30. uLevels: UniformSpec('f'),
  31. uCount: UniformSpec('f'),
  32. uInvert: UniformSpec('b'),
  33. uGridDim: UniformSpec('v3'),
  34. uGridTexDim: UniformSpec('v3'),
  35. uGridTransform: UniformSpec('m4'),
  36. uScale: UniformSpec('v2'),
  37. dPackedGroup: DefineSpec('boolean'),
  38. dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
  39. dConstantGroup: DefineSpec('boolean'),
  40. };
  41. type IsosurfaceValues = Values<typeof IsosurfaceSchema>
  42. const IsosurfaceName = 'isosurface';
  43. function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean): ComputeRenderable<IsosurfaceValues> {
  44. if (ctx.namedComputeRenderables[IsosurfaceName]) {
  45. const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
  46. ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
  47. ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
  48. ValueCell.update(v.tVolumeData, volumeData);
  49. ValueCell.updateIfChanged(v.uIsoValue, isoValue);
  50. ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
  51. ValueCell.updateIfChanged(v.uLevels, levels);
  52. ValueCell.updateIfChanged(v.uCount, count);
  53. ValueCell.updateIfChanged(v.uInvert, invert);
  54. ValueCell.update(v.uGridDim, gridDim);
  55. ValueCell.update(v.uGridTexDim, gridTexDim);
  56. ValueCell.update(v.uGridTransform, transform);
  57. ValueCell.update(v.uScale, scale);
  58. ValueCell.updateIfChanged(v.dPackedGroup, packedGroup);
  59. ValueCell.updateIfChanged(v.dAxisOrder, axisOrder.join(''));
  60. ValueCell.updateIfChanged(v.dConstantGroup, constantGroup);
  61. ctx.namedComputeRenderables[IsosurfaceName].update();
  62. } else {
  63. ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
  64. }
  65. return ctx.namedComputeRenderables[IsosurfaceName];
  66. }
  67. function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean) {
  68. // console.log('uSize', Math.pow(2, levels))
  69. const values: IsosurfaceValues = {
  70. ...QuadValues,
  71. tTriIndices: ValueCell.create(getTriIndices()),
  72. tActiveVoxelsPyramid: ValueCell.create(activeVoxelsPyramid),
  73. tActiveVoxelsBase: ValueCell.create(activeVoxelsBase),
  74. tVolumeData: ValueCell.create(volumeData),
  75. uIsoValue: ValueCell.create(isoValue),
  76. uSize: ValueCell.create(Math.pow(2, levels)),
  77. uLevels: ValueCell.create(levels),
  78. uCount: ValueCell.create(count),
  79. uInvert: ValueCell.create(invert),
  80. uGridDim: ValueCell.create(gridDim),
  81. uGridTexDim: ValueCell.create(gridTexDim),
  82. uGridTransform: ValueCell.create(transform),
  83. uScale: ValueCell.create(scale),
  84. dPackedGroup: ValueCell.create(packedGroup),
  85. dAxisOrder: ValueCell.create(axisOrder.join('')),
  86. dConstantGroup: ValueCell.create(constantGroup),
  87. };
  88. const schema = { ...IsosurfaceSchema };
  89. const shaderCode = ShaderCode('isosurface', quad_vert, isosurface_frag, { drawBuffers: 'required' });
  90. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  91. return createComputeRenderable(renderItem, values);
  92. }
  93. function setRenderingDefaults(ctx: WebGLContext) {
  94. const { gl, state } = ctx;
  95. state.disable(gl.CULL_FACE);
  96. state.disable(gl.BLEND);
  97. state.disable(gl.DEPTH_TEST);
  98. state.disable(gl.SCISSOR_TEST);
  99. state.depthMask(false);
  100. state.colorMask(true, true, true, true);
  101. state.clearColor(0, 0, 0, 0);
  102. }
  103. export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
  104. const { drawBuffers } = ctx.extensions;
  105. if (!drawBuffers) throw new Error('need WebGL draw buffers');
  106. if (isTimingMode) ctx.timer.mark('createIsosurfaceBuffers');
  107. const { gl, resources, extensions } = ctx;
  108. const { pyramidTex, height, levels, scale, count } = histogramPyramid;
  109. const width = pyramidTex.getWidth();
  110. // console.log('width', width, 'height', height);
  111. // console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim);
  112. // console.log('iso volumeData', volumeData);
  113. if (!ctx.namedFramebuffers[IsosurfaceName]) {
  114. ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
  115. }
  116. const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
  117. if (isWebGL2(gl)) {
  118. if (!vertexTexture) {
  119. vertexTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
  120. ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
  121. : resources.texture('image-float32', 'rgba', 'float', 'nearest');
  122. }
  123. if (!groupTexture) {
  124. groupTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
  125. }
  126. if (!normalTexture) {
  127. normalTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
  128. ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
  129. : resources.texture('image-float32', 'rgba', 'float', 'nearest');
  130. }
  131. } else {
  132. // in webgl1 drawbuffers must be in the same format for some reason
  133. // this is quite wasteful but good enough for medium size meshes
  134. if (!vertexTexture) {
  135. vertexTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
  136. }
  137. if (!groupTexture) {
  138. groupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
  139. }
  140. if (!normalTexture) {
  141. normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
  142. }
  143. }
  144. vertexTexture.define(width, height);
  145. groupTexture.define(width, height);
  146. normalTexture.define(width, height);
  147. vertexTexture.attachFramebuffer(framebuffer, 0);
  148. groupTexture.attachFramebuffer(framebuffer, 1);
  149. normalTexture.attachFramebuffer(framebuffer, 2);
  150. const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
  151. ctx.state.currentRenderItemId = -1;
  152. framebuffer.bind();
  153. drawBuffers.drawBuffers([
  154. drawBuffers.COLOR_ATTACHMENT0,
  155. drawBuffers.COLOR_ATTACHMENT1,
  156. drawBuffers.COLOR_ATTACHMENT2,
  157. ]);
  158. setRenderingDefaults(ctx);
  159. gl.viewport(0, 0, width, height);
  160. gl.clear(gl.COLOR_BUFFER_BIT);
  161. renderable.render();
  162. gl.finish();
  163. if (isTimingMode) ctx.timer.markEnd('createIsosurfaceBuffers');
  164. return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
  165. }
  166. //
  167. /**
  168. * GPU isosurface extraction
  169. *
  170. * Algorithm from "High‐speed Marching Cubes using HistoPyramids"
  171. * by C Dyken, G Ziegler, C Theobalt, HP Seidel
  172. * https://doi.org/10.1111/j.1467-8659.2008.01182.x
  173. *
  174. * Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
  175. */
  176. export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
  177. if (isTimingMode) ctx.timer.mark('extractIsosurface');
  178. const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
  179. const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
  180. const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture);
  181. if (isTimingMode) ctx.timer.markEnd('extractIsosurface');
  182. return gv;
  183. }