renderer.ts 34 KB

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