renderer.ts 34 KB

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