diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index b9b20e81a7f7d871928a326a381ead3dbd73437b..1751c4c77ac72572360ff4af1cbe6c041509f532 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -255,8 +255,9 @@ namespace Canvas3D { if (multiSample.enabled) { multiSample.render(true, p.transparentBackground); } else { - drawPass.render(!postprocessing.enabled, p.transparentBackground); - if (postprocessing.enabled) postprocessing.render(true); + const toDrawingBuffer = !postprocessing.enabled && scene.volumes.renderables.length === 0; + drawPass.render(toDrawingBuffer, p.transparentBackground); + if (!toDrawingBuffer) postprocessing.render(true); } pickPass.pickDirty = true; didRender = true; diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 33dc0073d7f90d841e34e6001f4647dfceb80b99..67fec49ec519035d2ebf8538c092f85b24dac68f 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -14,6 +14,42 @@ import { Camera } from '../camera'; import { CameraHelper, CameraHelperParams } from '../helper/camera-helper'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { HandleHelper } from '../helper/handle-helper'; +import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; +import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema'; +import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable'; +import { ShaderCode } from '../../mol-gl/shader-code'; +import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; +import { ValueCell } from '../../mol-util'; +import { Vec2 } from '../../mol-math/linear-algebra'; + +import quad_vert from '../../mol-gl/shader/quad.vert'; +import depthMerge_frag from '../../mol-gl/shader/depth-merge.frag'; + +const DepthMergeSchema = { + ...QuadSchema, + tDepthPrimitives: TextureSpec('texture', 'depth', 'ushort', 'nearest'), + tDepthVolumes: TextureSpec('texture', 'depth', 'ushort', 'nearest'), + uTexSize: UniformSpec('v2'), + dPackedDepth: DefineSpec('boolean'), +}; + +type DepthMergeRenderable = ComputeRenderable<Values<typeof DepthMergeSchema>> + +function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Texture, depthTextureVolumes: Texture, packedDepth: boolean): DepthMergeRenderable { + const values: Values<typeof DepthMergeSchema> = { + ...QuadValues, + tDepthPrimitives: ValueCell.create(depthTexturePrimitives), + tDepthVolumes: ValueCell.create(depthTextureVolumes), + uTexSize: ValueCell.create(Vec2.create(depthTexturePrimitives.getWidth(), depthTexturePrimitives.getHeight())), + dPackedDepth: ValueCell.create(packedDepth), + }; + + const schema = { ...DepthMergeSchema }; + const shaderCode = ShaderCode('depth-merge', quad_vert, depthMerge_frag); + const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} export const DrawPassParams = { cameraHelper: PD.Group(CameraHelperParams) @@ -28,7 +64,12 @@ export class DrawPass { cameraHelper: CameraHelper - private depthTarget: RenderTarget | null + private depthTarget: RenderTarget + private depthTargetPrimitives: RenderTarget | null + private depthTargetVolumes: RenderTarget | null + private depthTexturePrimitives: Texture + private depthTextureVolumes: Texture + private depthMerge: DepthMergeRenderable constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, private handleHelper: HandleHelper, props: Partial<DrawPassProps> = {}) { const { gl, extensions, resources } = webgl; @@ -36,12 +77,20 @@ export class DrawPass { const height = gl.drawingBufferHeight; this.colorTarget = webgl.createRenderTarget(width, height); this.packedDepth = !extensions.depthTexture; - this.depthTarget = this.packedDepth ? webgl.createRenderTarget(width, height) : null; - this.depthTexture = this.depthTarget ? this.depthTarget.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest'); + + this.depthTarget = webgl.createRenderTarget(width, height); + this.depthTexture = this.depthTarget.texture; + + this.depthTargetPrimitives = this.packedDepth ? webgl.createRenderTarget(width, height) : null; + this.depthTargetVolumes = this.packedDepth ? webgl.createRenderTarget(width, height) : null; + + this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest'); + this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest'); if (!this.packedDepth) { - this.depthTexture.define(width, height); - this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + this.depthTexturePrimitives.define(width, height); + this.depthTextureVolumes.define(width, height); } + this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth); const p = { ...DefaultDrawPassProps, ...props }; this.cameraHelper = new CameraHelper(webgl, p.cameraHelper); @@ -49,11 +98,21 @@ export class DrawPass { setSize(width: number, height: number) { this.colorTarget.setSize(width, height); - if (this.depthTarget) { - this.depthTarget.setSize(width, height); + this.depthTarget.setSize(width, height); + + if (this.depthTargetPrimitives) { + this.depthTargetPrimitives.setSize(width, height); + } else { + this.depthTexturePrimitives.define(width, height); + } + + if (this.depthTargetVolumes) { + this.depthTargetVolumes.setSize(width, height); } else { - this.depthTexture.define(width, height); + this.depthTextureVolumes.define(width, height); } + + ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, width, height)); } setProps(props: Partial<DrawPassProps>) { @@ -67,41 +126,67 @@ export class DrawPass { } render(toDrawingBuffer: boolean, transparentBackground: boolean) { - const { webgl, renderer, colorTarget, depthTarget } = this; if (toDrawingBuffer) { - webgl.unbindFramebuffer(); + this.webgl.unbindFramebuffer(); } else { - colorTarget.bind(); + this.colorTarget.bind(); if (!this.packedDepth) { - // TODO unlcear why it is not enough to call `attachFramebuffer` in `Texture.reset` - this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); } } - renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight()); - this.renderInternal('color', transparentBackground); + this.renderer.setViewport(0, 0, this.colorTarget.getWidth(), this.colorTarget.getHeight()); + this.renderer.render(this.scene.primitives, this.camera, 'color', true, transparentBackground, null); // do a depth pass if not rendering to drawing buffer and // extensions.depthTexture is unsupported (i.e. depthTarget is set) - if (!toDrawingBuffer && depthTarget) { - depthTarget.bind(); - this.renderInternal('depth', transparentBackground); + if (!toDrawingBuffer && this.depthTargetPrimitives) { + this.depthTargetPrimitives.bind(); + this.renderer.render(this.scene.primitives, this.camera, 'depth', true, transparentBackground, null); + this.colorTarget.bind(); + } + + // do direct-volume rendering + if (!toDrawingBuffer && this.scene.volumes.renderables.length > 0) { + if (!this.packedDepth) { + this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + this.webgl.state.depthMask(true); + this.webgl.gl.clear(this.webgl.gl.DEPTH_BUFFER_BIT); + } + this.renderer.render(this.scene.volumes, this.camera, 'color', false, transparentBackground, this.depthTexturePrimitives); + + // do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set) + if (this.depthTargetVolumes) { + this.depthTargetVolumes.bind(); + this.renderer.render(this.scene.volumes, this.camera, 'depth', false, transparentBackground, this.depthTexturePrimitives); + this.colorTarget.bind(); + } + } + + // merge depths from primitive and volume rendering + if (!toDrawingBuffer) { + this.depthMerge.update(); + this.depthTarget.bind(); + this.webgl.state.disable(this.webgl.gl.SCISSOR_TEST); + this.webgl.state.disable(this.webgl.gl.BLEND); + this.webgl.state.disable(this.webgl.gl.DEPTH_TEST); + this.webgl.state.depthMask(false); + this.webgl.state.clearColor(1, 1, 1, 1); + this.webgl.gl.clear(this.webgl.gl.COLOR_BUFFER_BIT); + this.depthMerge.render(); + this.colorTarget.bind(); } - } - private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) { - const { renderer, scene, camera, debugHelper, cameraHelper, handleHelper } = this; - renderer.render(scene, camera, variant, true, transparentBackground); - if (debugHelper.isEnabled) { - debugHelper.syncVisibility(); - renderer.render(debugHelper.scene, camera, variant, false, transparentBackground); + if (this.debugHelper.isEnabled) { + this.debugHelper.syncVisibility(); + this.renderer.render(this.debugHelper.scene, this.camera, 'color', false, transparentBackground, null); } - if (handleHelper.isEnabled) { - renderer.render(handleHelper.scene, camera, variant, false, transparentBackground); + if (this.handleHelper.isEnabled) { + this.renderer.render(this.handleHelper.scene, this.camera, 'color', false, transparentBackground, null); } - if (cameraHelper.isEnabled) { - cameraHelper.update(camera); - renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground); + if (this.cameraHelper.isEnabled) { + this.cameraHelper.update(this.camera); + this.renderer.render(this.cameraHelper.scene, this.cameraHelper.camera, 'color', false, transparentBackground, null); } } } \ No newline at end of file diff --git a/src/mol-canvas3d/passes/pick.ts b/src/mol-canvas3d/passes/pick.ts index c64d20dfbafe6b5c5f9ee03da16fdab28c4722ce..425c07544ce563ce360a4107ad36c86ea709b31c 100644 --- a/src/mol-canvas3d/passes/pick.ts +++ b/src/mol-canvas3d/passes/pick.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -72,14 +72,14 @@ export class PickPass { renderer.setViewport(0, 0, this.pickWidth, this.pickHeight); this.objectPickTarget.bind(); - renderer.render(scene, camera, 'pickObject', true, false); - renderer.render(handleScene, camera, 'pickObject', false, false); + renderer.render(scene, camera, 'pickObject', true, false, null); + renderer.render(handleScene, camera, 'pickObject', false, false, null); this.instancePickTarget.bind(); - renderer.render(scene, camera, 'pickInstance', true, false); - renderer.render(handleScene, camera, 'pickInstance', false, false); + renderer.render(scene, camera, 'pickInstance', true, false, null); + renderer.render(handleScene, camera, 'pickInstance', false, false, null); this.groupPickTarget.bind(); - renderer.render(scene, camera, 'pickGroup', true, false); - renderer.render(handleScene, camera, 'pickGroup', false, false); + renderer.render(scene, camera, 'pickGroup', true, false, null); + renderer.render(handleScene, camera, 'pickGroup', false, false, null); this.pickDirty = false; } diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts index 86be9580946dcca972d9a70c3424a19c2368cfdd..fb4a34378ac2a1db78c2964ae720a52c9ddcf122 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/src/mol-canvas3d/passes/postprocessing.ts @@ -25,7 +25,7 @@ import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag'; const PostprocessingSchema = { ...QuadSchema, tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), - tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + tPackedDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'), uTexSize: UniformSpec('v2'), dOrthographic: DefineSpec('number'), @@ -43,8 +43,6 @@ const PostprocessingSchema = { dOutlineEnable: DefineSpec('boolean'), uOutlineScale: UniformSpec('f'), uOutlineThreshold: UniformSpec('f'), - - dPackedDepth: DefineSpec('boolean'), }; export const PostprocessingParams = { @@ -73,7 +71,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d const values: Values<typeof PostprocessingSchema> = { ...QuadValues, tColor: ValueCell.create(colorTexture), - tDepth: ValueCell.create(depthTexture), + tPackedDepth: ValueCell.create(depthTexture), uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())), dOrthographic: ValueCell.create(0), @@ -91,8 +89,6 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d dOutlineEnable: ValueCell.create(p.outline.name === 'on'), uOutlineScale: ValueCell.create((p.outline.name === 'on' ? p.outline.params.scale : 1) * ctx.pixelRatio), uOutlineThreshold: ValueCell.create(p.outline.name === 'on' ? p.outline.params.threshold : 0.8), - - dPackedDepth: ValueCell.create(packedDepth), }; const schema = { ...PostprocessingSchema }; diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index fe16fdd357702f9e853dce42f823e417f01dfc11..aa18b974b36a80e7962e514b2db55feb8fe7c534 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -42,15 +42,20 @@ export interface DirectVolume { readonly bboxMax: ValueCell<Vec3> readonly transform: ValueCell<Mat4> + readonly cellDim: ValueCell<Vec3> + readonly unitToCartn: ValueCell<Mat4> + readonly cartnToUnit: ValueCell<Mat4> + readonly packedGroup: ValueCell<boolean> + /** Bounding sphere of the volume */ - boundingSphere: Sphere3D + readonly boundingSphere: Sphere3D } export namespace DirectVolume { - export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, stats: Grid['stats'], directVolume?: DirectVolume): DirectVolume { + export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, directVolume?: DirectVolume): DirectVolume { return directVolume ? - update(bbox, gridDimension, transform, texture, stats, directVolume) : - fromData(bbox, gridDimension, transform, texture, stats); + update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, directVolume) : + fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup); } function hashCode(directVolume: DirectVolume) { @@ -61,7 +66,7 @@ export namespace DirectVolume { ]); } - function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, stats: Grid['stats']): DirectVolume { + function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean): DirectVolume { const boundingSphere = Sphere3D(); let currentHash = -1; @@ -77,8 +82,11 @@ export namespace DirectVolume { gridStats: ValueCell.create(Vec4.create(stats.min, stats.max, stats.mean, stats.sigma)), bboxMin: ValueCell.create(bbox.min), bboxMax: ValueCell.create(bbox.max), - bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)), + bboxSize: ValueCell.create(Vec3.sub(Vec3(), bbox.max, bbox.min)), transform: ValueCell.create(transform), + cellDim: ValueCell.create(cellDim), + unitToCartn: ValueCell.create(unitToCartn), + cartnToUnit: ValueCell.create(Mat4.invert(Mat4(), unitToCartn)), get boundingSphere() { const newHash = hashCode(directVolume); if (newHash !== currentHash) { @@ -88,11 +96,12 @@ export namespace DirectVolume { } return boundingSphere; }, + packedGroup: ValueCell.create(packedGroup) }; return directVolume; } - function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, stats: Grid['stats'], directVolume: DirectVolume): DirectVolume { + function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, directVolume: DirectVolume): DirectVolume { const width = texture.getWidth(); const height = texture.getHeight(); const depth = texture.getDepth(); @@ -105,6 +114,10 @@ export namespace DirectVolume { ValueCell.update(directVolume.bboxMax, bbox.max); ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min)); ValueCell.update(directVolume.transform, transform); + ValueCell.update(directVolume.cellDim, cellDim); + ValueCell.update(directVolume.unitToCartn, unitToCartn); + ValueCell.update(directVolume.cartnToUnit, Mat4.invert(Mat4(), unitToCartn)); + ValueCell.updateIfChanged(directVolume.packedGroup, packedGroup); return directVolume; } @@ -138,6 +151,7 @@ export namespace DirectVolume { flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory), ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory), renderMode: createRenderModeParam(), + stepsPerCell: PD.Numeric(5, { min: 1, max: 20, step: 1 }), }; export type Params = typeof Params @@ -182,7 +196,7 @@ export namespace DirectVolume { ? props.renderMode.params.isoValue : Volume.IsoValue.relative(2); - const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value) * 5); + const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value) * props.stepsPerCell); return { ...color, @@ -204,6 +218,7 @@ export namespace DirectVolume { uBboxMax: bboxMax, uBboxSize: bboxSize, uMaxSteps: ValueCell.create(maxSteps), + uStepFactor: ValueCell.create(1 / props.stepsPerCell), uTransform: gridTransform, uGridDim: gridDimension, dRenderMode: ValueCell.create(props.renderMode.name), @@ -214,6 +229,11 @@ export namespace DirectVolume { tGridTex: gridTexture, uGridStats: gridStats, + uCellDim: directVolume.cellDim, + uCartnToUnit: directVolume.cartnToUnit, + uUnitToCartn: directVolume.unitToCartn, + dPackedGroup: directVolume.packedGroup, + dDoubleSided: ValueCell.create(false), dFlatShaded: ValueCell.create(props.flatShaded), dFlipSided: ValueCell.create(true), @@ -242,6 +262,10 @@ export namespace DirectVolume { const controlPoints = getControlPointsFromVec2Array(props.renderMode.params.controlPoints); createTransferFunctionTexture(controlPoints, props.renderMode.params.list.colors, values.tTransferTex); } + + const maxSteps = Math.ceil(Vec3.magnitude(values.uGridDim.ref.value) * props.stepsPerCell); + ValueCell.updateIfChanged(values.uMaxSteps, maxSteps); + ValueCell.updateIfChanged(values.uStepFactor, 1 / props.stepsPerCell); } function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) { @@ -260,6 +284,7 @@ export namespace DirectVolume { function createRenderableState(props: PD.Values<Params>): RenderableState { const state = BaseGeometry.createRenderableState(props); state.opaque = false; + state.writeDepth = props.renderMode.name === 'isosurface'; return state; } diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 7e5e34af040738177698447a49256313bd95572d..729e4f0e582938b54fbf76e09dba442d702af4e3 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable'; import { WebGLContext } from '../webgl/context'; import { createGraphicsRenderItem } from '../webgl/render-item'; -import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema, TextureSpec, ValueSpec, ElementsSpec, DefineSpec, InternalValues } from './schema'; +import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema, TextureSpec, ValueSpec, ElementsSpec, DefineSpec, InternalValues, GlobalTextureSchema } from './schema'; import { DirectVolumeShaderCode } from '../shader-code'; import { ValueCell } from '../../mol-util'; @@ -65,6 +65,7 @@ export const DirectVolumeSchema = { uBboxMax: UniformSpec('v3'), uBboxSize: UniformSpec('v3'), uMaxSteps: UniformSpec('i'), + uStepFactor: UniformSpec('f'), uTransform: UniformSpec('m4'), uGridDim: UniformSpec('v3'), dRenderMode: DefineSpec('string', ['isosurface', 'volume']), @@ -75,6 +76,11 @@ export const DirectVolumeSchema = { tGridTex: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), uGridStats: UniformSpec('v4'), // [min, max, mean, sigma] + uCellDim: UniformSpec('v3'), + uCartnToUnit: UniformSpec('m4'), + uUnitToCartn: UniformSpec('m4'), + dPackedGroup: DefineSpec('boolean'), + dDoubleSided: DefineSpec('boolean'), dFlipSided: DefineSpec('boolean'), dFlatShaded: DefineSpec('boolean'), @@ -84,7 +90,7 @@ export type DirectVolumeSchema = typeof DirectVolumeSchema export type DirectVolumeValues = Values<DirectVolumeSchema> export function DirectVolumeRenderable(ctx: WebGLContext, id: number, values: DirectVolumeValues, state: RenderableState, materialId: number): Renderable<DirectVolumeValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolumeSchema }; + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...DirectVolumeSchema }; if (!ctx.isWebGL2) { // workaround for webgl1 limitation that loop counters need to be `const` (schema.uMaxSteps as any) = DefineSpec('number'); diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index eaadd52e73772c32c88c3abee32b9b7820c0a287..c35193cf00e83e23fa4e6909f697a2122da75579 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -40,8 +40,9 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues) const spec = schema[k]; if (spec.type === 'attribute') attributeValues[k] = values[k]; if (spec.type === 'define') defineValues[k] = values[k]; - if (spec.type === 'texture') textureValues[k] = values[k]; - // check if k exists in values so that global uniforms are excluded here + // check if k exists in values to exclude global textures + if (spec.type === 'texture' && values[k] !== undefined) textureValues[k] = values[k]; + // check if k exists in values to exclude global uniforms if (spec.type === 'uniform' && values[k] !== undefined) { if (spec.isMaterial) materialUniformValues[k] = values[k]; else uniformValues[k] = values[k]; @@ -121,6 +122,7 @@ export const GlobalUniformSchema = { uViewOffset: UniformSpec('v2'), uCameraPosition: UniformSpec('v3'), + uCameraDir: UniformSpec('v3'), uNear: UniformSpec('f'), uFar: UniformSpec('f'), uFogNear: UniformSpec('f'), @@ -154,13 +156,19 @@ export const GlobalUniformSchema = { uSelectColor: UniformSpec('v3'), } as const; export type GlobalUniformSchema = typeof GlobalUniformSchema -export type GlobalUniformValues = Values<GlobalUniformSchema> // { [k in keyof GlobalUniformSchema]: ValueCell<any> } +export type GlobalUniformValues = Values<GlobalUniformSchema> + +export const GlobalTextureSchema = { + tDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'), +} as const; +export type GlobalTextureSchema = typeof GlobalTextureSchema +export type GlobalTextureValues = Values<GlobalTextureSchema> export const InternalSchema = { uObjectId: UniformSpec('i'), } as const; export type InternalSchema = typeof InternalSchema -export type InternalValues = { [k in keyof InternalSchema]: ValueCell<any> } +export type InternalValues = Values<InternalSchema> export const ColorSchema = { // aColor: AttributeSpec('float32', 3, 0), // TODO diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 5359479a545b3ab0fdd921ba1244862dea0283d6..c563f2eb2d1b29789c5763a07b9004b6722d1afe 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -20,6 +20,7 @@ import { Clipping } from '../mol-theme/clipping'; import { stringToWords } from '../mol-util/string'; import { Transparency } from '../mol-theme/transparency'; import { degToRad } from '../mol-math/misc'; +import { Texture } from './webgl/texture'; export interface RendererStats { programCount: number @@ -42,7 +43,7 @@ interface Renderer { readonly props: Readonly<RendererProps> clear: (transparentBackground: boolean) => void - render: (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean) => void + render: (group: Scene.Group, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, depthTexture: Texture | null) => void setProps: (props: Partial<RendererProps>) => void setViewport: (x: number, y: number, width: number, height: number) => void dispose: () => void @@ -174,6 +175,7 @@ namespace Renderer { const modelViewProjection = Mat4(); const invModelViewProjection = Mat4(); + const cameraDir = Vec3(); const viewOffset = Vec2(); const globalUniforms: GlobalUniformValues = { @@ -195,6 +197,7 @@ namespace Renderer { uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)), uCameraPosition: ValueCell.create(Vec3()), + uCameraDir: ValueCell.create(cameraDir), uNear: ValueCell.create(1), uFar: ValueCell.create(10000), uFogNear: ValueCell.create(1), @@ -228,7 +231,7 @@ namespace Renderer { let globalUniformsNeedUpdate = true; - const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant) => { + const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant, depthTexture: Texture | null) => { if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) { return; } @@ -261,6 +264,8 @@ namespace Renderer { globalUniformsNeedUpdate = false; } + if (depthTexture) program.bindTextures([['tDepth', depthTexture]]); + if (r.values.dDoubleSided) { if (r.values.dDoubleSided.ref.value || r.values.hasReflection.ref.value) { state.disable(gl.CULL_FACE); @@ -293,11 +298,11 @@ namespace Renderer { r.render(variant); }; - const render = (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean) => { - ValueCell.update(globalUniforms.uModel, scene.view); + const render = (group: Scene.Group, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, depthTexture: Texture | null) => { + ValueCell.update(globalUniforms.uModel, group.view); ValueCell.update(globalUniforms.uView, camera.view); ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view)); - ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, scene.view, camera.view)); + ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view)); ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView)); ValueCell.update(globalUniforms.uProjection, camera.projection); ValueCell.update(globalUniforms.uInvProjection, Mat4.invert(invProjection, camera.projection)); @@ -308,6 +313,8 @@ namespace Renderer { ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0)); ValueCell.update(globalUniforms.uCameraPosition, camera.state.position); + ValueCell.update(globalUniforms.uCameraDir, Vec3.normalize(cameraDir, Vec3.sub(cameraDir, camera.state.target, camera.state.position))); + ValueCell.update(globalUniforms.uFar, camera.far); ValueCell.update(globalUniforms.uNear, camera.near); ValueCell.update(globalUniforms.uFogFar, camera.fogFar); @@ -318,7 +325,7 @@ namespace Renderer { globalUniformsNeedUpdate = true; state.currentRenderItemId = -1; - const { renderables } = scene; + const { renderables } = group; state.disable(gl.SCISSOR_TEST); state.disable(gl.BLEND); @@ -338,22 +345,28 @@ namespace Renderer { if (variant === 'color') { for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - if (r.state.opaque) renderObject(r, variant); + if (r.state.opaque) { + renderObject(r, variant, depthTexture); + } } state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); state.enable(gl.BLEND); for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - if (!r.state.opaque && r.state.writeDepth) renderObject(r, variant); + if (!r.state.opaque && r.state.writeDepth) { + renderObject(r, variant, depthTexture); + } } for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - if (!r.state.opaque && !r.state.writeDepth) renderObject(r, variant); + if (!r.state.opaque && !r.state.writeDepth) { + renderObject(r, variant, depthTexture); + } } } else { // picking & depth for (let i = 0, il = renderables.length; i < il; ++i) { - renderObject(renderables[i], variant); + renderObject(renderables[i], variant, depthTexture); } } diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 980e90618a21be7efa8cd7a0754cbbaf6164f2a0..4838b4724878237b5f8ae91421e57d8ff8d3b0df 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -66,6 +66,9 @@ interface Scene extends Object3D { readonly boundingSphere: Sphere3D readonly boundingSphereVisible: Sphere3D + readonly primitives: Scene.Group + readonly volumes: Scene.Group + /** Returns `true` if some visibility has changed, `false` otherwise. */ syncVisibility: () => boolean update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void @@ -79,21 +82,34 @@ interface Scene extends Object3D { } namespace Scene { + export interface Group extends Object3D { + readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>> + } + export function create(ctx: WebGLContext): Scene { const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>(); const renderables: Renderable<RenderableValues & BaseValues>[] = []; const boundingSphere = Sphere3D(); const boundingSphereVisible = Sphere3D(); + const primitives: Renderable<RenderableValues & BaseValues>[] = []; + const volumes: Renderable<RenderableValues & BaseValues>[] = []; + let boundingSphereDirty = true; let boundingSphereVisibleDirty = true; const object3d = Object3D.create(); + const { view, position, direction, up } = object3d; function add(o: GraphicsRenderObject) { if (!renderableMap.has(o)) { const renderable = createRenderable(ctx, o); renderables.push(renderable); + if (o.type === 'direct-volume') { + volumes.push(renderable); + } else { + primitives.push(renderable); + } renderableMap.set(o, renderable); boundingSphereDirty = true; boundingSphereVisibleDirty = true; @@ -109,6 +125,8 @@ namespace Scene { if (renderable) { renderable.dispose(); arraySetRemove(renderables, renderable); + arraySetRemove(primitives, renderable); + arraySetRemove(volumes, renderable); renderableMap.delete(o); boundingSphereDirty = true; boundingSphereVisibleDirty = true; @@ -164,10 +182,11 @@ namespace Scene { } return { - get view () { return object3d.view; }, - get position () { return object3d.position; }, - get direction () { return object3d.direction; }, - get up () { return object3d.up; }, + view, position, direction, up, + + renderables, + primitives: { view, position, direction, up, renderables: primitives }, + volumes: { view, position, direction, up, renderables: volumes }, syncVisibility, update(objects, keepBoundingSphere) { @@ -212,7 +231,6 @@ namespace Scene { get count() { return renderables.length; }, - renderables, get boundingSphere() { if (boundingSphereDirty) { calculateBoundingSphere(renderables, boundingSphere, false); diff --git a/src/mol-gl/shader/chunks/apply-fog.glsl.ts b/src/mol-gl/shader/chunks/apply-fog.glsl.ts index f77354be3e2c4cf8bade029d16e6fd9659e28b92..5045fcf0ac739b137df13888ffc0f4f6a95a9585 100644 --- a/src/mol-gl/shader/chunks/apply-fog.glsl.ts +++ b/src/mol-gl/shader/chunks/apply-fog.glsl.ts @@ -1,6 +1,6 @@ export default ` -float depth = length(vViewPosition); -float fogFactor = smoothstep(uFogNear, uFogFar, depth); +float fogDepth = length(vViewPosition); +float fogFactor = smoothstep(uFogNear, uFogFar, fogDepth); float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a; if (uTransparentBackground == 0) { gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor); diff --git a/src/mol-gl/shader/chunks/common.glsl.ts b/src/mol-gl/shader/chunks/common.glsl.ts index 4156d6230df0f882125dd93c087e182a96af0732..965f1a1d1a09ca47f51c7543e3bb8e40e2c97d1b 100644 --- a/src/mol-gl/shader/chunks/common.glsl.ts +++ b/src/mol-gl/shader/chunks/common.glsl.ts @@ -67,6 +67,10 @@ vec4 linearTosRGB(const in vec4 c) { return vec4(mix(pow(c.rgb, vec3(0.41666)) * 1.055 - vec3(0.055), c.rgb * 12.92, vec3(lessThanEqual(c.rgb, vec3(0.0031308)))), c.a); } +float linearizeDepth(in float depth, in float near, in float far) { + return (2.0 * near) / (far + near - depth * (far - near)); +} + #if __VERSION__ != 300 // transpose diff --git a/src/mol-gl/shader/depth-merge.frag.ts b/src/mol-gl/shader/depth-merge.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..2eb9ee381e2080a2ff83766a8115bb6b51c3c412 --- /dev/null +++ b/src/mol-gl/shader/depth-merge.frag.ts @@ -0,0 +1,24 @@ +export default ` +precision highp float; +precision highp sampler2D; + +uniform sampler2D tDepthPrimitives; +uniform sampler2D tDepthVolumes; +uniform vec2 uTexSize; + +#include common + +float getDepth(const in vec2 coords, sampler2D tDepth) { + #ifdef dPackedDepth + return unpackRGBAToDepth(texture2D(tDepth, coords)); + #else + return texture2D(tDepth, coords).r; + #endif +} + +void main() { + vec2 coords = gl_FragCoord.xy / uTexSize; + float depth = min(getDepth(coords, tDepthPrimitives), getDepth(coords, tDepthVolumes)); + gl_FragColor = packDepthToRGBA(depth); +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.frag.ts b/src/mol-gl/shader/direct-volume.frag.ts index e2571db4e623736dd75c0c5c44a9a9bb91d7d7b5..7540037cdc96181a447f1e7525b1a9b4507cb3a9 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/src/mol-gl/shader/direct-volume.frag.ts @@ -17,6 +17,12 @@ precision highp int; #include texture3d_from_2d_linear uniform mat4 uProjection, uTransform, uModelView, uView; +uniform vec3 uCameraDir; + +uniform sampler2D tDepth; +uniform vec4 uViewport; +uniform float uNear; +uniform float uFar; varying vec3 unitCoord; varying vec3 origPos; @@ -26,6 +32,7 @@ uniform mat4 uInvView; uniform vec2 uIsoValue; uniform vec3 uGridDim; uniform sampler2D tTransferTex; +uniform float uStepFactor; uniform int uObjectId; uniform int uInstanceCount; @@ -51,6 +58,11 @@ bool interior; uniform float uIsOrtho; +uniform vec3 uCellDim; +uniform vec3 uCameraPosition; +uniform mat4 uCartnToUnit; +uniform mat4 uUnitToCartn; + #if __VERSION__ == 300 // for webgl1 this is given as a 'define' uniform int uMaxSteps; @@ -107,61 +119,71 @@ float calcDepth(const in vec3 cameraPos){ return 0.5 + 0.5 * clipZW.x / clipZW.y; } +float getDepth(const in vec2 coords) { + #ifdef dPackedDepth + return unpackRGBAToDepth(texture2D(tDepth, coords)); + #else + return texture2D(tDepth, coords).r; + #endif +} + const float gradOffset = 0.5; -vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir, vec3 rayDir) { +vec3 toUnit(vec3 p) { + return (uCartnToUnit * vec4(p, 1.0)).xyz; +} + +vec4 raymarch(vec3 startLoc, vec3 step) { vec3 scaleVol = vec3(1.0) / uGridDim; vec3 pos = startLoc; + vec4 cell; + vec4 prevCell = vec4(-1); float prevValue = -1.0; float value = 0.0; vec4 src = vec4(0.0); vec4 dst = vec4(0.0); bool hit = false; - // float count = 0.0; vec3 posMin = vec3(0.0); vec3 posMax = vec3(1.0) - vec3(1.0) / uGridDim; - #if defined(dRenderMode_isosurface) - vec3 isoPos; - float tmp; + vec3 unitPos; + vec3 isoPos; - vec3 color = vec3(0.45, 0.55, 0.8); - vec3 gradient = vec3(1.0); - vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0); - vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0); - vec3 dz = vec3(0.0, 0.0, gradOffset * scaleVol.z); - #endif + vec3 color = vec3(0.45, 0.55, 0.8); + vec3 gradient = vec3(1.0); + vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0); + vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0); + vec3 dz = vec3(0.0, 0.0, gradOffset * scaleVol.z); for(int i = 0; i < uMaxSteps; ++i){ - value = textureVal(pos).a; // current voxel value - // if(pos.x > 1.01 || pos.y > 1.01 || pos.z > 1.01 || pos.x < -0.01 || pos.y < -0.01 || pos.z < -0.01) - // break; - - if(pos.x > posMax.x || pos.y > posMax.y || pos.z > posMax.z || pos.x < posMin.x || pos.y < posMin.y || pos.z < posMin.z) { + unitPos = toUnit(pos); + if(unitPos.x > posMax.x || unitPos.y > posMax.y || unitPos.z > posMax.z || unitPos.x < posMin.x || unitPos.y < posMin.y || unitPos.z < posMin.z) { + if (hit) break; prevValue = value; + prevCell = cell; pos += step; continue; } - #if defined(dRenderMode_volume) - src = transferFunction(value); - src.rgb *= src.a; - dst = (1.0 - dst.a) * src + dst; // standard blending - #endif + cell = textureVal(unitPos); + value = cell.a; // current voxel value #if defined(dRenderMode_isosurface) if(prevValue > 0.0 && ( // there was a prev Value (prevValue < uIsoValue.x && value > uIsoValue.x) || // entering isosurface (prevValue > uIsoValue.x && value < uIsoValue.x) // leaving isosurface )) { - tmp = ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x))); - isoPos = mix(pos - step, pos, tmp); + isoPos = toUnit(mix(pos - step, pos, ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x))))); vec4 mvPosition = uModelView * uTransform * vec4(isoPos * uGridDim, 1.0); + float depth = calcDepth(mvPosition.xyz); + if (depth > getDepth(gl_FragCoord.xy / uViewport.zw)) + break; + #ifdef enabledFragDepth if (!hit) { - gl_FragDepthEXT = calcDepth(mvPosition.xyz); + gl_FragDepthEXT = depth; hit = true; } #endif @@ -171,7 +193,12 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir, vec3 rayDir) { #elif defined(dRenderVariant_pickInstance) return vec4(encodeFloatRGB(instance), 1.0); #elif defined(dRenderVariant_pickGroup) - return vec4(textureGroup(isoPos).rgb, 1.0); + #ifdef dPackedGroup + return vec4(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb, 1.0); + #else + vec3 g = floor(isoPos * uGridDim + 0.5); + return vec4(encodeFloatRGB(g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y), 1.0); + #endif #elif defined(dRenderVariant_depth) #ifdef enabledFragDepth return packDepthToRGBA(gl_FragDepthEXT); @@ -179,7 +206,12 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir, vec3 rayDir) { return packDepthToRGBA(gl_FragCoord.z); #endif #elif defined(dRenderVariant_color) - float group = floor(decodeFloatRGB(textureGroup(isoPos).rgb) + 0.5); + #ifdef dPackedGroup + float group = decodeFloatRGB(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb); + #else + vec3 g = floor(isoPos * uGridDim + 0.5); + float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y; + #endif #if defined(dColorType_instance) color = readFromTexture(tColor, instance, uColorTexDim).rgb; @@ -203,10 +235,14 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir, vec3 rayDir) { // nearest grid point isoPos = floor(isoPos * uGridDim + 0.5) / uGridDim; #endif - // compute gradient by central differences - gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a; - gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a; - gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a; + #ifdef dPackedGroup + // compute gradient by central differences + gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a; + gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a; + gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a; + #else + gradient = textureVal(isoPos).xyz * 2.0 - 1.0; + #endif mat3 normalMatrix = transpose3(inverse3(mat3(uModelView))); vec3 normal = -normalize(normalMatrix * normalize(gradient)); normal = normal * (float(flipped) * 2.0 - 1.0); @@ -222,30 +258,60 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir, vec3 rayDir) { src.rgb = gl_FragColor.rgb; src.a = gl_FragColor.a; - // count += 1.0; src.rgb *= src.a; dst = (1.0 - dst.a) * src + dst; // standard blending - // dst.rgb = vec3(1.0, 0.0, 0.0) * (count / 20.0); - dst.a = min(1.0, dst.a); - if(dst.a >= 1.0) { - // dst.rgb = vec3(1.0, 0.0, 0.0); - break; - } #endif } prevValue = value; #endif + #if defined(dRenderMode_volume) + isoPos = toUnit(pos); + vec4 mvPosition = uModelView * uTransform * vec4(isoPos * uGridDim, 1.0); + if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uViewport.zw)) + break; + + // bool flipped = value > uIsoValue.y; // negative isosurfaces + // interior = value < uIsoValue.x && flipped; + vec3 vViewPosition = mvPosition.xyz; + vec4 material = transferFunction(value); + src.a = material.a * uAlpha; + + if (material.a >= 0.01) { + #ifdef dPackedGroup + // compute gradient by central differences + gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a; + gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a; + gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a; + #else + gradient = cell.xyz * 2.0 - 1.0; + #endif + mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * uUnitToCartn))); + vec3 normal = -normalize(normalMatrix * normalize(gradient)); + // normal = normal * (float(flipped) * 2.0 - 1.0); + // normal = normal * -(float(interior) * 2.0 - 1.0); + #include apply_light_color + src.rgb = gl_FragColor.rgb; + } else { + src.rgb = material.rgb; + } + + src.rgb *= src.a; + dst = (1.0 - dst.a) * src + dst; // standard blending + #endif + + // break if the color is opaque enough + if (dst.a > 0.95) + break; + pos += step; } return dst; } // TODO calculate normalMatrix on CPU -// TODO fix orthographic projection // TODO fix near/far clipping // TODO support clip objects -// TODO check and combine with pre-rendererd opaque texture // TODO support float texture for higher precision values void main () { @@ -258,18 +324,16 @@ void main () { discard; // ignore so the element below can be picked #endif #endif - // gl_FragColor = vec4(1.0, 0.0, 0.0, uAlpha); vec3 cameraPos = uInvView[3].xyz / uInvView[3].w; + vec3 rayDir = mix(normalize(origPos - uCameraPosition), uCameraDir, uIsOrtho);; - vec3 rayDir = normalize(origPos - cameraPos); - vec3 step = rayDir * (1.0 / uGridDim) * 0.2; - vec3 startLoc = unitCoord; // - step * float(uMaxSteps); + // TODO: set the scale as uniform? + float stepScale = min(uCellDim.x, min(uCellDim.y, uCellDim.z)) * uStepFactor; + vec3 step = rayDir * stepScale; - gl_FragColor = raymarch(startLoc, step, normalize(cameraPos), rayDir); - if (length(gl_FragColor.rgb) < 0.00001) discard; - #if defined(dRenderMode_volume) - gl_FragColor.a *= uAlpha; - #endif + gl_FragColor = raymarch(origPos, step); + if (length(gl_FragColor.rgb) < 0.00001) + discard; } `; \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.vert.ts b/src/mol-gl/shader/direct-volume.vert.ts index bf2361a6b99fbc7a7d3be8310f86de3f82b4d9b5..370e2cb053f873a165a39af9202b5ef2c04a45a0 100644 --- a/src/mol-gl/shader/direct-volume.vert.ts +++ b/src/mol-gl/shader/direct-volume.vert.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Michael Krone <michael.krone@uni-tuebingen.de> @@ -22,13 +22,16 @@ uniform vec3 uBboxMax; uniform vec3 uGridDim; uniform mat4 uTransform; +uniform mat4 uUnitToCartn; +uniform mat4 uCartnToUnit; + uniform mat4 uModelView; uniform mat4 uProjection; void main() { unitCoord = aPosition + vec3(0.5); - vec4 mvPosition = uModelView * uTransform * vec4(unitCoord * uGridDim, 1.0); - origPos = unitCoord * uBboxSize + uBboxMin; + vec4 mvPosition = uModelView * uUnitToCartn * vec4(unitCoord, 1.0); + origPos = (uUnitToCartn * vec4(unitCoord, 1.0)).xyz; instance = aInstance; gl_Position = uProjection * mvPosition; diff --git a/src/mol-gl/shader/lines.vert.ts b/src/mol-gl/shader/lines.vert.ts index 5bbe0c42485706dd20872a260edacf3bcea898d7..f2fd212fe07b2d4ab9fac6b4b65ad10eaf2c5b75 100644 --- a/src/mol-gl/shader/lines.vert.ts +++ b/src/mol-gl/shader/lines.vert.ts @@ -32,8 +32,8 @@ attribute vec3 aEnd; void trimSegment(const in vec4 start, inout vec4 end) { // trim end segment so it terminates between the camera plane and the near plane // conservative estimate of the near plane - float a = uProjection[2][2]; // 3nd entry in 3th column - float b = uProjection[3][2]; // 3nd entry in 4th column + float a = uProjection[2][2]; // 3rd entry in 3rd column + float b = uProjection[3][2]; // 3rd entry in 4th column float nearEstimate = -0.5 * b / a; float alpha = (nearEstimate - start.z) / (end.z - start.z); end.xyz = mix(start.xyz, end.xyz, alpha); diff --git a/src/mol-gl/shader/postprocessing.frag.ts b/src/mol-gl/shader/postprocessing.frag.ts index a75180878859fe7f1a9ef153b530cd96191b5b37..04d2864cfa23fb87280c970c2ca764a13db04064 100644 --- a/src/mol-gl/shader/postprocessing.frag.ts +++ b/src/mol-gl/shader/postprocessing.frag.ts @@ -4,7 +4,7 @@ precision highp int; precision highp sampler2D; uniform sampler2D tColor; -uniform sampler2D tDepth; +uniform sampler2D tPackedDepth; uniform vec2 uTexSize; uniform float uNear; @@ -25,52 +25,48 @@ const vec4 occlusionColor = vec4(0.0, 0.0, 0.0, 1.0); #include common float noise(const in vec2 coords) { - float a = 12.9898; - float b = 78.233; - float c = 43758.5453; - float dt = dot(coords, vec2(a,b)); - float sn = mod(dt, 3.14159); + float a = 12.9898; + float b = 78.233; + float c = 43758.5453; + float dt = dot(coords, vec2(a,b)); + float sn = mod(dt, 3.14159); - return fract(sin(sn) * c); + return fract(sin(sn) * c); } float perspectiveDepthToViewZ(const in float invClipZ, const in float near, const in float far) { - return (near * far) / ((far - near) * invClipZ - far); + return (near * far) / ((far - near) * invClipZ - far); } float orthographicDepthToViewZ(const in float linearClipZ, const in float near, const in float far) { - return linearClipZ * (near - far) - near; + return linearClipZ * (near - far) - near; } float getViewZ(const in float depth) { - #if dOrthographic == 1 - return orthographicDepthToViewZ(depth, uNear, uFar); - #else - return perspectiveDepthToViewZ(depth, uNear, uFar); - #endif + #if dOrthographic == 1 + return orthographicDepthToViewZ(depth, uNear, uFar); + #else + return perspectiveDepthToViewZ(depth, uNear, uFar); + #endif } float getDepth(const in vec2 coords) { - #ifdef dPackedDepth - return unpackRGBAToDepth(texture2D(tDepth, coords)); - #else - return texture2D(tDepth, coords).r; - #endif + return unpackRGBAToDepth(texture2D(tPackedDepth, coords)); } float calcSSAO(const in vec2 coords, const in float depth) { - float occlusionFactor = 0.0; + float occlusionFactor = 0.0; - for (int i = -dOcclusionKernelSize; i <= dOcclusionKernelSize; i++) { - for (int j = -dOcclusionKernelSize; j <= dOcclusionKernelSize; j++) { - vec2 coordsDelta = coords + uOcclusionRadius / float(dOcclusionKernelSize) * vec2(float(i) / uTexSize.x, float(j) / uTexSize.y); + for (int i = -dOcclusionKernelSize; i <= dOcclusionKernelSize; i++) { + for (int j = -dOcclusionKernelSize; j <= dOcclusionKernelSize; j++) { + vec2 coordsDelta = coords + uOcclusionRadius / float(dOcclusionKernelSize) * vec2(float(i) / uTexSize.x, float(j) / uTexSize.y); coordsDelta += noiseAmount * (noise(coordsDelta) - 0.5) / uTexSize; coordsDelta = clamp(coordsDelta, 0.5 / uTexSize, 1.0 - 1.0 / uTexSize); - if (getDepth(coordsDelta) < depth) occlusionFactor += 1.0; - } - } + if (getDepth(coordsDelta) < depth) occlusionFactor += 1.0; + } + } - return occlusionFactor / float((2 * dOcclusionKernelSize + 1) * (2 * dOcclusionKernelSize + 1)); + return occlusionFactor / float((2 * dOcclusionKernelSize + 1) * (2 * dOcclusionKernelSize + 1)); } vec2 calcEdgeDepth(const in vec2 coords) { @@ -92,34 +88,34 @@ vec2 calcEdgeDepth(const in vec2 coords) { float depthFiniteDifference1 = depth3 - depth2; return vec2( - sqrt(pow(depthFiniteDifference0, 2.0) + pow(depthFiniteDifference1, 2.0)) * 100.0, - min(depth0, min(depth1, min(depth2, depth3))) - ); + sqrt(pow(depthFiniteDifference0, 2.0) + pow(depthFiniteDifference1, 2.0)) * 100.0, + min(depth0, min(depth1, min(depth2, depth3))) + ); } void main(void) { - vec2 coords = gl_FragCoord.xy / uTexSize; - vec4 color = texture2D(tColor, coords); - - #ifdef dOutlineEnable - vec2 edgeDepth = calcEdgeDepth(coords); - float edgeFlag = step(edgeDepth.x, uOutlineThreshold); - color.rgb *= edgeFlag; - - float viewDist = abs(getViewZ(edgeDepth.y)); - float fogFactor = smoothstep(uFogNear, uFogFar, viewDist) * (1.0 - edgeFlag); - color.rgb = mix(color.rgb, uFogColor, fogFactor); - #endif - - // occlusion needs to be handled after outline to darken them properly - #ifdef dOcclusionEnable - float depth = getDepth(coords); - if (depth != 1.0) { - float occlusionFactor = calcSSAO(coords, depth); - color = mix(color, occlusionColor, uOcclusionBias * occlusionFactor); - } - #endif - - gl_FragColor = color; + vec2 coords = gl_FragCoord.xy / uTexSize; + vec4 color = texture2D(tColor, coords); + + #ifdef dOutlineEnable + vec2 edgeDepth = calcEdgeDepth(coords); + float edgeFlag = step(edgeDepth.x, uOutlineThreshold); + color.rgb *= edgeFlag; + + float viewDist = abs(getViewZ(edgeDepth.y)); + float fogFactor = smoothstep(uFogNear, uFogFar, viewDist) * (1.0 - edgeFlag); + color.rgb = mix(color.rgb, uFogColor, fogFactor); + #endif + + // occlusion needs to be handled after outline to darken them properly + #ifdef dOcclusionEnable + float depth = getDepth(coords); + if (depth <= 0.99) { + float occlusionFactor = calcSSAO(coords, depth); + color = mix(color, occlusionColor, uOcclusionBias * occlusionFactor); + } + #endif + + gl_FragColor = color; } `; \ No newline at end of file diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts index 8f67d8ce062f3a1d8a1231cf4095467619c742b4..b968e5dd69c530108ca6788a24befbdc4cc30592 100644 --- a/src/mol-gl/webgl/program.ts +++ b/src/mol-gl/webgl/program.ts @@ -200,11 +200,17 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi for (let i = 0, il = textures.length; i < il; ++i) { const [k, texture] = textures[i]; const l = locations[k]; - if (l !== null) { - // TODO if the order and count of textures in a material can be made invariant - // bind needs to be called only when the material changes - texture.bind(i as TextureId); - uniformSetters[k](gl, l, i as TextureId); + if (l !== null && l !== undefined) { + if (k === 'tDepth') { + // TODO find more explicit way? + texture.bind(15 as TextureId); + uniformSetters[k](gl, l, 15 as TextureId); + } else { + // TODO if the order and count of textures in a material can be made invariant + // bind needs to be called only when the material changes + texture.bind(i as TextureId); + uniformSetters[k](gl, l, i as TextureId); + } } } }, diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 3552738dd0278ea54f9550d53954a6b9d4937f78..f0f8205a37ec7f1f9e11a40dead7812942784eef 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -154,12 +154,6 @@ export type TextureId = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 1 export type TextureValues = { [k: string]: ValueCell<TextureValueType> } export type Textures = [string, Texture][] -type FramebufferAttachment = { - framebuffer: Framebuffer - attachment: TextureAttachment - layer?: number -} - function getTexture(gl: GLRenderingContext) { const texture = gl.createTexture(); if (texture === null) { @@ -167,7 +161,7 @@ function getTexture(gl: GLRenderingContext) { } return texture; } -// export type TextureProps = { kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter } + export function createTexture(gl: GLRenderingContext, extensions: WebGLExtensions, kind: TextureKind, _format: TextureFormat, _type: TextureType, _filter: TextureFilter): Texture { const id = getNextTextureId(); let texture = getTexture(gl); @@ -198,8 +192,6 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension } init(); - let fba: undefined | FramebufferAttachment = undefined; - let width = 0, height = 0, depth = 0; let loadedData: undefined | TextureImage<any> | TextureVolume<any>; let destroyed = false; @@ -237,9 +229,6 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension } function attachFramebuffer(framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) { - if (fba && fba.framebuffer === framebuffer && fba.attachment === attachment && fba.layer === layer) { - return; - } framebuffer.bind(); if (target === gl.TEXTURE_2D) { gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), gl.TEXTURE_2D, texture, 0); @@ -249,7 +238,6 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension } else { throw new Error('unknown texture target'); } - fba = { framebuffer, attachment, layer }; } return { @@ -283,7 +271,6 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension } else { throw new Error('unknown texture target'); } - fba = undefined; }, reset: () => { texture = getTexture(gl); @@ -294,12 +281,6 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension } else { define(width, height, depth); } - - if (fba) { - // TODO unclear why calling `attachFramebuffer` here does not work reliably after context loss - // e.g. it still needs to be called in `DrawPass` to work - fba = undefined; - } }, destroy: () => { if (destroyed) return; @@ -315,12 +296,15 @@ export function createTextures(ctx: WebGLContext, schema: RenderableSchema, valu Object.keys(schema).forEach(k => { const spec = schema[k]; if (spec.type === 'texture') { - if (spec.kind === 'texture') { - textures[textures.length] = [k, values[k].ref.value as Texture]; - } else { - const texture = resources.texture(spec.kind, spec.format, spec.dataType, spec.filter); - texture.load(values[k].ref.value as TextureImage<any> | TextureVolume<any>); - textures[textures.length] = [k, texture]; + const value = values[k]; + if (value) { + if (spec.kind === 'texture') { + textures[textures.length] = [k, value.ref.value as Texture]; + } else { + const texture = resources.texture(spec.kind, spec.format, spec.dataType, spec.filter); + texture.load(value.ref.value as TextureImage<any> | TextureVolume<any>); + textures[textures.length] = [k, texture]; + } } } }); diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts index f3e6783e37d365c1ea0096455acebde9276b24c8..abc415c5923c7d2635099253a890a0218a57d882 100644 --- a/src/mol-gl/webgl/uniform.ts +++ b/src/mol-gl/webgl/uniform.ts @@ -71,7 +71,7 @@ export function getUniformSetters(schema: RenderableSchema) { Object.keys(schema).forEach(k => { const spec = schema[k]; if (spec.type === 'uniform') { - setters[k] = getUniformSetter(spec.kind as UniformKind); + setters[k] = getUniformSetter(spec.kind); } else if (spec.type === 'texture') { setters[k] = getUniformSetter('t'); } diff --git a/src/mol-repr/structure/visual/gaussian-density-volume.ts b/src/mol-repr/structure/visual/gaussian-density-volume.ts index 1344fbeff25493c42094caaca0199b99712df493..cdb7e5e66f129a86a4dc75d6a2838466a6f06446 100644 --- a/src/mol-repr/structure/visual/gaussian-density-volume.ts +++ b/src/mol-repr/structure/visual/gaussian-density-volume.ts @@ -11,10 +11,9 @@ import { Theme } from '../../../mol-theme/theme'; import { GaussianDensityTextureProps, computeStructureGaussianDensityTexture, GaussianDensityTextureParams } from './util/gaussian'; import { DirectVolume } from '../../../mol-geo/geometry/direct-volume/direct-volume'; import { ComplexDirectVolumeParams, ComplexVisual, ComplexDirectVolumeVisual } from '../complex-visual'; -import { LocationIterator } from '../../../mol-geo/util/location-iterator'; -import { NullLocation } from '../../../mol-model/location'; -import { EmptyLoci } from '../../../mol-model/loci'; import { VisualUpdateState } from '../../util'; +import { Mat4, Vec3 } from '../../../mol-math/linear-algebra'; +import { eachSerialElement, ElementIterator, getSerialElementLoci } from './util/element'; async function createGaussianDensityVolume(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityTextureProps, directVolume?: DirectVolume): Promise<DirectVolume> { const { runtime, webgl } = ctx; @@ -26,7 +25,10 @@ async function createGaussianDensityVolume(ctx: VisualContext, structure: Struct const { transform, texture, bbox, gridDim } = densityTextureData; const stats = { min: 0, max: 1, mean: 0.5, sigma: 0.1 }; - return DirectVolume.create(bbox, gridDim, transform, texture, stats, directVolume); + const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim)); + const cellDim = Vec3.create(1, 1, 1); + + return DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, directVolume); } export const GaussianDensityVolumeParams = { @@ -40,9 +42,9 @@ export function GaussianDensityVolumeVisual(materialId: number): ComplexVisual<G return ComplexDirectVolumeVisual<GaussianDensityVolumeParams>({ defaultProps: PD.getDefaultValues(GaussianDensityVolumeParams), createGeometry: createGaussianDensityVolume, - createLocationIterator: (structure: Structure) => LocationIterator(structure.elementCount, 1, () => NullLocation), - getLoci: () => EmptyLoci, // TODO - eachLocation: () => false, // TODO + createLocationIterator: ElementIterator.fromStructure, + getLoci: getSerialElementLoci, + eachLocation: eachSerialElement, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<GaussianDensityVolumeParams>, currentProps: PD.Values<GaussianDensityVolumeParams>) => { if (newProps.resolution !== currentProps.resolution) state.createGeometry = true; if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true; diff --git a/src/mol-repr/volume/direct-volume.ts b/src/mol-repr/volume/direct-volume.ts index 736fbe203bcbd3c2afaeb86875db1ec82b9c71fd..33073f7e5dfc9fdd06701943dedcaf716ff7d020 100644 --- a/src/mol-repr/volume/direct-volume.ts +++ b/src/mol-repr/volume/direct-volume.ts @@ -22,10 +22,9 @@ import { Interval } from '../../mol-data/int'; import { Loci, EmptyLoci } from '../../mol-model/loci'; import { PickingId } from '../../mol-geo/geometry/picking'; import { eachVolumeLoci } from './util'; -import { encodeFloatRGBtoArray } from '../../mol-util/float-packing'; function getBoundingBox(gridDimension: Vec3, transform: Mat4) { - const bbox = Box3D.setEmpty(Box3D()); + const bbox = Box3D(); Box3D.add(bbox, gridDimension); Box3D.transform(bbox, bbox, transform); return bbox; @@ -54,7 +53,7 @@ function getVolumeTexture2dLayout(dim: Vec3, maxTextureSize: number) { function createVolumeTexture2d(volume: Volume, maxTextureSize: number) { const { cells: { space, data }, stats: { max, min } } = volume.grid; const dim = space.dimensions as Vec3; - const { dataOffset } = space; + const { dataOffset: o } = space; const { width, height } = getVolumeTexture2dLayout(dim, maxTextureSize); const array = new Uint8Array(width * height * 4); @@ -65,6 +64,10 @@ function createVolumeTexture2d(volume: Volume, maxTextureSize: number) { const xlp = xl + 1; // horizontal padding const ylp = yl + 1; // vertical padding + const n0 = Vec3(); + const n1 = Vec3(); + + let i = 0; for (let z = 0; z < zl; ++z) { for (let y = 0; y < yl; ++y) { for (let x = 0; x < xl; ++x) { @@ -72,9 +75,16 @@ function createVolumeTexture2d(volume: Volume, maxTextureSize: number) { const row = Math.floor((z * xlp) / width); const px = column * xlp + x; const index = 4 * ((row * ylp * width) + (y * width) + px); - const offset = dataOffset(x, y, z); - encodeFloatRGBtoArray(offset, array, index); + const offset = o(x, y, z); + + Vec3.set(n0, data[o(x - 1, y, z)], data[o(x, y - 1, z)], data[o(x, y, z - 1)]); + Vec3.set(n1, data[o(x + 1, y, z)], data[o(x, y + 1, z)], data[o(x, y, z + 1)]); + Vec3.normalize(n0, Vec3.sub(n0, n0, n1)); + Vec3.addScalar(n0, Vec3.scale(n0, n0, 0.5), 0.5); + Vec3.toArray(Vec3.scale(n0, n0, 255), array, i); + array[index + 3] = ((data[offset] - min) / diff) * 255; + i += 4; } } } @@ -95,7 +105,8 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); texture.load(textureImage); - return DirectVolume.create(bbox, dim, transform, texture, volume.grid.stats, directVolume); + const { unitToCartn, cellDim } = getUnitToCartn(volume.grid); + return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, directVolume); } // 3d volume texture @@ -103,18 +114,27 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v function createVolumeTexture3d(volume: Volume) { const { cells: { space, data }, stats: { max, min } } = volume.grid; const [ width, height, depth ] = space.dimensions as Vec3; - const { dataOffset } = space; + const { dataOffset: o } = space; const array = new Uint8Array(width * height * depth * 4); const textureVolume = { array, width, height, depth }; const diff = max - min; + const n0 = Vec3(); + const n1 = Vec3(); + let i = 0; for (let z = 0; z < depth; ++z) { for (let y = 0; y < height; ++y) { for (let x = 0; x < width; ++x) { - const offset = dataOffset(x, y, z); - encodeFloatRGBtoArray(offset, array, i); + const offset = o(x, y, z); + + Vec3.set(n0, data[o(x - 1, y, z)], data[o(x, y - 1, z)], data[o(x, y, z - 1)]); + Vec3.set(n1, data[o(x + 1, y, z)], data[o(x, y + 1, z)], data[o(x, y, z + 1)]); + Vec3.normalize(n0, Vec3.sub(n0, n0, n1)); + Vec3.addScalar(n0, Vec3.scale(n0, n0, 0.5), 0.5); + Vec3.toArray(Vec3.scale(n0, n0, 255), array, i); + array[i + 3] = ((data[offset] - min) / diff) * 255; i += 4; } @@ -124,6 +144,27 @@ function createVolumeTexture3d(volume: Volume) { return textureVolume; } +function getUnitToCartn(grid: Grid) { + if (grid.transform.kind === 'matrix') { + // TODO: + return { + unitToCartn: Mat4.mul(Mat4(), + Grid.getGridToCartesianTransform(grid), + Mat4.fromScaling(Mat4(), grid.cells.space.dimensions as Vec3)), + cellDim: Vec3.create(1, 1, 1) + }; + } + const box = grid.transform.fractionalBox; + const size = Box3D.size(Vec3(), box); + return { + unitToCartn: Mat4.mul3(Mat4(), + grid.transform.cell.fromFractional, + Mat4.fromTranslation(Mat4(), box.min), + Mat4.fromScaling(Mat4(), size)), + cellDim: Vec3.div(Vec3(), grid.transform.cell.size, grid.cells.space.dimensions as Vec3) + }; +} + export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) { const gridDimension = volume.grid.cells.space.dimensions as Vec3; const textureVolume = createVolumeTexture3d(volume); @@ -133,7 +174,8 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear'); texture.load(textureVolume); - return DirectVolume.create(bbox, gridDimension, transform, texture, volume.grid.stats, directVolume); + const { unitToCartn, cellDim } = getUnitToCartn(volume.grid); + return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, directVolume); } //