diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 75e995aac8d784997a3ef2ac9ddf3572b40f6835..c2b4a98c18aa10c5102ff7c01e399a3140d73a09 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -32,6 +32,7 @@ import { readTexture } from '../mol-gl/compute/util'; import { DrawPass } from './passes/draw'; import { PickPass } from './passes/pick'; import { Task } from '../mol-task'; +import { ImagePass, ImageProps } from './passes/image'; export const Canvas3DParams = { cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]), @@ -74,6 +75,7 @@ interface Canvas3D { downloadScreenshot: () => void getPixelData: (variant: GraphicsRenderVariant) => PixelData setProps: (props: Partial<Canvas3DProps>) => void + getImagePass: () => ImagePass /** Returns a copy of the current Canvas3D instance props */ readonly props: Readonly<Canvas3DProps> @@ -128,12 +130,12 @@ namespace Canvas3D { }) const controls = TrackballControls.create(input, camera, p.trackball) - const renderer = Renderer.create(webgl, camera, p.renderer) + const renderer = Renderer.create(webgl, p.renderer) const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug); const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input); - const drawPass = new DrawPass(webgl, renderer, scene, debugHelper) - const pickPass = new PickPass(webgl, renderer, scene, 0.5) + const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper) + const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5) const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing) const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample) @@ -177,6 +179,7 @@ namespace Canvas3D { let didRender = false controls.update(currentTime) + Viewport.set(camera.viewport, 0, 0, width, height) const cameraChanged = camera.update() multiSample.update(force || cameraChanged, currentTime) @@ -186,9 +189,9 @@ namespace Canvas3D { pickPass.render() break; case 'draw': - renderer.setViewport(0, 0, width, height); + renderer.setViewport(0, 0, width, height) if (multiSample.enabled) { - multiSample.render() + multiSample.render(true) } else { drawPass.render(!postprocessing.enabled) if (postprocessing.enabled) postprocessing.render(true) @@ -309,7 +312,7 @@ namespace Canvas3D { getLoci, handleResize, - resetCamera: (/*dir?: Vec3*/) => { + resetCamera: () => { if (scene.isCommiting) { cameraResetRequested = true } else { @@ -348,6 +351,9 @@ namespace Canvas3D { if (props.debug) debugHelper.setProps(props.debug) requestDraw(true) }, + getImagePass: (props: Partial<ImageProps> = {}) => { + return new ImagePass(webgl, renderer, scene, camera, debugHelper, props) + }, get props() { return { diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index a6a6bf3985e373c930a1a9c63340c30cfa329ada..b2f5a9477707150b45ac0152a8e9aa439d7db36e 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -10,6 +10,7 @@ import Renderer from '../../mol-gl/renderer'; import Scene from '../../mol-gl/scene'; import { BoundingSphereHelper } from '../helper/bounding-sphere-helper'; import { createTexture, Texture } from '../../mol-gl/webgl/texture'; +import { Camera } from '../camera'; export class DrawPass { colorTarget: RenderTarget @@ -18,11 +19,11 @@ export class DrawPass { private depthTarget: RenderTarget | null - constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private debugHelper: BoundingSphereHelper) { + constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) { const { gl, extensions } = webgl const width = gl.drawingBufferWidth const height = gl.drawingBufferHeight - this.colorTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight) + this.colorTarget = createRenderTarget(webgl, width, height) this.packedDepth = !extensions.depthTexture this.depthTarget = this.packedDepth ? createRenderTarget(webgl, width, height) : null this.depthTexture = this.depthTarget ? this.depthTarget.texture : createTexture(webgl, 'image-depth', 'depth', 'ushort', 'nearest') @@ -42,28 +43,28 @@ export class DrawPass { } render(toDrawingBuffer: boolean) { - const { webgl, renderer, scene, debugHelper, colorTarget, depthTarget } = this - const { gl } = webgl + const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this if (toDrawingBuffer) { webgl.unbindFramebuffer() } else { colorTarget.bind() } - renderer.setViewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight) - renderer.render(scene, 'color', true) + + renderer.setViewport(0, 0, colorTarget.width, colorTarget.height) + renderer.render(scene, camera, 'color', true) if (debugHelper.isEnabled) { debugHelper.syncVisibility() - renderer.render(debugHelper.scene, 'color', false) + renderer.render(debugHelper.scene, camera, 'color', false) } // 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() - renderer.render(scene, 'depth', true) + renderer.render(scene, camera, 'depth', true) if (debugHelper.isEnabled) { debugHelper.syncVisibility() - renderer.render(debugHelper.scene, 'depth', false) + renderer.render(debugHelper.scene, camera, 'depth', false) } } } diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts new file mode 100644 index 0000000000000000000000000000000000000000..dde52c99d4d39fc855bbf22ad0d016a16187872d --- /dev/null +++ b/src/mol-canvas3d/passes/image.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { WebGLContext } from '../../mol-gl/webgl/context'; +import { RenderTarget } from '../../mol-gl/webgl/render-target'; +import Renderer from '../../mol-gl/renderer'; +import Scene from '../../mol-gl/scene'; +import { BoundingSphereHelper } from '../helper/bounding-sphere-helper'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { DrawPass } from './draw' +import { PostprocessingPass, PostprocessingParams } from './postprocessing' +import { MultiSamplePass, MultiSampleParams } from './multi-sample' +import { Camera } from '../camera'; +import { Viewport } from '../camera/util'; + +export const ImageParams = { + multiSample: PD.Group(MultiSampleParams), + postprocessing: PD.Group(PostprocessingParams), +} +export type ImageProps = PD.Values<typeof ImageParams> + +export class ImagePass { + private _width = 1024 + private _height = 768 + private _camera = new Camera() + + private _colorTarget: RenderTarget + get colorTarget() { return this._colorTarget } + + readonly drawPass: DrawPass + private readonly postprocessing: PostprocessingPass + private readonly multiSample: MultiSamplePass + + get width() { return this._width } + get height() { return this._height } + + constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) { + const p = { ...PD.getDefaultValues(ImageParams), ...props } + + this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper) + this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing) + this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample) + + this.setSize(this._width, this._height) + } + + setSize(width: number, height: number) { + this._width = width + this._height = height + + this.drawPass.setSize(width, height) + this.postprocessing.setSize(width, height) + this.multiSample.setSize(width, height) + } + + setProps(props: Partial<ImageProps> = {}) { + if (props.postprocessing) this.postprocessing.setProps(props.postprocessing) + if (props.multiSample) this.multiSample.setProps(props.multiSample) + } + + render() { + Camera.copySnapshot(this._camera.state, this.camera.state) + Viewport.set(this._camera.viewport, 0, 0, this._width, this._height) + this._camera.update() + + this.renderer.setViewport(0, 0, this._width, this._height); + + if (this.multiSample.enabled) { + this.multiSample.render(false) + this._colorTarget = this.multiSample.colorTarget + } else { + this.drawPass.render(false) + if (this.postprocessing.enabled) { + this.postprocessing.render(false) + this._colorTarget = this.postprocessing.target + } else { + this._colorTarget = this.drawPass.colorTarget + } + } + } + + getImageData(width: number, height: number) { + this.setSize(width, height) + this.render() + const pd = this.colorTarget.getPixelData() + return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height) + } +} \ No newline at end of file diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts index ba907667123e1fb403a6b4c64b500e7af57e37b6..94dfd4cdaa94655dda9ebc665f7437c29582f4da 100644 --- a/src/mol-canvas3d/passes/multi-sample.ts +++ b/src/mol-canvas3d/passes/multi-sample.ts @@ -54,6 +54,7 @@ export type MultiSampleProps = PD.Values<typeof MultiSampleParams> export class MultiSamplePass { props: MultiSampleProps + colorTarget: RenderTarget private composeTarget: RenderTarget private holdTarget: RenderTarget @@ -65,6 +66,7 @@ export class MultiSamplePass { constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) { const { gl } = webgl + this.colorTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight) this.composeTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight) this.holdTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight) this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture) @@ -92,6 +94,7 @@ export class MultiSamplePass { } setSize(width: number, height: number) { + this.colorTarget.setSize(width, height) this.composeTarget.setSize(width, height) this.holdTarget.setSize(width, height) ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height)) @@ -102,15 +105,15 @@ export class MultiSamplePass { if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel } - render() { + render(toDrawingBuffer: boolean) { if (this.props.mode === 'temporal') { - this.renderTemporalMultiSample() + this.renderTemporalMultiSample(toDrawingBuffer) } else { - this.renderMultiSample() + this.renderMultiSample(toDrawingBuffer) } } - private renderMultiSample() { + private renderMultiSample(toDrawingBuffer: boolean) { const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this const { gl, state } = webgl @@ -168,7 +171,11 @@ export class MultiSamplePass { ValueCell.update(compose.values.tColor, composeTarget.texture) compose.update() - webgl.unbindFramebuffer() + if (toDrawingBuffer) { + webgl.unbindFramebuffer() + } else { + this.colorTarget.bind() + } gl.viewport(0, 0, width, height) state.disable(gl.BLEND) compose.render() @@ -177,7 +184,7 @@ export class MultiSamplePass { camera.update() } - private renderTemporalMultiSample() { + private renderTemporalMultiSample(toDrawingBuffer: boolean) { const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this const { gl, state } = webgl @@ -252,7 +259,11 @@ export class MultiSamplePass { ValueCell.update(compose.values.uWeight, 1.0) ValueCell.update(compose.values.tColor, composeTarget.texture) compose.update() - webgl.unbindFramebuffer() + if (toDrawingBuffer) { + webgl.unbindFramebuffer() + } else { + this.colorTarget.bind() + } gl.viewport(0, 0, width, height) state.disable(gl.BLEND) compose.render() @@ -261,7 +272,11 @@ export class MultiSamplePass { ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight) ValueCell.update(compose.values.tColor, holdTarget.texture) compose.update() - webgl.unbindFramebuffer() + if (toDrawingBuffer) { + webgl.unbindFramebuffer() + } else { + this.colorTarget.bind() + } gl.viewport(0, 0, width, height) if (accumulationWeight === 0) state.disable(gl.BLEND) else state.enable(gl.BLEND) diff --git a/src/mol-canvas3d/passes/pick.ts b/src/mol-canvas3d/passes/pick.ts index 59d332b8f155dcb4ff7b97a10374bb7975d374f9..28867455f11403050358b035d6ad8849653dff09 100644 --- a/src/mol-canvas3d/passes/pick.ts +++ b/src/mol-canvas3d/passes/pick.ts @@ -10,6 +10,7 @@ import Renderer from '../../mol-gl/renderer'; import Scene from '../../mol-gl/scene'; import { PickingId } from '../../mol-geo/geometry/picking'; import { decodeFloatRGB } from '../../mol-util/float-packing'; +import { Camera } from '../camera'; export class PickPass { pickDirty = true @@ -26,7 +27,7 @@ export class PickPass { private pickWidth: number private pickHeight: number - constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private pickBaseScale: number) { + constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private pickBaseScale: number) { const { gl } = webgl const width = gl.drawingBufferWidth const height = gl.drawingBufferHeight @@ -64,14 +65,14 @@ export class PickPass { } render() { - const { renderer, scene } = this + const { renderer, scene, camera } = this renderer.setViewport(0, 0, this.pickWidth, this.pickHeight); this.objectPickTarget.bind(); - renderer.render(scene, 'pickObject', true); + renderer.render(scene, camera, 'pickObject', true); this.instancePickTarget.bind(); - renderer.render(scene, 'pickInstance', true); + renderer.render(scene, camera, 'pickInstance', true); this.groupPickTarget.bind(); - renderer.render(scene, 'pickGroup', true); + renderer.render(scene, camera, 'pickGroup', true); this.pickDirty = false } diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index c632c17ccb82882123c6b1d8f25aa2dd4dcf5ddf..c6bc26503d683882b822491ed95018d4adb76982 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -30,7 +30,7 @@ function createRenderer(gl: WebGLRenderingContext) { const camera = new Camera({ position: Vec3.create(0, 0, 50) }) - const renderer = Renderer.create(ctx, camera) + const renderer = Renderer.create(ctx) return { ctx, camera, renderer } } diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index db06d23bd21387015f46c989a0b705b474dbf099..7c50c0de8d707bc06ab91968f5a47b885ec13c74 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -38,7 +38,7 @@ interface Renderer { readonly props: Readonly<RendererProps> clear: () => void - render: (scene: Scene, variant: GraphicsRenderVariant, clear: boolean) => void + render: (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean) => void setProps: (props: Partial<RendererProps>) => void setViewport: (x: number, y: number, width: number, height: number) => void dispose: () => void @@ -60,39 +60,40 @@ export const RendererParams = { export type RendererProps = PD.Values<typeof RendererParams> namespace Renderer { - export function create(ctx: WebGLContext, camera: Camera, props: Partial<RendererProps> = {}): Renderer { + export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer { const { gl, state, stats } = ctx const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props }) const viewport = Viewport() const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor) - const view = Mat4.clone(camera.view) - const invView = Mat4.invert(Mat4.identity(), view) - const modelView = Mat4.clone(camera.view) - const invModelView = Mat4.invert(Mat4.identity(), modelView) - const invProjection = Mat4.invert(Mat4.identity(), camera.projection) - const modelViewProjection = Mat4.mul(Mat4.identity(), modelView, camera.projection) - const invModelViewProjection = Mat4.invert(Mat4.identity(), modelViewProjection) + const view = Mat4() + const invView = Mat4() + const modelView = Mat4() + const invModelView = Mat4() + const invProjection = Mat4() + const modelViewProjection = Mat4() + const invModelViewProjection = Mat4() - const viewOffset = camera.viewOffset.enabled ? Vec2.create(camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2() + const viewOffset = Vec2() const globalUniforms: GlobalUniformValues = { uModel: ValueCell.create(Mat4.identity()), - uView: ValueCell.create(camera.view), + uView: ValueCell.create(view), uInvView: ValueCell.create(invView), uModelView: ValueCell.create(modelView), uInvModelView: ValueCell.create(invModelView), uInvProjection: ValueCell.create(invProjection), - uProjection: ValueCell.create(Mat4.clone(camera.projection)), + uProjection: ValueCell.create(Mat4()), uModelViewProjection: ValueCell.create(modelViewProjection), uInvModelViewProjection: ValueCell.create(invModelViewProjection), - uIsOrtho: ValueCell.create(camera.state.mode === 'orthographic' ? 1 : 0), + uIsOrtho: ValueCell.create(1), + uViewOffset: ValueCell.create(viewOffset), + uPixelRatio: ValueCell.create(ctx.pixelRatio), uViewportHeight: ValueCell.create(viewport.height), uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)), - uViewOffset: ValueCell.create(viewOffset), uLightIntensity: ValueCell.create(p.lightIntensity), uAmbientIntensity: ValueCell.create(p.ambientIntensity), @@ -101,11 +102,11 @@ namespace Renderer { uRoughness: ValueCell.create(p.roughness), uReflectivity: ValueCell.create(p.reflectivity), - uCameraPosition: ValueCell.create(Vec3.clone(camera.state.position)), - uNear: ValueCell.create(camera.near), - uFar: ValueCell.create(camera.far), - uFogNear: ValueCell.create(camera.fogNear), - uFogFar: ValueCell.create(camera.fogFar), + uCameraPosition: ValueCell.create(Vec3()), + uNear: ValueCell.create(1), + uFar: ValueCell.create(10000), + uFogNear: ValueCell.create(1), + uFogFar: ValueCell.create(10000), uFogColor: ValueCell.create(bgColor), uTransparentBackground: ValueCell.create(p.transparentBackground ? 1 : 0), @@ -160,7 +161,7 @@ namespace Renderer { } } - const render = (scene: Scene, variant: GraphicsRenderVariant, clear: boolean) => { + const render = (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean) => { ValueCell.update(globalUniforms.uModel, scene.view) ValueCell.update(globalUniforms.uView, camera.view) ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view))