UNPKG

molstar

Version:

A comprehensive macromolecular library.

429 lines (428 loc) 24.4 kB
"use strict"; /** * Copyright (c) 2024-2025 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TracingPass = exports.TracingParams = void 0; const util_1 = require("../../mol-gl/compute/util"); const schema_1 = require("../../mol-gl/renderable/schema"); const mol_util_1 = require("../../mol-util"); const debug_1 = require("../../mol-util/debug"); const shader_code_1 = require("../../mol-gl/shader-code"); const quad_vert_1 = require("../../mol-gl/shader/quad.vert"); const renderable_1 = require("../../mol-gl/renderable"); const trace_frag_1 = require("../../mol-gl/shader/illumination/trace.frag"); const vec2_1 = require("../../mol-math/linear-algebra/3d/vec2"); const render_item_1 = require("../../mol-gl/webgl/render-item"); const mat4_1 = require("../../mol-math/linear-algebra/3d/mat4"); const vec4_1 = require("../../mol-math/linear-algebra/3d/vec4"); const vec3_1 = require("../../mol-math/linear-algebra/3d/vec3"); const param_definition_1 = require("../../mol-util/param-definition"); const color_1 = require("../../mol-util/color/color"); const accumulate_frag_1 = require("../../mol-gl/shader/illumination/accumulate.frag"); const now_1 = require("../../mol-util/now"); const interpolate_1 = require("../../mol-math/interpolate"); exports.TracingParams = { rendersPerFrame: param_definition_1.ParamDefinition.Interval([1, 16], { min: 1, max: 64, step: 1 }, { description: 'Number of rays per pixel each frame. May be adjusted to reach targetFps but will stay within given interval.' }), targetFps: param_definition_1.ParamDefinition.Numeric(30, { min: 0, max: 120, step: 0.1 }, { description: 'Target FPS per frame. If observed FPS is lower or higher, some parameters may get adjusted.' }), steps: param_definition_1.ParamDefinition.Numeric(32, { min: 1, max: 1024, step: 1 }), firstStepSize: param_definition_1.ParamDefinition.Numeric(0.01, { min: 0.001, max: 1, step: 0.001 }), refineSteps: param_definition_1.ParamDefinition.Numeric(4, { min: 0, max: 8, step: 1 }, { description: 'Number of refine steps per ray hit. May be lower to reach targetFps.' }), rayDistance: param_definition_1.ParamDefinition.Numeric(256, { min: 1, max: 8192, step: 1 }, { description: 'Maximum distance a ray can travel (in world units).' }), thicknessMode: param_definition_1.ParamDefinition.Select('auto', param_definition_1.ParamDefinition.arrayToOptions(['auto', 'fixed'])), minThickness: param_definition_1.ParamDefinition.Numeric(0.5, { min: 0.1, max: 16, step: 0.1 }, { hideIf: p => p.thicknessMode === 'fixed' }), thicknessFactor: param_definition_1.ParamDefinition.Numeric(1, { min: 0.1, max: 2, step: 0.05 }, { hideIf: p => p.thicknessMode === 'fixed' }), thickness: param_definition_1.ParamDefinition.Numeric(4, { min: 0.1, max: 512, step: 0.1 }, { hideIf: p => p.thicknessMode === 'auto' }), bounces: param_definition_1.ParamDefinition.Numeric(4, { min: 1, max: 32, step: 1 }, { description: 'Number of bounces for each ray.' }), glow: param_definition_1.ParamDefinition.Boolean(true, { description: 'Bounced rays always get the full light. This produces a slight glowing effect.' }), shadowEnable: param_definition_1.ParamDefinition.Boolean(false), shadowSoftness: param_definition_1.ParamDefinition.Numeric(0.1, { min: 0.01, max: 1.0, step: 0.01 }), shadowThickness: param_definition_1.ParamDefinition.Numeric(0.5, { min: 0.1, max: 32, step: 0.1 }), }; class TracingPass { constructor(webgl, drawPass) { this.webgl = webgl; this.drawPass = drawPass; this.clearAdjustedProps = true; this.prevTime = 0; this.currTime = 0; this.rendersPerFrame = 1; this.refineSteps = 1; this.steps = 16; const { extensions: { drawBuffers, colorBufferHalfFloat, textureHalfFloat }, resources, isWebGL2 } = webgl; const { depthTextureOpaque } = drawPass; const width = depthTextureOpaque.getWidth(); const height = depthTextureOpaque.getHeight(); if (isWebGL2) { this.shadedTextureOpaque = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); this.shadedTextureOpaque.define(width, height); this.normalTextureOpaque = colorBufferHalfFloat && textureHalfFloat ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest') : resources.texture('image-float32', 'rgba', 'float', 'nearest'); this.normalTextureOpaque.define(width, height); this.colorTextureOpaque = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); this.colorTextureOpaque.define(width, height); } else { // webgl1 requires consistent bit plane counts this.shadedTextureOpaque = resources.texture('image-float32', 'rgba', 'float', 'nearest'); this.shadedTextureOpaque.define(width, height); this.normalTextureOpaque = resources.texture('image-float32', 'rgba', 'float', 'nearest'); this.normalTextureOpaque.define(width, height); this.colorTextureOpaque = resources.texture('image-float32', 'rgba', 'float', 'nearest'); this.colorTextureOpaque.define(width, height); } this.framebuffer = resources.framebuffer(); this.framebuffer.bind(); drawBuffers.drawBuffers([ drawBuffers.COLOR_ATTACHMENT0, drawBuffers.COLOR_ATTACHMENT1, drawBuffers.COLOR_ATTACHMENT2, ]); this.shadedTextureOpaque.attachFramebuffer(this.framebuffer, 'color0'); this.normalTextureOpaque.attachFramebuffer(this.framebuffer, 'color1'); this.colorTextureOpaque.attachFramebuffer(this.framebuffer, 'color2'); this.thicknessTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'nearest'); this.holdTarget = webgl.createRenderTarget(width, height, false, 'float32'); this.accumulateTarget = webgl.createRenderTarget(width, height, false, 'float32'); this.composeTarget = webgl.createRenderTarget(width, height, false, 'uint8', 'linear'); this.traceRenderable = getTraceRenderable(webgl, this.colorTextureOpaque, this.normalTextureOpaque, this.shadedTextureOpaque, this.thicknessTarget.texture, this.accumulateTarget.texture, this.drawPass.depthTextureOpaque); this.accumulateRenderable = getAccumulateRenderable(webgl, this.holdTarget.texture); } renderInput(renderer, camera, scene, props) { if (debug_1.isTimingMode) this.webgl.timer.mark('TracePass.renderInput'); const { gl, state } = this.webgl; this.framebuffer.bind(); this.drawPass.depthTextureOpaque.attachFramebuffer(this.framebuffer, 'depth'); renderer.clear(true); renderer.renderTracing(scene.primitives, camera); // if (props.thicknessMode === 'auto') { this.thicknessTarget.bind(); state.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); renderer.renderDepthOpaqueBack(scene.primitives, camera); } if (debug_1.isTimingMode) this.webgl.timer.markEnd('TracePass.renderInput'); } setSize(width, height) { const w = this.composeTarget.getWidth(); const h = this.composeTarget.getHeight(); if (width !== w || height !== h) { this.thicknessTarget.setSize(width, height); this.holdTarget.setSize(width, height); this.accumulateTarget.setSize(width, height); this.composeTarget.setSize(width, height); this.colorTextureOpaque.define(width, height); this.normalTextureOpaque.define(width, height); this.shadedTextureOpaque.define(width, height); mol_util_1.ValueCell.update(this.traceRenderable.values.uTexSize, vec2_1.Vec2.set(this.traceRenderable.values.uTexSize.ref.value, width, height)); mol_util_1.ValueCell.update(this.accumulateRenderable.values.uTexSize, vec2_1.Vec2.set(this.accumulateRenderable.values.uTexSize.ref.value, width, height)); } } reset() { const { drawBuffers } = this.webgl.extensions; this.framebuffer.bind(); drawBuffers.drawBuffers([ drawBuffers.COLOR_ATTACHMENT0, drawBuffers.COLOR_ATTACHMENT1, drawBuffers.COLOR_ATTACHMENT2, ]); this.shadedTextureOpaque.attachFramebuffer(this.framebuffer, 'color0'); this.normalTextureOpaque.attachFramebuffer(this.framebuffer, 'color1'); this.colorTextureOpaque.attachFramebuffer(this.framebuffer, 'color2'); this.restart(true); } restart(clearAdjustedProps = false) { const { gl, state } = this.webgl; this.accumulateTarget.bind(); state.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); this.composeTarget.bind(); state.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); if (clearAdjustedProps) { this.prevTime = 0; this.currTime = 0; this.clearAdjustedProps = true; } } increaseAdjustedProps(props) { this.steps += 1; if (this.steps > props.steps) { this.refineSteps += 1; } if (this.refineSteps > props.refineSteps) { this.rendersPerFrame += 1; } } decreaseAdjustedProps(props) { const minRefineSteps = Math.min(1, props.refineSteps); this.rendersPerFrame -= 1; if (this.rendersPerFrame < 1) { this.refineSteps -= 1; } if (this.refineSteps < minRefineSteps) { this.steps -= 1; } } getAdjustedProps(props, iteration) { this.currTime = (0, now_1.now)(); const minRefineSteps = Math.min(1, props.refineSteps); const minSteps = Math.round(props.steps / 2); if (this.clearAdjustedProps) { this.rendersPerFrame = props.rendersPerFrame[0]; this.refineSteps = minRefineSteps; this.steps = minSteps; this.clearAdjustedProps = false; } if (iteration > 0) { const targetTimeMs = 1000 / props.targetFps; const deltaTime = this.currTime - this.prevTime; let f = Math.round(deltaTime / targetTimeMs); if (f >= 2) { while (f > 0) { this.decreaseAdjustedProps(props); f -= 1; } } else if (deltaTime < targetTimeMs) { this.increaseAdjustedProps(props); } else if (deltaTime > targetTimeMs + 0.5) { this.decreaseAdjustedProps(props); } } this.prevTime = this.currTime; this.rendersPerFrame = (0, interpolate_1.clamp)(this.rendersPerFrame, props.rendersPerFrame[0], props.rendersPerFrame[1]); this.refineSteps = (0, interpolate_1.clamp)(this.refineSteps, minRefineSteps, props.refineSteps); this.steps = (0, interpolate_1.clamp)(this.steps, minSteps, props.steps); return { rendersPerFrame: iteration === 0 ? Math.ceil(this.rendersPerFrame / 2) : this.rendersPerFrame, refineSteps: iteration === 0 ? minRefineSteps : this.refineSteps, steps: iteration === 0 ? minSteps : this.steps, }; } render(ctx, transparentBackground, props, iteration, forceRenderInput) { const { rendersPerFrame, refineSteps, steps } = this.getAdjustedProps(props, iteration); if (debug_1.isTimingMode) { this.webgl.timer.mark('TracePass.render', { note: `${rendersPerFrame} rendersPerFrame, ${refineSteps} refineSteps, ${steps} steps` }); } const { renderer, camera, scene } = ctx; const { gl, state } = this.webgl; const { x, y, width, height } = camera.viewport; if (iteration === 0 || forceRenderInput) { // render color & depth renderer.setTransparentBackground(transparentBackground); renderer.setDrawingBufferSize(this.composeTarget.getWidth(), this.composeTarget.getHeight()); renderer.setPixelRatio(this.webgl.pixelRatio); renderer.setViewport(x, y, width, height); renderer.update(camera, scene); this.renderInput(renderer, camera, scene, props); } state.disable(gl.BLEND); state.disable(gl.DEPTH_TEST); state.disable(gl.CULL_FACE); state.depthMask(false); state.viewport(x, y, width, height); state.scissor(x, y, width, height); const invProjection = mat4_1.Mat4.identity(); mat4_1.Mat4.invert(invProjection, camera.projection); const orthographic = camera.state.mode === 'orthographic' ? 1 : 0; const [w, h] = this.traceRenderable.values.uTexSize.ref.value; const v = camera.viewport; const ambientColor = (0, vec3_1.Vec3)(); vec3_1.Vec3.scale(ambientColor, color_1.Color.toArrayNormalized(renderer.props.ambientColor, ambientColor, 0), renderer.props.ambientIntensity); const lightStrength = vec3_1.Vec3.clone(ambientColor); for (let i = 0, il = renderer.light.count; i < il; ++i) { const light = vec3_1.Vec3.fromArray((0, vec3_1.Vec3)(), renderer.light.color, i * 3); vec3_1.Vec3.add(lightStrength, lightStrength, light); } // trace this.holdTarget.bind(); let needsUpdateTrace = false; mol_util_1.ValueCell.update(this.traceRenderable.values.uFrameNo, iteration); if (this.traceRenderable.values.dRendersPerFrame.ref.value !== rendersPerFrame) { mol_util_1.ValueCell.update(this.traceRenderable.values.dRendersPerFrame, rendersPerFrame); needsUpdateTrace = true; } mol_util_1.ValueCell.update(this.traceRenderable.values.uProjection, camera.projection); mol_util_1.ValueCell.update(this.traceRenderable.values.uInvProjection, invProjection); vec4_1.Vec4.set(this.traceRenderable.values.uBounds.ref.value, v.x / w, v.y / h, (v.x + v.width) / w, (v.y + v.height) / h); mol_util_1.ValueCell.update(this.traceRenderable.values.uBounds, this.traceRenderable.values.uBounds.ref.value); mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uNear, camera.near); mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uFar, camera.far); mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uFogFar, camera.fogFar); mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uFogNear, camera.fogNear); mol_util_1.ValueCell.update(this.traceRenderable.values.uFogColor, color_1.Color.toVec3Normalized(this.traceRenderable.values.uFogColor.ref.value, renderer.props.backgroundColor)); if (this.traceRenderable.values.dOrthographic.ref.value !== orthographic) { mol_util_1.ValueCell.update(this.traceRenderable.values.dOrthographic, orthographic); needsUpdateTrace = true; } mol_util_1.ValueCell.update(this.traceRenderable.values.uLightDirection, renderer.light.direction); mol_util_1.ValueCell.update(this.traceRenderable.values.uLightColor, renderer.light.color); if (this.traceRenderable.values.dLightCount.ref.value !== renderer.light.count) { mol_util_1.ValueCell.update(this.traceRenderable.values.dLightCount, renderer.light.count); needsUpdateTrace = true; } mol_util_1.ValueCell.update(this.traceRenderable.values.uAmbientColor, ambientColor); mol_util_1.ValueCell.update(this.traceRenderable.values.uLightStrength, lightStrength); if (this.traceRenderable.values.dGlow.ref.value !== props.glow) { mol_util_1.ValueCell.update(this.traceRenderable.values.dGlow, props.glow); needsUpdateTrace = true; } if (this.traceRenderable.values.dBounces.ref.value !== props.bounces) { mol_util_1.ValueCell.update(this.traceRenderable.values.dBounces, props.bounces); needsUpdateTrace = true; } if (this.traceRenderable.values.dSteps.ref.value !== steps) { mol_util_1.ValueCell.update(this.traceRenderable.values.dSteps, steps); needsUpdateTrace = true; } if (this.traceRenderable.values.dFirstStepSize.ref.value !== props.firstStepSize) { mol_util_1.ValueCell.update(this.traceRenderable.values.dFirstStepSize, props.firstStepSize); needsUpdateTrace = true; } if (this.traceRenderable.values.dRefineSteps.ref.value !== refineSteps) { mol_util_1.ValueCell.update(this.traceRenderable.values.dRefineSteps, refineSteps); needsUpdateTrace = true; } mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uRayDistance, props.rayDistance); if (this.traceRenderable.values.dThicknessMode.ref.value !== props.thicknessMode) { mol_util_1.ValueCell.update(this.traceRenderable.values.dThicknessMode, props.thicknessMode); needsUpdateTrace = true; } mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uMinThickness, props.minThickness); mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uThicknessFactor, props.thicknessFactor); mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uThickness, props.thickness); if (this.traceRenderable.values.dShadowEnable.ref.value !== props.shadowEnable) { mol_util_1.ValueCell.update(this.traceRenderable.values.dShadowEnable, props.shadowEnable); needsUpdateTrace = true; } mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uShadowSoftness, props.shadowSoftness); mol_util_1.ValueCell.updateIfChanged(this.traceRenderable.values.uShadowThickness, props.shadowThickness); if (needsUpdateTrace) this.traceRenderable.update(); if (debug_1.isTimingMode) this.webgl.timer.mark('TracePass.renderTrace'); this.traceRenderable.render(); if (debug_1.isTimingMode) this.webgl.timer.markEnd('TracePass.renderTrace'); // accumulate this.accumulateTarget.bind(); this.accumulateRenderable.render(); if (debug_1.isTimingMode) this.webgl.timer.markEnd('TracePass.render'); } } exports.TracingPass = TracingPass; // const TraceSchema = { ...util_1.QuadSchema, tColor: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'nearest'), tNormal: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'nearest'), tShaded: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'nearest'), tThickness: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'nearest'), tAccumulate: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'nearest'), tDepth: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'nearest'), uTexSize: (0, schema_1.UniformSpec)('v2'), dOrthographic: (0, schema_1.DefineSpec)('number'), uNear: (0, schema_1.UniformSpec)('f'), uFar: (0, schema_1.UniformSpec)('f'), uFogNear: (0, schema_1.UniformSpec)('f'), uFogFar: (0, schema_1.UniformSpec)('f'), uFogColor: (0, schema_1.UniformSpec)('v3'), uProjection: (0, schema_1.UniformSpec)('m4'), uInvProjection: (0, schema_1.UniformSpec)('m4'), uBounds: (0, schema_1.UniformSpec)('v4'), uLightDirection: (0, schema_1.UniformSpec)('v3[]'), uLightColor: (0, schema_1.UniformSpec)('v3[]'), dLightCount: (0, schema_1.DefineSpec)('number'), uAmbientColor: (0, schema_1.UniformSpec)('v3'), uLightStrength: (0, schema_1.UniformSpec)('v3'), uFrameNo: (0, schema_1.UniformSpec)('i'), dRendersPerFrame: (0, schema_1.DefineSpec)('number'), dGlow: (0, schema_1.DefineSpec)('boolean'), dBounces: (0, schema_1.DefineSpec)('number'), dSteps: (0, schema_1.DefineSpec)('number'), dFirstStepSize: (0, schema_1.DefineSpec)('number'), dRefineSteps: (0, schema_1.DefineSpec)('number'), uRayDistance: (0, schema_1.UniformSpec)('f'), dThicknessMode: (0, schema_1.DefineSpec)('string'), uMinThickness: (0, schema_1.UniformSpec)('f'), uThicknessFactor: (0, schema_1.UniformSpec)('f'), uThickness: (0, schema_1.UniformSpec)('f'), dShadowEnable: (0, schema_1.DefineSpec)('boolean'), uShadowSoftness: (0, schema_1.UniformSpec)('f'), uShadowThickness: (0, schema_1.UniformSpec)('f'), }; const TraceShaderCode = (0, shader_code_1.ShaderCode)('trace', quad_vert_1.quad_vert, trace_frag_1.trace_frag); function getTraceRenderable(ctx, colorTexture, normalTexture, shadedTexture, thicknessTexture, accumulateTexture, depthTexture) { const values = { ...util_1.QuadValues, tColor: mol_util_1.ValueCell.create(colorTexture), tNormal: mol_util_1.ValueCell.create(normalTexture), tShaded: mol_util_1.ValueCell.create(shadedTexture), tThickness: mol_util_1.ValueCell.create(thicknessTexture), tAccumulate: mol_util_1.ValueCell.create(accumulateTexture), tDepth: mol_util_1.ValueCell.create(depthTexture), uTexSize: mol_util_1.ValueCell.create(vec2_1.Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())), dOrthographic: mol_util_1.ValueCell.create(0), uNear: mol_util_1.ValueCell.create(1), uFar: mol_util_1.ValueCell.create(10000), uFogNear: mol_util_1.ValueCell.create(10000), uFogFar: mol_util_1.ValueCell.create(10000), uFogColor: mol_util_1.ValueCell.create(vec3_1.Vec3.create(1, 1, 1)), uProjection: mol_util_1.ValueCell.create(mat4_1.Mat4.identity()), uInvProjection: mol_util_1.ValueCell.create(mat4_1.Mat4.identity()), uBounds: mol_util_1.ValueCell.create((0, vec4_1.Vec4)()), uLightDirection: mol_util_1.ValueCell.create([]), uLightColor: mol_util_1.ValueCell.create([]), dLightCount: mol_util_1.ValueCell.create(0), uAmbientColor: mol_util_1.ValueCell.create((0, vec3_1.Vec3)()), uLightStrength: mol_util_1.ValueCell.create(vec3_1.Vec3.create(1, 1, 1)), uFrameNo: mol_util_1.ValueCell.create(0), dRendersPerFrame: mol_util_1.ValueCell.create(1), dGlow: mol_util_1.ValueCell.create(true), dBounces: mol_util_1.ValueCell.create(4), dSteps: mol_util_1.ValueCell.create(32), dFirstStepSize: mol_util_1.ValueCell.create(0.01), dRefineSteps: mol_util_1.ValueCell.create(4), uRayDistance: mol_util_1.ValueCell.create(256), dThicknessMode: mol_util_1.ValueCell.create('auto'), uMinThickness: mol_util_1.ValueCell.create(0.5), uThicknessFactor: mol_util_1.ValueCell.create(1), uThickness: mol_util_1.ValueCell.create(4), dShadowEnable: mol_util_1.ValueCell.create(false), uShadowSoftness: mol_util_1.ValueCell.create(0.1), uShadowThickness: mol_util_1.ValueCell.create(0.1), }; const schema = { ...TraceSchema }; const renderItem = (0, render_item_1.createComputeRenderItem)(ctx, 'triangles', TraceShaderCode, schema, values); return (0, renderable_1.createComputeRenderable)(renderItem, values); } // const AccumulateSchema = { ...util_1.QuadSchema, tColor: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'nearest'), uTexSize: (0, schema_1.UniformSpec)('v2'), uWeight: (0, schema_1.UniformSpec)('f'), }; const AccumulateShaderCode = (0, shader_code_1.ShaderCode)('accumulate', quad_vert_1.quad_vert, accumulate_frag_1.accumulate_frag); function getAccumulateRenderable(ctx, colorTexture) { const values = { ...util_1.QuadValues, tColor: mol_util_1.ValueCell.create(colorTexture), uTexSize: mol_util_1.ValueCell.create(vec2_1.Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())), uWeight: mol_util_1.ValueCell.create(1.0), }; const schema = { ...AccumulateSchema }; const renderItem = (0, render_item_1.createComputeRenderItem)(ctx, 'triangles', AccumulateShaderCode, schema, values); return (0, renderable_1.createComputeRenderable)(renderItem, values); }