renderer.ts 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. /**
  2. * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author Gianluca Tomasello <giagitom@gmail.com>
  6. */
  7. import { Viewport } from '../mol-canvas3d/camera/util';
  8. import { ICamera } from '../mol-canvas3d/camera';
  9. import { Scene } from './scene';
  10. import { WebGLContext } from './webgl/context';
  11. import { Mat4, Vec3, Vec4, Vec2 } from '../mol-math/linear-algebra';
  12. import { GraphicsRenderable } from './renderable';
  13. import { Color } from '../mol-util/color';
  14. import { ValueCell, deepEqual } from '../mol-util';
  15. import { GlobalUniformValues } from './renderable/schema';
  16. import { GraphicsRenderVariant } from './webgl/render-item';
  17. import { ParamDefinition as PD } from '../mol-util/param-definition';
  18. import { degToRad } from '../mol-math/misc';
  19. import { Texture, Textures } from './webgl/texture';
  20. import { arrayMapUpsert } from '../mol-util/array';
  21. import { clamp } from '../mol-math/interpolate';
  22. import { isTimingMode } from '../mol-util/debug';
  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. export enum PickType {
  37. None = 0,
  38. Object = 1,
  39. Instance = 2,
  40. Group = 3,
  41. }
  42. export enum MarkingType {
  43. None = 0,
  44. Depth = 1,
  45. Mask = 2,
  46. }
  47. interface Renderer {
  48. readonly stats: RendererStats
  49. readonly props: Readonly<RendererProps>
  50. readonly light: Readonly<Light>
  51. clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => void
  52. clearDepth: (packed?: boolean) => void
  53. update: (camera: ICamera, scene: Scene) => void
  54. renderPick: (group: Scene.Group, camera: ICamera, variant: 'pick' | 'depth', depthTexture: Texture | null, pickType: PickType) => void
  55. renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  56. renderDepthOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  57. renderDepthTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  58. renderMarkingDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  59. renderMarkingMask: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  60. renderBlended: (group: Scene, camera: ICamera) => void
  61. renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  62. renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  63. renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  64. renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  65. renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  66. renderDpoitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  67. renderDpoitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => void
  68. renderDpoitVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
  69. setProps: (props: Partial<RendererProps>) => void
  70. setViewport: (x: number, y: number, width: number, height: number) => void
  71. setTransparentBackground: (value: boolean) => void
  72. setDrawingBufferSize: (width: number, height: number) => void
  73. setPixelRatio: (value: number) => void
  74. dispose: () => void
  75. }
  76. export const RendererParams = {
  77. backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
  78. 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.' }),
  79. interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
  80. interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }),
  81. interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
  82. colorMarker: PD.Boolean(true, { description: 'Enable color marker' }),
  83. highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
  84. selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
  85. dimColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
  86. highlightStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
  87. selectStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
  88. dimStrength: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.1 }),
  89. markerPriority: PD.Select(1, [[1, 'Highlight'], [2, 'Select']]),
  90. xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
  91. exposure: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.01 }),
  92. light: PD.ObjectList({
  93. inclination: PD.Numeric(150, { min: 0, max: 180, step: 1 }),
  94. azimuth: PD.Numeric(320, { min: 0, max: 360, step: 1 }),
  95. color: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
  96. intensity: PD.Numeric(0.6, { min: 0.0, max: 5.0, step: 0.01 }),
  97. }, o => Color.toHexString(o.color), { defaultValue: [{
  98. inclination: 150,
  99. azimuth: 320,
  100. color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
  101. intensity: 0.6
  102. }] }),
  103. ambientColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
  104. ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 2.0, step: 0.01 }),
  105. };
  106. export type RendererProps = PD.Values<typeof RendererParams>
  107. export type Light = {
  108. count: number
  109. direction: number[]
  110. color: number[]
  111. }
  112. const tmpDir = Vec3();
  113. const tmpColor = Vec3();
  114. function getLight(props: RendererProps['light'], light?: Light): Light {
  115. const count = props.length;
  116. const { direction, color } = light || {
  117. direction: (new Array(count * 3)).fill(0),
  118. color: (new Array(count * 3)).fill(0),
  119. };
  120. for (let i = 0; i < count; ++i) {
  121. const p = props[i];
  122. Vec3.directionFromSpherical(tmpDir, degToRad(p.inclination), degToRad(p.azimuth), 1);
  123. Vec3.toArray(tmpDir, direction, i * 3);
  124. Vec3.scale(tmpColor, Color.toVec3Normalized(tmpColor, p.color), p.intensity);
  125. Vec3.toArray(tmpColor, color, i * 3);
  126. }
  127. return { count, direction, color };
  128. }
  129. namespace Renderer {
  130. const enum Flag {
  131. None = 0,
  132. BlendedFront = 1,
  133. BlendedBack = 2,
  134. }
  135. const enum Mask {
  136. All = 0,
  137. Opaque = 1,
  138. Transparent = 2,
  139. }
  140. export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
  141. const { gl, state, stats } = ctx;
  142. const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
  143. const light = getLight(p.light);
  144. const viewport = Viewport();
  145. const drawingBufferSize = Vec2.create(gl.drawingBufferWidth, gl.drawingBufferHeight);
  146. const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor);
  147. let transparentBackground = false;
  148. const emptyDepthTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
  149. emptyDepthTexture.define(1, 1);
  150. emptyDepthTexture.load({ array: new Uint8Array([255, 255, 255, 255]), width: 1, height: 1 });
  151. const sharedTexturesList: Textures = [
  152. ['tDepth', emptyDepthTexture]
  153. ];
  154. const view = Mat4();
  155. const invView = Mat4();
  156. const modelView = Mat4();
  157. const invModelView = Mat4();
  158. const invProjection = Mat4();
  159. const modelViewProjection = Mat4();
  160. const invModelViewProjection = Mat4();
  161. const cameraDir = Vec3();
  162. const viewOffset = Vec2();
  163. const ambientColor = Vec3();
  164. Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
  165. const globalUniforms: GlobalUniformValues = {
  166. uModel: ValueCell.create(Mat4.identity()),
  167. uView: ValueCell.create(view),
  168. uInvView: ValueCell.create(invView),
  169. uModelView: ValueCell.create(modelView),
  170. uInvModelView: ValueCell.create(invModelView),
  171. uInvProjection: ValueCell.create(invProjection),
  172. uProjection: ValueCell.create(Mat4()),
  173. uModelViewProjection: ValueCell.create(modelViewProjection),
  174. uInvModelViewProjection: ValueCell.create(invModelViewProjection),
  175. uIsOrtho: ValueCell.create(1),
  176. uViewOffset: ValueCell.create(viewOffset),
  177. uPixelRatio: ValueCell.create(ctx.pixelRatio),
  178. uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)),
  179. uDrawingBufferSize: ValueCell.create(drawingBufferSize),
  180. uCameraPosition: ValueCell.create(Vec3()),
  181. uCameraDir: ValueCell.create(cameraDir),
  182. uNear: ValueCell.create(1),
  183. uFar: ValueCell.create(10000),
  184. uFogNear: ValueCell.create(1),
  185. uFogFar: ValueCell.create(10000),
  186. uFogColor: ValueCell.create(bgColor),
  187. uRenderMask: ValueCell.create(0),
  188. uMarkingDepthTest: ValueCell.create(false),
  189. uPickType: ValueCell.create(PickType.None),
  190. uMarkingType: ValueCell.create(MarkingType.None),
  191. uTransparentBackground: ValueCell.create(false),
  192. uLightDirection: ValueCell.create(light.direction),
  193. uLightColor: ValueCell.create(light.color),
  194. uAmbientColor: ValueCell.create(ambientColor),
  195. uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
  196. uInteriorDarkening: ValueCell.create(p.interiorDarkening),
  197. uInteriorColorFlag: ValueCell.create(p.interiorColorFlag),
  198. uInteriorColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.interiorColor)),
  199. uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
  200. uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
  201. uDimColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.dimColor)),
  202. uHighlightStrength: ValueCell.create(p.highlightStrength),
  203. uSelectStrength: ValueCell.create(p.selectStrength),
  204. uDimStrength: ValueCell.create(p.dimStrength),
  205. uMarkerPriority: ValueCell.create(p.markerPriority),
  206. uMarkerAverage: ValueCell.create(0),
  207. uXrayEdgeFalloff: ValueCell.create(p.xrayEdgeFalloff),
  208. uExposure: ValueCell.create(p.exposure),
  209. };
  210. const globalUniformList = Object.entries(globalUniforms);
  211. let globalUniformsNeedUpdate = true;
  212. const renderObject = (r: GraphicsRenderable, variant: GraphicsRenderVariant, flag: Flag) => {
  213. if (r.state.disposed || !r.state.visible || (!r.state.pickable && variant === 'pick')) {
  214. return;
  215. }
  216. let definesNeedUpdate = false;
  217. if (r.values.dLightCount.ref.value !== light.count) {
  218. ValueCell.update(r.values.dLightCount, light.count);
  219. definesNeedUpdate = true;
  220. }
  221. if (r.values.dColorMarker.ref.value !== p.colorMarker) {
  222. ValueCell.update(r.values.dColorMarker, p.colorMarker);
  223. definesNeedUpdate = true;
  224. }
  225. if (definesNeedUpdate) r.update();
  226. const program = r.getProgram(variant);
  227. if (state.currentProgramId !== program.id) {
  228. // console.log('new program')
  229. globalUniformsNeedUpdate = true;
  230. program.use();
  231. }
  232. if (globalUniformsNeedUpdate) {
  233. // console.log('globalUniformsNeedUpdate')
  234. program.setUniforms(globalUniformList);
  235. program.bindTextures(sharedTexturesList, 0);
  236. globalUniformsNeedUpdate = false;
  237. }
  238. if (r.values.dGeometryType.ref.value === 'directVolume') {
  239. if (variant !== 'colorDpoit' && variant !== 'colorWboit' && variant !== 'colorBlended') {
  240. return; // only color supported
  241. }
  242. // culling done in fragment shader
  243. state.disable(gl.CULL_FACE);
  244. state.frontFace(gl.CCW);
  245. if (variant === 'colorBlended') {
  246. // depth test done manually in shader against `depthTexture`
  247. state.disable(gl.DEPTH_TEST);
  248. state.depthMask(false);
  249. }
  250. } else if (flag === Flag.BlendedFront) {
  251. state.enable(gl.CULL_FACE);
  252. if (r.values.dFlipSided?.ref.value) {
  253. state.frontFace(gl.CW);
  254. state.cullFace(gl.FRONT);
  255. } else {
  256. state.frontFace(gl.CCW);
  257. state.cullFace(gl.BACK);
  258. }
  259. } else if (flag === Flag.BlendedBack) {
  260. state.enable(gl.CULL_FACE);
  261. if (r.values.dFlipSided?.ref.value) {
  262. state.frontFace(gl.CW);
  263. state.cullFace(gl.BACK);
  264. } else {
  265. state.frontFace(gl.CCW);
  266. state.cullFace(gl.FRONT);
  267. }
  268. } else {
  269. if (r.values.uDoubleSided) {
  270. if (r.values.uDoubleSided.ref.value || r.values.hasReflection.ref.value) {
  271. state.disable(gl.CULL_FACE);
  272. } else {
  273. state.enable(gl.CULL_FACE);
  274. }
  275. } else {
  276. // webgl default
  277. state.disable(gl.CULL_FACE);
  278. }
  279. if (r.values.dFlipSided?.ref.value) {
  280. state.frontFace(gl.CW);
  281. state.cullFace(gl.FRONT);
  282. } else {
  283. // webgl default
  284. state.frontFace(gl.CCW);
  285. state.cullFace(gl.BACK);
  286. }
  287. }
  288. r.render(variant, sharedTexturesList.length);
  289. };
  290. const update = (camera: ICamera, scene: Scene) => {
  291. ValueCell.update(globalUniforms.uView, camera.view);
  292. ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view));
  293. ValueCell.update(globalUniforms.uProjection, camera.projection);
  294. ValueCell.update(globalUniforms.uInvProjection, Mat4.invert(invProjection, camera.projection));
  295. ValueCell.updateIfChanged(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0);
  296. ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0));
  297. ValueCell.update(globalUniforms.uCameraPosition, camera.state.position);
  298. ValueCell.update(globalUniforms.uCameraDir, Vec3.normalize(cameraDir, Vec3.sub(cameraDir, camera.state.target, camera.state.position)));
  299. ValueCell.updateIfChanged(globalUniforms.uFar, camera.far);
  300. ValueCell.updateIfChanged(globalUniforms.uNear, camera.near);
  301. ValueCell.updateIfChanged(globalUniforms.uFogFar, camera.fogFar);
  302. ValueCell.updateIfChanged(globalUniforms.uFogNear, camera.fogNear);
  303. ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground);
  304. ValueCell.updateIfChanged(globalUniforms.uMarkerAverage, scene.markerAverage);
  305. };
  306. const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderMask: Mask, markingDepthTest: boolean) => {
  307. arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || emptyDepthTexture);
  308. ValueCell.update(globalUniforms.uModel, group.view);
  309. ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, camera.view, group.view));
  310. ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView));
  311. ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection));
  312. ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection));
  313. ValueCell.updateIfChanged(globalUniforms.uRenderMask, renderMask);
  314. ValueCell.updateIfChanged(globalUniforms.uMarkingDepthTest, markingDepthTest);
  315. state.enable(gl.SCISSOR_TEST);
  316. state.colorMask(true, true, true, true);
  317. const { x, y, width, height } = viewport;
  318. state.viewport(x, y, width, height);
  319. state.scissor(x, y, width, height);
  320. globalUniformsNeedUpdate = true;
  321. state.currentRenderItemId = -1;
  322. };
  323. const renderPick = (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null, pickType: PickType) => {
  324. if (isTimingMode) ctx.timer.mark('Renderer.renderPick');
  325. state.disable(gl.BLEND);
  326. state.enable(gl.DEPTH_TEST);
  327. state.depthMask(true);
  328. updateInternal(group, camera, depthTexture, Mask.All, false);
  329. ValueCell.updateIfChanged(globalUniforms.uPickType, pickType);
  330. const { renderables } = group;
  331. for (let i = 0, il = renderables.length; i < il; ++i) {
  332. if (!renderables[i].state.colorOnly) {
  333. renderObject(renderables[i], variant, Flag.None);
  334. }
  335. }
  336. if (isTimingMode) ctx.timer.markEnd('Renderer.renderPick');
  337. };
  338. const renderDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  339. if (isTimingMode) ctx.timer.mark('Renderer.renderDepth');
  340. state.disable(gl.BLEND);
  341. state.enable(gl.DEPTH_TEST);
  342. state.depthMask(true);
  343. updateInternal(group, camera, depthTexture, Mask.All, false);
  344. const { renderables } = group;
  345. for (let i = 0, il = renderables.length; i < il; ++i) {
  346. renderObject(renderables[i], 'depth', Flag.None);
  347. }
  348. if (isTimingMode) ctx.timer.markEnd('Renderer.renderDepth');
  349. };
  350. const renderDepthOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  351. if (isTimingMode) ctx.timer.mark('Renderer.renderDepthOpaque');
  352. state.disable(gl.BLEND);
  353. state.enable(gl.DEPTH_TEST);
  354. state.depthMask(true);
  355. updateInternal(group, camera, depthTexture, Mask.Opaque, false);
  356. const { renderables } = group;
  357. for (let i = 0, il = renderables.length; i < il; ++i) {
  358. const r = renderables[i];
  359. if (r.state.opaque && r.values.transparencyAverage.ref.value !== 1 && r.values.dXrayShaded?.ref.value === 'off') {
  360. renderObject(r, 'depth', Flag.None);
  361. }
  362. }
  363. if (isTimingMode) ctx.timer.markEnd('Renderer.renderDepthOpaque');
  364. };
  365. const renderDepthTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  366. if (isTimingMode) ctx.timer.mark('Renderer.renderDepthTransparent');
  367. state.disable(gl.BLEND);
  368. state.enable(gl.DEPTH_TEST);
  369. state.depthMask(true);
  370. updateInternal(group, camera, depthTexture, Mask.Transparent, false);
  371. const { renderables } = group;
  372. for (let i = 0, il = renderables.length; i < il; ++i) {
  373. const r = renderables[i];
  374. if (!r.state.opaque || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value !== 'off') {
  375. renderObject(r, 'depth', Flag.None);
  376. }
  377. }
  378. if (isTimingMode) ctx.timer.markEnd('Renderer.renderDepthTransparent');
  379. };
  380. const renderMarkingDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  381. if (isTimingMode) ctx.timer.mark('Renderer.renderMarkingDepth');
  382. state.disable(gl.BLEND);
  383. state.enable(gl.DEPTH_TEST);
  384. state.depthMask(true);
  385. updateInternal(group, camera, depthTexture, Mask.All, false);
  386. ValueCell.updateIfChanged(globalUniforms.uMarkingType, MarkingType.Depth);
  387. const { renderables } = group;
  388. for (let i = 0, il = renderables.length; i < il; ++i) {
  389. const r = renderables[i];
  390. const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
  391. if (alpha !== 0 && r.values.markerAverage.ref.value !== 1) {
  392. renderObject(renderables[i], 'marking', Flag.None);
  393. }
  394. }
  395. if (isTimingMode) ctx.timer.markEnd('Renderer.renderMarkingDepth');
  396. };
  397. const renderMarkingMask = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  398. if (isTimingMode) ctx.timer.mark('Renderer.renderMarkingMask');
  399. state.disable(gl.BLEND);
  400. state.enable(gl.DEPTH_TEST);
  401. state.depthMask(true);
  402. updateInternal(group, camera, depthTexture, Mask.All, !!depthTexture);
  403. ValueCell.updateIfChanged(globalUniforms.uMarkingType, MarkingType.Mask);
  404. const { renderables } = group;
  405. for (let i = 0, il = renderables.length; i < il; ++i) {
  406. const r = renderables[i];
  407. if (r.values.markerAverage.ref.value > 0) {
  408. renderObject(renderables[i], 'marking', Flag.None);
  409. }
  410. }
  411. if (isTimingMode) ctx.timer.markEnd('Renderer.renderMarkingMask');
  412. };
  413. const renderBlended = (scene: Scene, camera: ICamera) => {
  414. if (scene.hasOpaque) {
  415. renderBlendedOpaque(scene, camera, null);
  416. }
  417. if (scene.opacityAverage < 1) {
  418. renderBlendedTransparent(scene, camera, null);
  419. }
  420. };
  421. const renderBlendedOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  422. if (isTimingMode) ctx.timer.mark('Renderer.renderBlendedOpaque');
  423. state.disable(gl.BLEND);
  424. state.enable(gl.DEPTH_TEST);
  425. state.depthMask(true);
  426. updateInternal(group, camera, depthTexture, Mask.Opaque, false);
  427. const { renderables } = group;
  428. for (let i = 0, il = renderables.length; i < il; ++i) {
  429. const r = renderables[i];
  430. if (r.state.opaque) {
  431. renderObject(r, 'colorBlended', Flag.None);
  432. } else if (r.values.uDoubleSided?.ref.value && r.values.dTransparentBackfaces?.ref.value === 'opaque') {
  433. renderObject(r, 'colorBlended', Flag.BlendedBack);
  434. }
  435. }
  436. if (isTimingMode) ctx.timer.markEnd('Renderer.renderBlendedOpaque');
  437. };
  438. const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  439. if (isTimingMode) ctx.timer.mark('Renderer.renderBlendedTransparent');
  440. state.enable(gl.DEPTH_TEST);
  441. updateInternal(group, camera, depthTexture, Mask.Transparent, false);
  442. const { renderables } = group;
  443. if (transparentBackground) {
  444. state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  445. } else {
  446. state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  447. }
  448. state.enable(gl.BLEND);
  449. state.depthMask(true);
  450. for (let i = 0, il = renderables.length; i < il; ++i) {
  451. const r = renderables[i];
  452. if (!r.state.opaque && r.state.writeDepth) {
  453. renderObject(r, 'colorBlended', Flag.None);
  454. }
  455. }
  456. state.depthMask(false);
  457. for (let i = 0, il = renderables.length; i < il; ++i) {
  458. const r = renderables[i];
  459. if ((!r.state.opaque && !r.state.writeDepth) || r.values.transparencyAverage.ref.value > 0) {
  460. if (r.values.uDoubleSided?.ref.value) {
  461. // render frontfaces and backfaces separately to avoid artefacts
  462. if (r.values.dTransparentBackfaces?.ref.value !== 'opaque') {
  463. renderObject(r, 'colorBlended', Flag.BlendedBack);
  464. }
  465. renderObject(r, 'colorBlended', Flag.BlendedFront);
  466. } else {
  467. renderObject(r, 'colorBlended', Flag.None);
  468. }
  469. }
  470. }
  471. if (isTimingMode) ctx.timer.markEnd('Renderer.renderBlendedTransparent');
  472. };
  473. const renderBlendedVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  474. if (isTimingMode) ctx.timer.mark('Renderer.renderBlendedVolume');
  475. state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  476. state.enable(gl.BLEND);
  477. updateInternal(group, camera, depthTexture, Mask.Transparent, false);
  478. const { renderables } = group;
  479. for (let i = 0, il = renderables.length; i < il; ++i) {
  480. const r = renderables[i];
  481. if (r.values.dGeometryType.ref.value === 'directVolume') {
  482. renderObject(r, 'colorBlended', Flag.None);
  483. }
  484. }
  485. if (isTimingMode) ctx.timer.markEnd('Renderer.renderBlendedVolume');
  486. };
  487. const renderWboitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  488. if (isTimingMode) ctx.timer.mark('Renderer.renderWboitOpaque');
  489. state.disable(gl.BLEND);
  490. state.enable(gl.DEPTH_TEST);
  491. state.depthMask(true);
  492. updateInternal(group, camera, depthTexture, Mask.Opaque, false);
  493. const { renderables } = group;
  494. for (let i = 0, il = renderables.length; i < il; ++i) {
  495. const r = renderables[i];
  496. // TODO: simplify, handle in renderable.state???
  497. // uAlpha is updated in "render" so we need to recompute it here
  498. const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
  499. if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && r.values.dXrayShaded?.ref.value === 'off') || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
  500. renderObject(r, 'colorWboit', Flag.None);
  501. }
  502. }
  503. if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitOpaque');
  504. };
  505. const renderWboitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  506. if (isTimingMode) ctx.timer.mark('Renderer.renderWboitTransparent');
  507. updateInternal(group, camera, depthTexture, Mask.Transparent, false);
  508. const { renderables } = group;
  509. for (let i = 0, il = renderables.length; i < il; ++i) {
  510. const r = renderables[i];
  511. // TODO: simplify, handle in renderable.state???
  512. // uAlpha is updated in "render" so we need to recompute it here
  513. const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
  514. if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || r.values.dXrayShaded?.ref.value !== 'off') {
  515. renderObject(r, 'colorWboit', Flag.None);
  516. }
  517. }
  518. if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitTransparent');
  519. };
  520. const renderDpoitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  521. if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitOpaque');
  522. state.disable(gl.BLEND);
  523. state.enable(gl.DEPTH_TEST);
  524. state.depthMask(true);
  525. updateInternal(group, camera, depthTexture, Mask.Opaque, false);
  526. const { renderables } = group;
  527. for (let i = 0, il = renderables.length; i < il; ++i) {
  528. const r = renderables[i];
  529. // TODO: simplify, handle in renderable.state???
  530. // uAlpha is updated in "render" so we need to recompute it here
  531. const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
  532. if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && r.values.dXrayShaded?.ref.value === 'off') || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
  533. renderObject(r, 'colorDpoit', Flag.None);
  534. }
  535. }
  536. if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitOpaque');
  537. };
  538. const renderDpoitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => {
  539. if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitTransparent');
  540. state.enable(gl.BLEND);
  541. arrayMapUpsert(sharedTexturesList, 'tDpoitDepth', dpoitTextures.depth);
  542. arrayMapUpsert(sharedTexturesList, 'tDpoitFrontColor', dpoitTextures.frontColor);
  543. arrayMapUpsert(sharedTexturesList, 'tDpoitBackColor', dpoitTextures.backColor);
  544. updateInternal(group, camera, depthTexture, Mask.Transparent, false);
  545. const { renderables } = group;
  546. for (let i = 0, il = renderables.length; i < il; ++i) {
  547. const r = renderables[i];
  548. // TODO: simplify, handle in renderable.state???
  549. // uAlpha is updated in "render" so we need to recompute it here
  550. const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
  551. if ((alpha < 1 && alpha !== 0) || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || r.values.dXrayShaded?.ref.value !== 'off') {
  552. renderObject(r, 'colorDpoit', Flag.None);
  553. }
  554. }
  555. if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitTransparent');
  556. };
  557. const renderDpoitVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
  558. if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitVolume');
  559. state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  560. state.enable(gl.BLEND);
  561. updateInternal(group, camera, depthTexture, Mask.Transparent, false);
  562. const { renderables } = group;
  563. for (let i = 0, il = renderables.length; i < il; ++i) {
  564. const r = renderables[i];
  565. if (r.values.dGeometryType.ref.value === 'directVolume') {
  566. renderObject(r, 'colorDpoit', Flag.None);
  567. }
  568. }
  569. if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitVolume');
  570. };
  571. return {
  572. clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => {
  573. state.enable(gl.SCISSOR_TEST);
  574. state.enable(gl.DEPTH_TEST);
  575. state.colorMask(true, true, true, true);
  576. state.depthMask(true);
  577. if (transparentBackground && !ignoreTransparentBackground) {
  578. state.clearColor(0, 0, 0, 0);
  579. } else if (toBackgroundColor) {
  580. state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1);
  581. } else {
  582. state.clearColor(1, 1, 1, 1);
  583. }
  584. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  585. },
  586. clearDepth: (packed = false) => {
  587. state.enable(gl.SCISSOR_TEST);
  588. if (packed) {
  589. state.colorMask(true, true, true, true);
  590. state.clearColor(1, 1, 1, 1);
  591. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  592. } else {
  593. state.enable(gl.DEPTH_TEST);
  594. state.depthMask(true);
  595. gl.clear(gl.DEPTH_BUFFER_BIT);
  596. }
  597. },
  598. update,
  599. renderPick,
  600. renderDepth,
  601. renderDepthOpaque,
  602. renderDepthTransparent,
  603. renderMarkingDepth,
  604. renderMarkingMask,
  605. renderBlended,
  606. renderBlendedOpaque,
  607. renderBlendedTransparent,
  608. renderBlendedVolume,
  609. renderWboitOpaque,
  610. renderWboitTransparent,
  611. renderDpoitOpaque,
  612. renderDpoitTransparent,
  613. renderDpoitVolume,
  614. setProps: (props: Partial<RendererProps>) => {
  615. if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
  616. p.backgroundColor = props.backgroundColor;
  617. Color.toVec3Normalized(bgColor, p.backgroundColor);
  618. ValueCell.update(globalUniforms.uFogColor, Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor));
  619. }
  620. if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== p.pickingAlphaThreshold) {
  621. p.pickingAlphaThreshold = props.pickingAlphaThreshold;
  622. ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold);
  623. }
  624. if (props.interiorDarkening !== undefined && props.interiorDarkening !== p.interiorDarkening) {
  625. p.interiorDarkening = props.interiorDarkening;
  626. ValueCell.update(globalUniforms.uInteriorDarkening, p.interiorDarkening);
  627. }
  628. if (props.interiorColorFlag !== undefined && props.interiorColorFlag !== p.interiorColorFlag) {
  629. p.interiorColorFlag = props.interiorColorFlag;
  630. ValueCell.update(globalUniforms.uInteriorColorFlag, p.interiorColorFlag);
  631. }
  632. if (props.interiorColor !== undefined && props.interiorColor !== p.interiorColor) {
  633. p.interiorColor = props.interiorColor;
  634. ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor));
  635. }
  636. if (props.colorMarker !== undefined && props.colorMarker !== p.colorMarker) {
  637. p.colorMarker = props.colorMarker;
  638. }
  639. if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) {
  640. p.highlightColor = props.highlightColor;
  641. ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor));
  642. }
  643. if (props.selectColor !== undefined && props.selectColor !== p.selectColor) {
  644. p.selectColor = props.selectColor;
  645. ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor));
  646. }
  647. if (props.dimColor !== undefined && props.dimColor !== p.dimColor) {
  648. p.dimColor = props.dimColor;
  649. ValueCell.update(globalUniforms.uDimColor, Color.toVec3Normalized(globalUniforms.uDimColor.ref.value, p.dimColor));
  650. }
  651. if (props.highlightStrength !== undefined && props.highlightStrength !== p.highlightStrength) {
  652. p.highlightStrength = props.highlightStrength;
  653. ValueCell.update(globalUniforms.uHighlightStrength, p.highlightStrength);
  654. }
  655. if (props.selectStrength !== undefined && props.selectStrength !== p.selectStrength) {
  656. p.selectStrength = props.selectStrength;
  657. ValueCell.update(globalUniforms.uSelectStrength, p.selectStrength);
  658. }
  659. if (props.dimStrength !== undefined && props.dimStrength !== p.dimStrength) {
  660. p.dimStrength = props.dimStrength;
  661. ValueCell.update(globalUniforms.uDimStrength, p.dimStrength);
  662. }
  663. if (props.markerPriority !== undefined && props.markerPriority !== p.markerPriority) {
  664. p.markerPriority = props.markerPriority;
  665. ValueCell.update(globalUniforms.uMarkerPriority, p.markerPriority);
  666. }
  667. if (props.xrayEdgeFalloff !== undefined && props.xrayEdgeFalloff !== p.xrayEdgeFalloff) {
  668. p.xrayEdgeFalloff = props.xrayEdgeFalloff;
  669. ValueCell.update(globalUniforms.uXrayEdgeFalloff, p.xrayEdgeFalloff);
  670. }
  671. if (props.exposure !== undefined && props.exposure !== p.exposure) {
  672. p.exposure = props.exposure;
  673. ValueCell.update(globalUniforms.uExposure, p.exposure);
  674. }
  675. if (props.light !== undefined && !deepEqual(props.light, p.light)) {
  676. p.light = props.light;
  677. Object.assign(light, getLight(props.light, light));
  678. ValueCell.update(globalUniforms.uLightDirection, light.direction);
  679. ValueCell.update(globalUniforms.uLightColor, light.color);
  680. }
  681. if (props.ambientColor !== undefined && props.ambientColor !== p.ambientColor) {
  682. p.ambientColor = props.ambientColor;
  683. Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
  684. ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
  685. }
  686. if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) {
  687. p.ambientIntensity = props.ambientIntensity;
  688. Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
  689. ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
  690. }
  691. },
  692. setViewport: (x: number, y: number, width: number, height: number) => {
  693. state.viewport(x, y, width, height);
  694. state.scissor(x, y, width, height);
  695. if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) {
  696. Viewport.set(viewport, x, y, width, height);
  697. ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height));
  698. }
  699. },
  700. setTransparentBackground: (value: boolean) => {
  701. transparentBackground = value;
  702. },
  703. setDrawingBufferSize: (width: number, height: number) => {
  704. if (width !== drawingBufferSize[0] || height !== drawingBufferSize[1]) {
  705. ValueCell.update(globalUniforms.uDrawingBufferSize, Vec2.set(drawingBufferSize, width, height));
  706. }
  707. },
  708. setPixelRatio: (value: number) => {
  709. ValueCell.update(globalUniforms.uPixelRatio, value);
  710. },
  711. props: p,
  712. get stats(): RendererStats {
  713. return {
  714. programCount: ctx.stats.resourceCounts.program,
  715. shaderCount: ctx.stats.resourceCounts.shader,
  716. attributeCount: ctx.stats.resourceCounts.attribute,
  717. elementsCount: ctx.stats.resourceCounts.elements,
  718. framebufferCount: ctx.stats.resourceCounts.framebuffer,
  719. renderbufferCount: ctx.stats.resourceCounts.renderbuffer,
  720. textureCount: ctx.stats.resourceCounts.texture,
  721. vertexArrayCount: ctx.stats.resourceCounts.vertexArray,
  722. drawCount: stats.drawCount,
  723. instanceCount: stats.instanceCount,
  724. instancedDrawCount: stats.instancedDrawCount,
  725. };
  726. },
  727. get light(): Light {
  728. return light;
  729. },
  730. dispose: () => {
  731. // TODO
  732. }
  733. };
  734. }
  735. }
  736. export { Renderer };