renderer.ts 30 KB

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