renderer.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  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 { ICamera } from '../mol-canvas3d/camera';
  8. import Scene from './scene';
  9. import { WebGLContext } from './webgl/context';
  10. import { Mat4, Vec3, Vec4, Vec2, Quat } from '../mol-math/linear-algebra';
  11. import { GraphicsRenderable } from './renderable';
  12. import { Color } from '../mol-util/color';
  13. import { ValueCell, deepEqual } from '../mol-util';
  14. import { GlobalUniformValues } from './renderable/schema';
  15. import { GraphicsRenderVariant } from './webgl/render-item';
  16. import { ParamDefinition as PD } from '../mol-util/param-definition';
  17. import { Clipping } from '../mol-theme/clipping';
  18. import { stringToWords } from '../mol-util/string';
  19. import { Transparency } from '../mol-theme/transparency';
  20. import { degToRad } from '../mol-math/misc';
  21. import { createNullTexture, Texture, Textures } from './webgl/texture';
  22. import { arrayMapUpsert } from '../mol-util/array';
  23. export interface RendererStats {
  24. programCount: number
  25. shaderCount: number
  26. attributeCount: number
  27. elementsCount: number
  28. framebufferCount: number
  29. renderbufferCount: number
  30. textureCount: number
  31. vertexArrayCount: number
  32. drawCount: number
  33. instanceCount: number
  34. instancedDrawCount: number
  35. }
  36. interface Renderer {
  37. readonly stats: RendererStats
  38. readonly props: Readonly<RendererProps>
  39. clear: (toBackgroundColor: boolean) => void
  40. clearDepth: () => void
  41. update: (camera: ICamera) => void
  42. renderPick: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null) => void
  43. renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  44. renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  45. renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  46. renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  47. renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  48. renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  49. renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  50. setProps: (props: Partial<RendererProps>) => void
  51. setViewport: (x: number, y: number, width: number, height: number) => void
  52. setTransparentBackground: (value: boolean) => void
  53. setDrawingBufferSize: (width: number, height: number) => void
  54. dispose: () => void
  55. }
  56. export const RendererParams = {
  57. backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
  58. // the following are general 'material' parameters
  59. 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.' }),
  60. transparencyVariant: PD.Select('single', PD.arrayToOptions<Transparency.Variant>(['single', 'multi'])),
  61. interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
  62. interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }),
  63. interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
  64. highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
  65. selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
  66. style: PD.MappedStatic('matte', {
  67. custom: PD.Group({
  68. lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
  69. ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
  70. metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
  71. roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
  72. reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
  73. }, { isExpanded: true }),
  74. flat: PD.Group({}),
  75. matte: PD.Group({}),
  76. glossy: PD.Group({}),
  77. metallic: PD.Group({}),
  78. plastic: PD.Group({}),
  79. }, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }),
  80. clip: PD.Group({
  81. variant: PD.Select('instance', PD.arrayToOptions<Clipping.Variant>(['instance', 'pixel'])),
  82. objects: PD.ObjectList({
  83. type: PD.Select('plane', PD.objectToOptions(Clipping.Type, t => stringToWords(t))),
  84. position: PD.Vec3(Vec3()),
  85. rotation: PD.Group({
  86. axis: PD.Vec3(Vec3.create(1, 0, 0)),
  87. angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }, { description: 'Angle in Degrees' }),
  88. }, { isExpanded: true }),
  89. scale: PD.Vec3(Vec3.create(1, 1, 1)),
  90. }, o => stringToWords(o.type))
  91. })
  92. };
  93. export type RendererProps = PD.Values<typeof RendererParams>
  94. function getStyle(props: RendererProps['style']) {
  95. switch (props.name) {
  96. case 'custom':
  97. return props.params;
  98. case 'flat':
  99. return {
  100. lightIntensity: 0, ambientIntensity: 1,
  101. metalness: 0, roughness: 0.4, reflectivity: 0.5
  102. };
  103. case 'matte':
  104. return {
  105. lightIntensity: 0.6, ambientIntensity: 0.4,
  106. metalness: 0, roughness: 1, reflectivity: 0.5
  107. };
  108. case 'glossy':
  109. return {
  110. lightIntensity: 0.6, ambientIntensity: 0.4,
  111. metalness: 0, roughness: 0.4, reflectivity: 0.5
  112. };
  113. case 'metallic':
  114. return {
  115. lightIntensity: 0.6, ambientIntensity: 0.4,
  116. metalness: 0.4, roughness: 0.6, reflectivity: 0.5
  117. };
  118. case 'plastic':
  119. return {
  120. lightIntensity: 0.6, ambientIntensity: 0.4,
  121. metalness: 0, roughness: 0.2, reflectivity: 0.5
  122. };
  123. }
  124. }
  125. type Clip = {
  126. variant: Clipping.Variant
  127. objects: {
  128. count: number
  129. type: number[]
  130. position: number[]
  131. rotation: number[]
  132. scale: number[]
  133. }
  134. }
  135. const tmpQuat = Quat();
  136. function getClip(props: RendererProps['clip'], clip?: Clip): Clip {
  137. const { type, position, rotation, scale } = clip?.objects || {
  138. type: (new Array(5)).fill(1),
  139. position: (new Array(5 * 3)).fill(0),
  140. rotation: (new Array(5 * 4)).fill(0),
  141. scale: (new Array(5 * 3)).fill(1),
  142. };
  143. for (let i = 0, il = props.objects.length; i < il; ++i) {
  144. const p = props.objects[i];
  145. type[i] = Clipping.Type[p.type];
  146. Vec3.toArray(p.position, position, i * 3);
  147. Quat.toArray(Quat.setAxisAngle(tmpQuat, p.rotation.axis, degToRad(p.rotation.angle)), rotation, i * 4);
  148. Vec3.toArray(p.scale, scale, i * 3);
  149. }
  150. return {
  151. variant: props.variant,
  152. objects: { count: props.objects.length, type, position, rotation, scale }
  153. };
  154. }
  155. namespace Renderer {
  156. export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
  157. const { gl, state, stats, extensions: { fragDepth } } = ctx;
  158. const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
  159. const style = getStyle(p.style);
  160. const clip = getClip(p.clip);
  161. const viewport = Viewport();
  162. const drawingBufferSize = Vec2.create(gl.drawingBufferWidth, gl.drawingBufferHeight);
  163. const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor);
  164. let transparentBackground = false;
  165. const nullDepthTexture = createNullTexture(gl, 'image-depth');
  166. const sharedTexturesList: Textures = [
  167. ['tDepth', nullDepthTexture]
  168. ];
  169. const view = Mat4();
  170. const invView = Mat4();
  171. const modelView = Mat4();
  172. const invModelView = Mat4();
  173. const invProjection = Mat4();
  174. const modelViewProjection = Mat4();
  175. const invModelViewProjection = Mat4();
  176. const cameraDir = Vec3();
  177. const viewOffset = Vec2();
  178. const globalUniforms: GlobalUniformValues = {
  179. uModel: ValueCell.create(Mat4.identity()),
  180. uView: ValueCell.create(view),
  181. uInvView: ValueCell.create(invView),
  182. uModelView: ValueCell.create(modelView),
  183. uInvModelView: ValueCell.create(invModelView),
  184. uInvProjection: ValueCell.create(invProjection),
  185. uProjection: ValueCell.create(Mat4()),
  186. uModelViewProjection: ValueCell.create(modelViewProjection),
  187. uInvModelViewProjection: ValueCell.create(invModelViewProjection),
  188. uIsOrtho: ValueCell.create(1),
  189. uViewOffset: ValueCell.create(viewOffset),
  190. uPixelRatio: ValueCell.create(ctx.pixelRatio),
  191. uViewportHeight: ValueCell.create(viewport.height),
  192. uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)),
  193. uDrawingBufferSize: ValueCell.create(drawingBufferSize),
  194. uCameraPosition: ValueCell.create(Vec3()),
  195. uCameraDir: ValueCell.create(cameraDir),
  196. uNear: ValueCell.create(1),
  197. uFar: ValueCell.create(10000),
  198. uFogNear: ValueCell.create(1),
  199. uFogFar: ValueCell.create(10000),
  200. uFogColor: ValueCell.create(bgColor),
  201. uRenderWboit: ValueCell.create(false),
  202. uTransparentBackground: ValueCell.create(false),
  203. uClipObjectType: ValueCell.create(clip.objects.type),
  204. uClipObjectPosition: ValueCell.create(clip.objects.position),
  205. uClipObjectRotation: ValueCell.create(clip.objects.rotation),
  206. uClipObjectScale: ValueCell.create(clip.objects.scale),
  207. // the following are general 'material' uniforms
  208. uLightIntensity: ValueCell.create(style.lightIntensity),
  209. uAmbientIntensity: ValueCell.create(style.ambientIntensity),
  210. uMetalness: ValueCell.create(style.metalness),
  211. uRoughness: ValueCell.create(style.roughness),
  212. uReflectivity: ValueCell.create(style.reflectivity),
  213. uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
  214. uInteriorDarkening: ValueCell.create(p.interiorDarkening),
  215. uInteriorColorFlag: ValueCell.create(p.interiorColorFlag),
  216. uInteriorColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.interiorColor)),
  217. uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
  218. uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
  219. };
  220. const globalUniformList = Object.entries(globalUniforms);
  221. let globalUniformsNeedUpdate = true;
  222. const renderObject = (r: GraphicsRenderable, variant: GraphicsRenderVariant) => {
  223. if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) {
  224. return;
  225. }
  226. let definesNeedUpdate = false;
  227. if (r.values.dClipObjectCount.ref.value !== clip.objects.count) {
  228. ValueCell.update(r.values.dClipObjectCount, clip.objects.count);
  229. definesNeedUpdate = true;
  230. }
  231. if (r.values.dClipVariant.ref.value !== clip.variant) {
  232. ValueCell.update(r.values.dClipVariant, clip.variant);
  233. definesNeedUpdate = true;
  234. }
  235. if (r.values.dTransparencyVariant.ref.value !== p.transparencyVariant) {
  236. ValueCell.update(r.values.dTransparencyVariant, p.transparencyVariant);
  237. definesNeedUpdate = true;
  238. }
  239. if (definesNeedUpdate) r.update();
  240. const program = r.getProgram(variant);
  241. if (state.currentProgramId !== program.id) {
  242. // console.log('new program')
  243. globalUniformsNeedUpdate = true;
  244. program.use();
  245. }
  246. if (globalUniformsNeedUpdate) {
  247. // console.log('globalUniformsNeedUpdate')
  248. program.setUniforms(globalUniformList);
  249. globalUniformsNeedUpdate = false;
  250. }
  251. if (r.values.dRenderMode) { // indicates direct-volume
  252. // culling done in fragment shader
  253. state.disable(gl.CULL_FACE);
  254. state.frontFace(gl.CCW);
  255. if (variant === 'colorBlended') {
  256. // depth test done manually in shader against `depthTexture`
  257. // still need to enable when fragDepth can be used to write depth
  258. if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) {
  259. state.disable(gl.DEPTH_TEST);
  260. state.depthMask(false);
  261. } else {
  262. state.enable(gl.DEPTH_TEST);
  263. state.depthMask(r.values.uAlpha.ref.value === 1.0);
  264. }
  265. }
  266. } else {
  267. if (r.values.dDoubleSided) {
  268. if (r.values.dDoubleSided.ref.value || r.values.hasReflection.ref.value) {
  269. state.disable(gl.CULL_FACE);
  270. } else {
  271. state.enable(gl.CULL_FACE);
  272. }
  273. } else {
  274. // webgl default
  275. state.disable(gl.CULL_FACE);
  276. }
  277. if (r.values.dFlipSided) {
  278. if (r.values.dFlipSided.ref.value) {
  279. state.frontFace(gl.CW);
  280. state.cullFace(gl.FRONT);
  281. } else {
  282. state.frontFace(gl.CCW);
  283. state.cullFace(gl.BACK);
  284. }
  285. } else {
  286. // webgl default
  287. state.frontFace(gl.CCW);
  288. state.cullFace(gl.BACK);
  289. }
  290. }
  291. r.render(variant, sharedTexturesList);
  292. };
  293. const update = (camera: ICamera) => {
  294. ValueCell.update(globalUniforms.uView, camera.view);
  295. ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view));
  296. ValueCell.update(globalUniforms.uProjection, camera.projection);
  297. ValueCell.update(globalUniforms.uInvProjection, Mat4.invert(invProjection, camera.projection));
  298. ValueCell.updateIfChanged(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0);
  299. ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0));
  300. ValueCell.update(globalUniforms.uCameraPosition, camera.state.position);
  301. ValueCell.update(globalUniforms.uCameraDir, Vec3.normalize(cameraDir, Vec3.sub(cameraDir, camera.state.target, camera.state.position)));
  302. ValueCell.updateIfChanged(globalUniforms.uFar, camera.far);
  303. ValueCell.updateIfChanged(globalUniforms.uNear, camera.near);
  304. ValueCell.updateIfChanged(globalUniforms.uFogFar, camera.fogFar);
  305. ValueCell.updateIfChanged(globalUniforms.uFogNear, camera.fogNear);
  306. ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground);
  307. };
  308. const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean) => {
  309. arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture);
  310. ValueCell.update(globalUniforms.uModel, group.view);
  311. ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view));
  312. ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView));
  313. ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection));
  314. ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection));
  315. ValueCell.updateIfChanged(globalUniforms.uRenderWboit, renderWboit);
  316. state.enable(gl.SCISSOR_TEST);
  317. state.colorMask(true, true, true, true);
  318. const { x, y, width, height } = viewport;
  319. gl.viewport(x, y, width, height);
  320. gl.scissor(x, y, width, height);
  321. globalUniformsNeedUpdate = true;
  322. state.currentRenderItemId = -1;
  323. };
  324. const renderPick = (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null) => {
  325. state.disable(gl.BLEND);
  326. state.enable(gl.DEPTH_TEST);
  327. state.depthMask(true);
  328. updateInternal(group, camera, depthTexture, false);
  329. const { renderables } = group;
  330. for (let i = 0, il = renderables.length; i < il; ++i) {
  331. if (!renderables[i].state.colorOnly) {
  332. renderObject(renderables[i], variant);
  333. }
  334. }
  335. };
  336. const renderDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  337. state.disable(gl.BLEND);
  338. state.enable(gl.DEPTH_TEST);
  339. state.depthMask(true);
  340. updateInternal(group, camera, depthTexture, false);
  341. const { renderables } = group;
  342. for (let i = 0, il = renderables.length; i < il; ++i) {
  343. renderObject(renderables[i], 'depth');
  344. }
  345. };
  346. const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  347. renderBlendedOpaque(group, camera, depthTexture);
  348. renderBlendedTransparent(group, camera, depthTexture);
  349. };
  350. const renderBlendedOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  351. state.disable(gl.BLEND);
  352. state.enable(gl.DEPTH_TEST);
  353. state.depthMask(true);
  354. updateInternal(group, camera, depthTexture, false);
  355. const { renderables } = group;
  356. for (let i = 0, il = renderables.length; i < il; ++i) {
  357. const r = renderables[i];
  358. if (r.state.opaque) {
  359. renderObject(r, 'colorBlended');
  360. }
  361. }
  362. };
  363. const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  364. state.enable(gl.DEPTH_TEST);
  365. state.depthMask(true);
  366. updateInternal(group, camera, depthTexture, false);
  367. const { renderables } = group;
  368. if (transparentBackground) {
  369. state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  370. } else {
  371. state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  372. }
  373. state.enable(gl.BLEND);
  374. state.depthMask(true);
  375. for (let i = 0, il = renderables.length; i < il; ++i) {
  376. const r = renderables[i];
  377. if (!r.state.opaque && r.state.writeDepth) {
  378. renderObject(r, 'colorBlended');
  379. }
  380. }
  381. state.depthMask(false);
  382. for (let i = 0, il = renderables.length; i < il; ++i) {
  383. const r = renderables[i];
  384. if (!r.state.opaque && !r.state.writeDepth) {
  385. renderObject(r, 'colorBlended');
  386. }
  387. }
  388. };
  389. const renderBlendedVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  390. state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  391. state.enable(gl.BLEND);
  392. updateInternal(group, camera, depthTexture, false);
  393. const { renderables } = group;
  394. for (let i = 0, il = renderables.length; i < il; ++i) {
  395. const r = renderables[i];
  396. renderObject(r, 'colorBlended');
  397. }
  398. };
  399. const renderWboitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  400. state.disable(gl.BLEND);
  401. state.enable(gl.DEPTH_TEST);
  402. state.depthMask(true);
  403. updateInternal(group, camera, depthTexture, false);
  404. const { renderables } = group;
  405. for (let i = 0, il = renderables.length; i < il; ++i) {
  406. const r = renderables[i];
  407. // TODO: simplify, handle on renderable.state???
  408. if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value) {
  409. renderObject(r, 'colorWboit');
  410. }
  411. }
  412. };
  413. const renderWboitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  414. updateInternal(group, camera, depthTexture, true);
  415. const { renderables } = group;
  416. for (let i = 0, il = renderables.length; i < il; ++i) {
  417. const r = renderables[i];
  418. // TODO: simplify, handle on renderable.state???
  419. if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor) {
  420. renderObject(r, 'colorWboit');
  421. }
  422. }
  423. };
  424. return {
  425. clear: (toBackgroundColor: boolean) => {
  426. state.enable(gl.SCISSOR_TEST);
  427. state.enable(gl.DEPTH_TEST);
  428. state.colorMask(true, true, true, true);
  429. state.depthMask(true);
  430. if (transparentBackground) {
  431. state.clearColor(0, 0, 0, 0);
  432. } else if (toBackgroundColor) {
  433. state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1);
  434. } else {
  435. state.clearColor(1, 1, 1, 1);
  436. }
  437. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  438. },
  439. clearDepth: () => {
  440. state.enable(gl.SCISSOR_TEST);
  441. state.enable(gl.DEPTH_TEST);
  442. state.depthMask(true);
  443. gl.clear(gl.DEPTH_BUFFER_BIT);
  444. },
  445. update,
  446. renderPick,
  447. renderDepth,
  448. renderBlended,
  449. renderBlendedOpaque,
  450. renderBlendedTransparent,
  451. renderBlendedVolume,
  452. renderWboitOpaque,
  453. renderWboitTransparent,
  454. setProps: (props: Partial<RendererProps>) => {
  455. if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
  456. p.backgroundColor = props.backgroundColor;
  457. Color.toVec3Normalized(bgColor, p.backgroundColor);
  458. ValueCell.update(globalUniforms.uFogColor, Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor));
  459. }
  460. if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== p.pickingAlphaThreshold) {
  461. p.pickingAlphaThreshold = props.pickingAlphaThreshold;
  462. ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold);
  463. }
  464. if (props.transparencyVariant !== undefined && props.transparencyVariant !== p.transparencyVariant) {
  465. p.transparencyVariant = props.transparencyVariant;
  466. }
  467. if (props.interiorDarkening !== undefined && props.interiorDarkening !== p.interiorDarkening) {
  468. p.interiorDarkening = props.interiorDarkening;
  469. ValueCell.update(globalUniforms.uInteriorDarkening, p.interiorDarkening);
  470. }
  471. if (props.interiorColorFlag !== undefined && props.interiorColorFlag !== p.interiorColorFlag) {
  472. p.interiorColorFlag = props.interiorColorFlag;
  473. ValueCell.update(globalUniforms.uInteriorColorFlag, p.interiorColorFlag);
  474. }
  475. if (props.interiorColor !== undefined && props.interiorColor !== p.interiorColor) {
  476. p.interiorColor = props.interiorColor;
  477. ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor));
  478. }
  479. if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) {
  480. p.highlightColor = props.highlightColor;
  481. ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor));
  482. }
  483. if (props.selectColor !== undefined && props.selectColor !== p.selectColor) {
  484. p.selectColor = props.selectColor;
  485. ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor));
  486. }
  487. if (props.style !== undefined) {
  488. p.style = props.style;
  489. Object.assign(style, getStyle(props.style));
  490. ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity);
  491. ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity);
  492. ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness);
  493. ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness);
  494. ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity);
  495. }
  496. if (props.clip !== undefined && !deepEqual(props.clip, p.clip)) {
  497. p.clip = props.clip;
  498. Object.assign(clip, getClip(props.clip, clip));
  499. ValueCell.update(globalUniforms.uClipObjectPosition, clip.objects.position);
  500. ValueCell.update(globalUniforms.uClipObjectRotation, clip.objects.rotation);
  501. ValueCell.update(globalUniforms.uClipObjectScale, clip.objects.scale);
  502. ValueCell.update(globalUniforms.uClipObjectType, clip.objects.type);
  503. }
  504. },
  505. setViewport: (x: number, y: number, width: number, height: number) => {
  506. gl.viewport(x, y, width, height);
  507. gl.scissor(x, y, width, height);
  508. if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) {
  509. Viewport.set(viewport, x, y, width, height);
  510. ValueCell.update(globalUniforms.uViewportHeight, height);
  511. ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height));
  512. }
  513. },
  514. setTransparentBackground: (value: boolean) => {
  515. transparentBackground = value;
  516. },
  517. setDrawingBufferSize: (width: number, height: number) => {
  518. if (width !== drawingBufferSize[0] || height !== drawingBufferSize[1]) {
  519. ValueCell.update(globalUniforms.uDrawingBufferSize, Vec2.set(drawingBufferSize, width, height));
  520. }
  521. },
  522. get props() {
  523. return p;
  524. },
  525. get stats(): RendererStats {
  526. return {
  527. programCount: ctx.stats.resourceCounts.program,
  528. shaderCount: ctx.stats.resourceCounts.shader,
  529. attributeCount: ctx.stats.resourceCounts.attribute,
  530. elementsCount: ctx.stats.resourceCounts.elements,
  531. framebufferCount: ctx.stats.resourceCounts.framebuffer,
  532. renderbufferCount: ctx.stats.resourceCounts.renderbuffer,
  533. textureCount: ctx.stats.resourceCounts.texture,
  534. vertexArrayCount: ctx.stats.resourceCounts.vertexArray,
  535. drawCount: stats.drawCount,
  536. instanceCount: stats.instanceCount,
  537. instancedDrawCount: stats.instancedDrawCount,
  538. };
  539. },
  540. dispose: () => {
  541. // TODO
  542. }
  543. };
  544. }
  545. }
  546. export default Renderer;