123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- /**
- * Copyright (c) 2018-2020 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
- attributeCount: number
- elementsCount: number
- framebufferCount: number
- renderbufferCount: number
- textureCount: number
- vertexArrayCount: number
- drawCount: number
- instanceCount: number
- instancedDrawCount: number
- }
- interface Renderer {
- readonly stats: RendererStats
- readonly props: Readonly<RendererProps>
- clear: (transparentBackground: boolean) => void
- render: (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: 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), { description: 'Background color of the 3D canvas' }),
- // the following are general 'material' parameters
- 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 }),
- interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }),
- interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
- highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
- selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
- style: PD.MappedStatic('matte', {
- custom: PD.Group({
- 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 }),
- }, { isExpanded: true }),
- flat: PD.Group({}),
- matte: PD.Group({}),
- glossy: PD.Group({}),
- metallic: PD.Group({}),
- plastic: PD.Group({}),
- }, { label: 'Render Style', description: 'Style in which the 3D scene is rendered' }),
- }
- export type RendererProps = PD.Values<typeof RendererParams>
- function getStyle(props: RendererProps['style']) {
- switch (props.name) {
- case 'custom':
- return props.params
- case 'flat':
- return {
- lightIntensity: 0, ambientIntensity: 1,
- metalness: 0, roughness: 0.4, reflectivity: 0.5
- }
- case 'matte':
- return {
- lightIntensity: 0.6, ambientIntensity: 0.4,
- metalness: 0, roughness: 1, reflectivity: 0.5
- }
- case 'glossy':
- return {
- lightIntensity: 0.6, ambientIntensity: 0.4,
- metalness: 0, roughness: 0.4, reflectivity: 0.5
- }
- case 'metallic':
- return {
- lightIntensity: 0.6, ambientIntensity: 0.4,
- metalness: 0.4, roughness: 0.6, reflectivity: 0.5
- }
- case 'plastic':
- return {
- lightIntensity: 0.6, ambientIntensity: 0.4,
- metalness: 0, roughness: 0.2, reflectivity: 0.5
- }
- }
- }
- namespace Renderer {
- export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
- const { gl, state, stats } = ctx
- const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
- const style = getStyle(p.style)
- const viewport = Viewport()
- const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor)
- const view = Mat4()
- const invView = Mat4()
- const modelView = Mat4()
- const invModelView = Mat4()
- const invProjection = Mat4()
- const modelViewProjection = Mat4()
- const invModelViewProjection = Mat4()
- const viewOffset = Vec2()
- const globalUniforms: GlobalUniformValues = {
- uModel: ValueCell.create(Mat4.identity()),
- uView: ValueCell.create(view),
- uInvView: ValueCell.create(invView),
- uModelView: ValueCell.create(modelView),
- uInvModelView: ValueCell.create(invModelView),
- uInvProjection: ValueCell.create(invProjection),
- uProjection: ValueCell.create(Mat4()),
- uModelViewProjection: ValueCell.create(modelViewProjection),
- uInvModelViewProjection: ValueCell.create(invModelViewProjection),
- 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)),
- 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(0),
- // the following are general 'material' uniforms
- uLightIntensity: ValueCell.create(style.lightIntensity),
- uAmbientIntensity: ValueCell.create(style.ambientIntensity),
- uMetalness: ValueCell.create(style.metalness),
- uRoughness: ValueCell.create(style.roughness),
- uReflectivity: ValueCell.create(style.reflectivity),
- uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
- uInteriorDarkening: ValueCell.create(p.interiorDarkening),
- uInteriorColorFlag: ValueCell.create(p.interiorColorFlag ? 1 : 0),
- uInteriorColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.interiorColor)),
- uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
- uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
- }
- 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, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: 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.far)
- ValueCell.update(globalUniforms.uNear, camera.near)
- ValueCell.update(globalUniforms.uFogFar, camera.fogFar)
- ValueCell.update(globalUniforms.uFogNear, camera.fogNear)
- ValueCell.update(globalUniforms.uTransparentBackground, transparentBackground ? 1 : 0)
- 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], transparentBackground ? 0 : 1)
- } 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.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) {
- state.depthMask(false)
- renderObject(r, variant)
- }
- }
- } else { // picking & depth
- for (let i = 0, il = renderables.length; i < il; ++i) {
- renderObject(renderables[i], variant)
- }
- }
- gl.finish()
- }
- return {
- clear: (transparentBackground: boolean) => {
- state.depthMask(true)
- state.colorMask(true, true, true, true)
- state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1)
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
- },
- render,
- setProps: (props: Partial<RendererProps>) => {
- 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.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.interiorColorFlag !== undefined && props.interiorColorFlag !== p.interiorColorFlag) {
- p.interiorColorFlag = props.interiorColorFlag
- ValueCell.update(globalUniforms.uInteriorColorFlag, p.interiorColorFlag ? 1 : 0)
- }
- if (props.interiorColor !== undefined && props.interiorColor !== p.interiorColor) {
- p.interiorColor = props.interiorColor
- ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor))
- }
- if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) {
- p.highlightColor = props.highlightColor
- ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor))
- }
- if (props.selectColor !== undefined && props.selectColor !== p.selectColor) {
- p.selectColor = props.selectColor
- ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor))
- }
- if (props.style !== undefined) {
- p.style = props.style
- Object.assign(style, getStyle(props.style))
- ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity)
- ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity)
- ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness)
- ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness)
- ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.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.stats.resourceCounts.program,
- shaderCount: ctx.stats.resourceCounts.shader,
- attributeCount: ctx.stats.resourceCounts.attribute,
- elementsCount: ctx.stats.resourceCounts.elements,
- framebufferCount: ctx.stats.resourceCounts.framebuffer,
- renderbufferCount: ctx.stats.resourceCounts.renderbuffer,
- textureCount: ctx.stats.resourceCounts.texture,
- vertexArrayCount: ctx.stats.resourceCounts.vertexArray,
- drawCount: stats.drawCount,
- instanceCount: stats.instanceCount,
- instancedDrawCount: stats.instancedDrawCount,
- }
- },
- dispose: () => {
- // TODO
- }
- }
- }
- }
- export default Renderer
|