diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index c92cdbf8d17acfe2fd536cb9b7b1afc21818fbb7..ea90276e73d925c6261b00dbffeceba0f8d5362e 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -10,6 +10,9 @@ import { RenderVariant, RenderItem } from './webgl/render-item'; import { Sphere3D } from 'mol-math/geometry'; import { Vec3 } from 'mol-math/linear-algebra'; import { ValueCell } from 'mol-util'; +import { idFactory } from 'mol-util/id-factory'; + +const getNextRenderableId = idFactory() export type RenderableState = { visible: boolean @@ -18,10 +21,12 @@ export type RenderableState = { } export interface Renderable<T extends RenderableValues> { + readonly id: number readonly values: T readonly state: RenderableState readonly boundingSphere: Sphere3D readonly invariantBoundingSphere: Sphere3D + readonly z: number render: (variant: RenderVariant) => void getProgram: (variant: RenderVariant) => Program @@ -34,6 +39,7 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem: const invariantBoundingSphere: Sphere3D = Sphere3D.create(Vec3.zero(), 0) return { + id: getNextRenderableId(), values, state, get boundingSphere () { @@ -48,6 +54,9 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem: } return invariantBoundingSphere }, + get z () { + return boundingSphere.center[2] + }, render: (variant: RenderVariant) => { if (values.uPickable) { diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index fca8f0fdba9e1ae075e7b5c02d7db87ad827574c..2c52cfc56844c3617533da5b6e72657a91ed9369 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -107,10 +107,9 @@ namespace Renderer { } let globalUniformsNeedUpdate = true - const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: RenderVariant, opaque: boolean) => { - if (r.state.opaque !== opaque) return + const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: RenderVariant) => { const program = r.getProgram(variant) - if (r.state.visible) { + if (r.state.visible) { if (ctx.currentProgramId !== program.id) { globalUniformsNeedUpdate = true } @@ -146,8 +145,6 @@ namespace Renderer { gl.cullFace(gl.BACK) } - gl.depthMask(r.state.opaque) - r.render(variant) } } @@ -170,16 +167,30 @@ namespace Renderer { const { renderables } = scene - gl.disable(gl.BLEND) - gl.enable(gl.DEPTH_TEST) - for (let i = 0, il = renderables.length; i < il; ++i) { - renderObject(renderables[i], variant, true) - } + if (variant === 'draw') { + gl.disable(gl.BLEND) + gl.enable(gl.DEPTH_TEST) + gl.depthMask(true) + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i] + if (r.state.opaque) renderObject(r, variant) + } - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) - gl.enable(gl.BLEND) - for (let i = 0, il = renderables.length; i < il; ++i) { - renderObject(renderables[i], variant, false) + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) + gl.enable(gl.BLEND) + gl.depthMask(false) + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i] + if (!r.state.opaque) renderObject(r, variant) + } + } else { + // picking + gl.disable(gl.BLEND) + gl.enable(gl.DEPTH_TEST) + gl.depthMask(true) + for (let i = 0, il = renderables.length; i < il; ++i) { + renderObject(renderables[i], variant) + } } gl.finish() diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 5e7b9a8ff291ccbbb3fb0b1398b95fe99f9d3e67..316a8e38650c4598c02fe638f045e6f7f9ec1dcc 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -35,6 +35,21 @@ function calculateBoundingSphere(renderables: Renderable<RenderableValues & Base return boundingSphere; } +function renderableSort(a: Renderable<any>, b: Renderable<any>) { + const drawProgramIdA = a.getProgram('draw').id + const drawProgramIdB = b.getProgram('draw').id + + if (drawProgramIdA !== drawProgramIdB) { + return drawProgramIdA - drawProgramIdB; // sort by program id to minimize gl state changes + } else if (a.z !== b.z) { + return a.state.opaque + ? a.z - b.z // when opaque draw closer elements first to minimize overdraw + : b.z - a.z // when transparent draw elements last to maximize partial visibility + } else { + return a.id - b.id; + } +} + interface Scene extends Object3D { readonly count: number readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>> @@ -70,11 +85,11 @@ namespace Scene { } if (!keepBoundingSphere) boundingSphereDirty = true }, - add: (o: RenderObject) => { if (!renderableMap.has(o)) { const renderable = createRenderable(ctx, o) renderables.push(renderable) + renderables.sort(renderableSort) renderableMap.set(o, renderable) boundingSphereDirty = true } else { @@ -86,6 +101,7 @@ namespace Scene { if (renderable) { renderable.dispose() renderables.splice(renderables.indexOf(renderable), 1) + renderables.sort(renderableSort) renderableMap.delete(o) boundingSphereDirty = true }