renderer.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Viewport } from '../mol-canvas3d/camera/util';
  7. import { Camera } from '../mol-canvas3d/camera';
  8. import Scene from './scene';
  9. import { WebGLContext } from './webgl/context';
  10. import { Mat4, Vec3, Vec4, Vec2 } from '../mol-math/linear-algebra';
  11. import { Renderable } from './renderable';
  12. import { Color } from '../mol-util/color';
  13. import { ValueCell } from '../mol-util';
  14. import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
  15. import { GraphicsRenderVariant } from './webgl/render-item';
  16. import { ParamDefinition as PD } from '../mol-util/param-definition';
  17. import { deepClone } from '../mol-util/object';
  18. export interface RendererStats {
  19. programCount: number
  20. shaderCount: number
  21. attributeCount: number
  22. elementsCount: number
  23. framebufferCount: number
  24. renderbufferCount: number
  25. textureCount: number
  26. vertexArrayCount: number
  27. drawCount: number
  28. instanceCount: number
  29. instancedDrawCount: number
  30. }
  31. interface Renderer {
  32. readonly stats: RendererStats
  33. readonly props: Readonly<RendererProps>
  34. clear: (transparentBackground: boolean) => void
  35. render: (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean) => void
  36. setProps: (props: Partial<RendererProps>) => void
  37. setViewport: (x: number, y: number, width: number, height: number) => void
  38. dispose: () => void
  39. }
  40. export const RendererParams = {
  41. backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
  42. // the following are general 'material' parameters
  43. 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.' }),
  44. interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
  45. interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }),
  46. interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
  47. highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
  48. selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
  49. style: PD.MappedStatic('matte', {
  50. custom: PD.Group({
  51. lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
  52. ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
  53. metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
  54. roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
  55. reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
  56. }, { isExpanded: true }),
  57. flat: PD.Group({}),
  58. matte: PD.Group({}),
  59. glossy: PD.Group({}),
  60. metallic: PD.Group({}),
  61. plastic: PD.Group({}),
  62. }, { label: 'Render Style', description: 'Style in which the 3D scene is rendered' }),
  63. }
  64. export type RendererProps = PD.Values<typeof RendererParams>
  65. function getStyle(props: RendererProps['style']) {
  66. switch (props.name) {
  67. case 'custom':
  68. return props.params
  69. case 'flat':
  70. return {
  71. lightIntensity: 0, ambientIntensity: 1,
  72. metalness: 0, roughness: 0.4, reflectivity: 0.5
  73. }
  74. case 'matte':
  75. return {
  76. lightIntensity: 0.6, ambientIntensity: 0.4,
  77. metalness: 0, roughness: 1, reflectivity: 0.5
  78. }
  79. case 'glossy':
  80. return {
  81. lightIntensity: 0.6, ambientIntensity: 0.4,
  82. metalness: 0, roughness: 0.4, reflectivity: 0.5
  83. }
  84. case 'metallic':
  85. return {
  86. lightIntensity: 0.6, ambientIntensity: 0.4,
  87. metalness: 0.4, roughness: 0.6, reflectivity: 0.5
  88. }
  89. case 'plastic':
  90. return {
  91. lightIntensity: 0.6, ambientIntensity: 0.4,
  92. metalness: 0, roughness: 0.2, reflectivity: 0.5
  93. }
  94. }
  95. }
  96. namespace Renderer {
  97. export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
  98. const { gl, state, stats } = ctx
  99. const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
  100. const style = getStyle(p.style)
  101. const viewport = Viewport()
  102. const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor)
  103. const view = Mat4()
  104. const invView = Mat4()
  105. const modelView = Mat4()
  106. const invModelView = Mat4()
  107. const invProjection = Mat4()
  108. const modelViewProjection = Mat4()
  109. const invModelViewProjection = Mat4()
  110. const viewOffset = Vec2()
  111. const globalUniforms: GlobalUniformValues = {
  112. uModel: ValueCell.create(Mat4.identity()),
  113. uView: ValueCell.create(view),
  114. uInvView: ValueCell.create(invView),
  115. uModelView: ValueCell.create(modelView),
  116. uInvModelView: ValueCell.create(invModelView),
  117. uInvProjection: ValueCell.create(invProjection),
  118. uProjection: ValueCell.create(Mat4()),
  119. uModelViewProjection: ValueCell.create(modelViewProjection),
  120. uInvModelViewProjection: ValueCell.create(invModelViewProjection),
  121. uIsOrtho: ValueCell.create(1),
  122. uViewOffset: ValueCell.create(viewOffset),
  123. uPixelRatio: ValueCell.create(ctx.pixelRatio),
  124. uViewportHeight: ValueCell.create(viewport.height),
  125. uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)),
  126. uCameraPosition: ValueCell.create(Vec3()),
  127. uNear: ValueCell.create(1),
  128. uFar: ValueCell.create(10000),
  129. uFogNear: ValueCell.create(1),
  130. uFogFar: ValueCell.create(10000),
  131. uFogColor: ValueCell.create(bgColor),
  132. uTransparentBackground: ValueCell.create(0),
  133. // the following are general 'material' uniforms
  134. uLightIntensity: ValueCell.create(style.lightIntensity),
  135. uAmbientIntensity: ValueCell.create(style.ambientIntensity),
  136. uMetalness: ValueCell.create(style.metalness),
  137. uRoughness: ValueCell.create(style.roughness),
  138. uReflectivity: ValueCell.create(style.reflectivity),
  139. uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
  140. uInteriorDarkening: ValueCell.create(p.interiorDarkening),
  141. uInteriorColorFlag: ValueCell.create(p.interiorColorFlag ? 1 : 0),
  142. uInteriorColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.interiorColor)),
  143. uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
  144. uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
  145. }
  146. const globalUniformList = Object.entries(globalUniforms)
  147. let globalUniformsNeedUpdate = true
  148. const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant) => {
  149. const program = r.getProgram(variant)
  150. if (r.state.visible) {
  151. if (state.currentProgramId !== program.id) {
  152. // console.log('new program')
  153. globalUniformsNeedUpdate = true
  154. program.use()
  155. }
  156. if (globalUniformsNeedUpdate) {
  157. // console.log('globalUniformsNeedUpdate')
  158. program.setUniforms(globalUniformList)
  159. globalUniformsNeedUpdate = false
  160. }
  161. if (r.values.dDoubleSided) {
  162. if (r.values.dDoubleSided.ref.value) {
  163. state.disable(gl.CULL_FACE)
  164. } else {
  165. state.enable(gl.CULL_FACE)
  166. }
  167. } else {
  168. // webgl default
  169. state.disable(gl.CULL_FACE)
  170. }
  171. if (r.values.dFlipSided) {
  172. if (r.values.dFlipSided.ref.value) {
  173. state.frontFace(gl.CW)
  174. state.cullFace(gl.FRONT)
  175. } else {
  176. state.frontFace(gl.CCW)
  177. state.cullFace(gl.BACK)
  178. }
  179. } else {
  180. // webgl default
  181. state.frontFace(gl.CCW)
  182. state.cullFace(gl.BACK)
  183. }
  184. r.render(variant)
  185. }
  186. }
  187. const render = (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean) => {
  188. ValueCell.update(globalUniforms.uModel, scene.view)
  189. ValueCell.update(globalUniforms.uView, camera.view)
  190. ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view))
  191. ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, scene.view, camera.view))
  192. ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView))
  193. ValueCell.update(globalUniforms.uProjection, camera.projection)
  194. ValueCell.update(globalUniforms.uInvProjection, Mat4.invert(invProjection, camera.projection))
  195. ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection))
  196. ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection))
  197. ValueCell.update(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0)
  198. ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0))
  199. ValueCell.update(globalUniforms.uCameraPosition, camera.state.position)
  200. ValueCell.update(globalUniforms.uFar, camera.far)
  201. ValueCell.update(globalUniforms.uNear, camera.near)
  202. ValueCell.update(globalUniforms.uFogFar, camera.fogFar)
  203. ValueCell.update(globalUniforms.uFogNear, camera.fogNear)
  204. ValueCell.update(globalUniforms.uTransparentBackground, transparentBackground ? 1 : 0)
  205. globalUniformsNeedUpdate = true
  206. state.currentRenderItemId = -1
  207. const { renderables } = scene
  208. state.disable(gl.SCISSOR_TEST)
  209. state.disable(gl.BLEND)
  210. state.depthMask(true)
  211. state.colorMask(true, true, true, true)
  212. state.enable(gl.DEPTH_TEST)
  213. if (clear) {
  214. if (variant === 'color') {
  215. state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1)
  216. } else {
  217. state.clearColor(1, 1, 1, 1)
  218. }
  219. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
  220. }
  221. if (variant === 'color') {
  222. for (let i = 0, il = renderables.length; i < il; ++i) {
  223. const r = renderables[i]
  224. if (r.state.opaque) renderObject(r, variant)
  225. }
  226. state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE)
  227. state.enable(gl.BLEND)
  228. for (let i = 0, il = renderables.length; i < il; ++i) {
  229. const r = renderables[i]
  230. if (!r.state.opaque) {
  231. state.depthMask(false)
  232. renderObject(r, variant)
  233. }
  234. }
  235. } else { // picking & depth
  236. for (let i = 0, il = renderables.length; i < il; ++i) {
  237. renderObject(renderables[i], variant)
  238. }
  239. }
  240. gl.finish()
  241. }
  242. return {
  243. clear: (transparentBackground: boolean) => {
  244. state.depthMask(true)
  245. state.colorMask(true, true, true, true)
  246. state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1)
  247. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
  248. },
  249. render,
  250. setProps: (props: Partial<RendererProps>) => {
  251. if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
  252. p.backgroundColor = props.backgroundColor
  253. Color.toVec3Normalized(bgColor, p.backgroundColor)
  254. ValueCell.update(globalUniforms.uFogColor, Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor))
  255. }
  256. if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== p.pickingAlphaThreshold) {
  257. p.pickingAlphaThreshold = props.pickingAlphaThreshold
  258. ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold)
  259. }
  260. if (props.interiorDarkening !== undefined && props.interiorDarkening !== p.interiorDarkening) {
  261. p.interiorDarkening = props.interiorDarkening
  262. ValueCell.update(globalUniforms.uInteriorDarkening, p.interiorDarkening)
  263. }
  264. if (props.interiorColorFlag !== undefined && props.interiorColorFlag !== p.interiorColorFlag) {
  265. p.interiorColorFlag = props.interiorColorFlag
  266. ValueCell.update(globalUniforms.uInteriorColorFlag, p.interiorColorFlag ? 1 : 0)
  267. }
  268. if (props.interiorColor !== undefined && props.interiorColor !== p.interiorColor) {
  269. p.interiorColor = props.interiorColor
  270. ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor))
  271. }
  272. if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) {
  273. p.highlightColor = props.highlightColor
  274. ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor))
  275. }
  276. if (props.selectColor !== undefined && props.selectColor !== p.selectColor) {
  277. p.selectColor = props.selectColor
  278. ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor))
  279. }
  280. if (props.style !== undefined) {
  281. p.style = props.style
  282. Object.assign(style, getStyle(props.style))
  283. ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity)
  284. ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity)
  285. ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness)
  286. ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness)
  287. ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity)
  288. }
  289. },
  290. setViewport: (x: number, y: number, width: number, height: number) => {
  291. gl.viewport(x, y, width, height)
  292. if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) {
  293. Viewport.set(viewport, x, y, width, height)
  294. ValueCell.update(globalUniforms.uViewportHeight, height)
  295. ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height))
  296. }
  297. },
  298. get props() {
  299. return p
  300. },
  301. get stats(): RendererStats {
  302. return {
  303. programCount: ctx.stats.resourceCounts.program,
  304. shaderCount: ctx.stats.resourceCounts.shader,
  305. attributeCount: ctx.stats.resourceCounts.attribute,
  306. elementsCount: ctx.stats.resourceCounts.elements,
  307. framebufferCount: ctx.stats.resourceCounts.framebuffer,
  308. renderbufferCount: ctx.stats.resourceCounts.renderbuffer,
  309. textureCount: ctx.stats.resourceCounts.texture,
  310. vertexArrayCount: ctx.stats.resourceCounts.vertexArray,
  311. drawCount: stats.drawCount,
  312. instanceCount: stats.instanceCount,
  313. instancedDrawCount: stats.instancedDrawCount,
  314. }
  315. },
  316. dispose: () => {
  317. // TODO
  318. }
  319. }
  320. }
  321. }
  322. export default Renderer