/** * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { Viewport } from '../mol-canvas3d/camera/util'; import { Camera } from '../mol-canvas3d/camera'; import Scene from './scene'; import { WebGLContext } from './webgl/context'; import { Mat4, Vec3, Vec4, Vec2 } from '../mol-math/linear-algebra'; import { Renderable } from './renderable'; import { Color } from '../mol-util/color'; import { ValueCell } from '../mol-util'; import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema'; import { GraphicsRenderVariant } from './webgl/render-item'; import { ParamDefinition as PD } from '../mol-util/param-definition'; import { deepClone } from '../mol-util/object'; export interface RendererStats { programCount: number shaderCount: number bufferCount: number framebufferCount: number renderbufferCount: number textureCount: number vaoCount: number drawCount: number instanceCount: number instancedDrawCount: number } interface Renderer { readonly stats: RendererStats readonly props: Readonly<RendererProps> clear: () => void render: (scene: Scene, variant: GraphicsRenderVariant, clear: boolean) => void setProps: (props: Partial<RendererProps>) => void setViewport: (x: number, y: number, width: number, height: number) => void dispose: () => void } export const RendererParams = { backgroundColor: PD.Color(Color(0x000000)), pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }), interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }), ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }), metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }), roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }), reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), } export type RendererProps = PD.Values<typeof RendererParams> namespace Renderer { export function create(ctx: WebGLContext, camera: Camera, 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 viewOffset = camera.viewOffset.enabled ? Vec2.create(camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2() const globalUniforms: GlobalUniformValues = { uModel: ValueCell.create(Mat4.identity()), uView: ValueCell.create(camera.view), uInvView: ValueCell.create(invView), uModelView: ValueCell.create(modelView), uInvModelView: ValueCell.create(invModelView), uInvProjection: ValueCell.create(invProjection), uProjection: ValueCell.create(Mat4.clone(camera.projection)), uModelViewProjection: ValueCell.create(modelViewProjection), uInvModelViewProjection: ValueCell.create(invModelViewProjection), uIsOrtho: ValueCell.create(camera.state.mode === 'orthographic' ? 1 : 0), 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), uMetalness: ValueCell.create(p.metalness), uRoughness: ValueCell.create(p.roughness), uReflectivity: ValueCell.create(p.reflectivity), uCameraPosition: ValueCell.create(Vec3.clone(camera.state.position)), uNear: ValueCell.create(camera.state.near), uFar: ValueCell.create(camera.state.far), uFogNear: ValueCell.create(camera.state.fogNear), uFogFar: ValueCell.create(camera.state.fogFar), uFogColor: ValueCell.create(bgColor), uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold), uInteriorDarkening: ValueCell.create(p.interiorDarkening), } const globalUniformList = Object.entries(globalUniforms) let globalUniformsNeedUpdate = true const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant) => { const program = r.getProgram(variant) if (r.state.visible) { if (state.currentProgramId !== program.id) { // console.log('new program') globalUniformsNeedUpdate = true program.use() } if (globalUniformsNeedUpdate) { // console.log('globalUniformsNeedUpdate') program.setUniforms(globalUniformList) globalUniformsNeedUpdate = false } if (r.values.dDoubleSided) { if (r.values.dDoubleSided.ref.value) { state.disable(gl.CULL_FACE) } else { state.enable(gl.CULL_FACE) } } else { // webgl default state.disable(gl.CULL_FACE) } if (r.values.dFlipSided) { if (r.values.dFlipSided.ref.value) { state.frontFace(gl.CW) state.cullFace(gl.FRONT) } else { state.frontFace(gl.CCW) state.cullFace(gl.BACK) } } else { // webgl default state.frontFace(gl.CCW) state.cullFace(gl.BACK) } r.render(variant) } } const render = (scene: Scene, 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)) ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, scene.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)) ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection)) ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection)) ValueCell.update(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0) 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.uFar, camera.state.far) ValueCell.update(globalUniforms.uNear, camera.state.near) ValueCell.update(globalUniforms.uFogFar, camera.state.fogFar) ValueCell.update(globalUniforms.uFogNear, camera.state.fogNear) globalUniformsNeedUpdate = true state.currentRenderItemId = -1 const { renderables } = scene state.disable(gl.SCISSOR_TEST) state.disable(gl.BLEND) state.depthMask(true) state.colorMask(true, true, true, true) state.enable(gl.DEPTH_TEST) if (clear) { if (variant === 'color') { state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1.0) } else { state.clearColor(1, 1, 1, 1) } gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) } if (variant === 'color') { for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i] if (r.state.opaque) renderObject(r, variant) } state.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) state.enable(gl.BLEND) for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i] state.depthMask(r.values.uAlpha.ref.value === 1.0) if (!r.state.opaque) renderObject(r, variant) } } else { // picking & depth for (let i = 0, il = renderables.length; i < il; ++i) { renderObject(renderables[i], variant) } } gl.finish() } return { clear: () => { state.depthMask(true) state.colorMask(true, true, true, true) state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1.0) gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) }, render, setProps: (props: Partial<RendererProps>) => { if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== p.pickingAlphaThreshold) { p.pickingAlphaThreshold = props.pickingAlphaThreshold ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold) } if (props.interiorDarkening !== undefined && props.interiorDarkening !== p.interiorDarkening) { p.interiorDarkening = props.interiorDarkening ValueCell.update(globalUniforms.uInteriorDarkening, p.interiorDarkening) } if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) { p.backgroundColor = props.backgroundColor Color.toVec3Normalized(bgColor, p.backgroundColor) ValueCell.update(globalUniforms.uFogColor, Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor)) } if (props.lightIntensity !== undefined && props.lightIntensity !== p.lightIntensity) { p.lightIntensity = props.lightIntensity ValueCell.update(globalUniforms.uLightIntensity, p.lightIntensity) } if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) { p.ambientIntensity = props.ambientIntensity ValueCell.update(globalUniforms.uAmbientIntensity, p.ambientIntensity) } if (props.metalness !== undefined && props.metalness !== p.metalness) { p.metalness = props.metalness ValueCell.update(globalUniforms.uMetalness, p.metalness) } if (props.roughness !== undefined && props.roughness !== p.roughness) { p.roughness = props.roughness ValueCell.update(globalUniforms.uRoughness, p.roughness) } if (props.reflectivity !== undefined && props.reflectivity !== p.reflectivity) { p.reflectivity = props.reflectivity ValueCell.update(globalUniforms.uReflectivity, p.reflectivity) } }, setViewport: (x: number, y: number, width: number, height: number) => { gl.viewport(x, y, width, height) if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) { Viewport.set(viewport, x, y, width, height) ValueCell.update(globalUniforms.uViewportHeight, height) ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height)) } }, get props() { return p }, get stats(): RendererStats { return { programCount: ctx.programCache.count, shaderCount: ctx.shaderCache.count, bufferCount: stats.bufferCount, framebufferCount: stats.framebufferCount, renderbufferCount: stats.renderbufferCount, textureCount: stats.textureCount, vaoCount: stats.vaoCount, drawCount: stats.drawCount, instanceCount: stats.instanceCount, instancedDrawCount: stats.instancedDrawCount, } }, dispose: () => { // TODO } } } } export default Renderer