Compare commits

...

175 Commits

Author SHA1 Message Date
Alexander Rose
c6f61ea06b 1.3.2 2021-03-18 22:37:34 -07:00
Alexander Rose
3095754817 add missing PRO to standard components 2021-03-18 22:23:50 -07:00
dsehnal
c76c433410 Fix getSymmetryOperatorRef indexing 2021-03-18 22:22:56 -07:00
dsehnal
347ef3ea7a Fix createModelProperty.isApplicable 2021-03-18 22:21:57 -07:00
Tomas Kulhanek
84c47738ac FIX molstar/molstar#147 offsetWidth/offsetHeight is correct size of element when css transform:scale is used 2021-03-18 22:21:00 -07:00
Alexander Rose
fe331ead80 1.3.1 2021-02-13 23:18:36 -08:00
Alexander Rose
77d013b775 webgl, ensure active attribute with divisor 0
- workaround for FF <85
- needed for `texture-mesh` geometry
2021-02-13 11:30:13 -08:00
Alexander Rose
02a466e8b9 only update repr visibility when changed
- avoids superfluous scene rendering, e.g., when animating
2021-02-13 11:27:44 -08:00
Alexander Rose
3cb65cbe3d reduce ssao quality defaults a bit
- less texture fetches
2021-02-13 11:24:07 -08:00
Alexander Rose
fe8838542c 1.3.0 2021-02-07 13:29:46 -08:00
Alexander Rose
78b5c9aac4 Merge pull request #129 from molstar/gpu
Gpu acceleration for isosurface & gaussian-surface
2021-02-07 13:25:22 -08:00
Alexander Rose
021fa7b79b clearer param names for using gpu/impostors 2021-02-07 13:23:36 -08:00
Alexander Rose
0443589b09 allow gaussian volume without blendMinMax 2021-02-07 13:18:24 -08:00
Alexander Rose
415288de9f tweak quality settings
- lower resolution
2021-02-07 12:52:10 -08:00
Alexander Rose
ecbafb086a fix mc example 2021-02-06 12:46:25 -08:00
Alexander Rose
e5dae6c0dd increased default quality for larger structures
- rational is that larger structures can take a bit longer to create
2021-02-06 12:45:00 -08:00
Alexander Rose
16f4524bdb improved gpu support for representations
- enabled in volume isosurface & structure gaussian surface
- only if suitable (check memory requirements and resolution)
- falls back to cpu code
2021-02-06 12:43:24 -08:00
Alexander Rose
6b33021f43 fix webgl stats on render-item disposal 2021-02-06 12:30:12 -08:00
Alexander Rose
fdf37100c2 improved gpu-mc
- lower memory usage
- support for 2^32 vertices in webgl2
- fix rounding issue when creating volume texture
2021-02-06 12:28:52 -08:00
Alexander Rose
e28674d0dc model format improvements
- add a source format to the mmcif format
- add pdb format
- allow undefined in typeguard .is helpers
2021-02-03 19:36:01 -08:00
Alexander Rose
fb7456286a fix label_seq_id assignment when undefined column 2021-02-03 19:32:20 -08:00
Alexander Rose
9d240f8928 allow views of undefined columns 2021-02-03 19:30:14 -08:00
David Sehnal
48ef5efb21 Merge pull request #127 from molstar/pp-res
lower resolution ssao
2021-02-01 12:59:53 +01:00
Alexander Rose
52b2e7c144 lower res ssao 2021-01-31 13:22:22 -08:00
Alexander Rose
f2d1d60f6b fix cellpack & unit creation issues
- cellpack generate color theme can be applied to 1 model trajectories
- don't clone supplied props in Unit.create
- fix cellpack structure building to share unit.props
2021-01-31 12:06:40 -08:00
dsehnal
5a176a378a 1.2.15 2021-01-31 18:32:08 +01:00
dsehnal
60151c2c24 fix getUnitsSortedByVolume 2021-01-31 18:30:13 +01:00
dsehnal
a5db6350a2 only normalize Canvas3D props when loading from a saved state 2021-01-31 18:01:42 +01:00
dsehnal
0618eb18ba 1.2.14 2021-01-31 17:10:17 +01:00
Alexander Rose
bffdff6aad gaussian surface visual improvements
- add structure.unitsSortedByVolume
- increase gaussian smoothness in coarse presets
- use slice area instead of volume to ensure reasonable resolution
- use largest units (by volume) for reasonable resolution calculation
2021-01-30 22:55:29 -08:00
Alexander Rose
7753a6ec56 renderable schema cleanup
- use base schema in direct-volume schema
2021-01-30 12:57:19 -08:00
Alexander Rose
b8aafa1d78 mol-gl improvements
- int textures (webgl2)
- read into Int32Array (webgl2)
- fix ctx.parameters.maxDrawingBuffers (webgl1)
- support setting frag out type (webgl2)
2021-01-30 12:42:48 -08:00
Alexander Rose
672875187b add renderable.state.disposed flag
- set when disposing render-objects
- don't render disposed objects (can be temporarily still in a scene)
2021-01-30 12:39:33 -08:00
Alexander Rose
547d60d573 fix texture-mesh vertex count 2021-01-30 12:33:59 -08:00
Alexander Rose
99471d2a7b add xray shading edge fallof parameter 2021-01-30 12:32:20 -08:00
Alexander Rose
45d249b71a log renderItemId in debug mode 2021-01-30 11:23:46 -08:00
Alexander Rose
1382edd81c improved trackball rotate on wide canvases 2021-01-30 11:23:15 -08:00
dsehnal
89a6102f8d 1.2.13 2021-01-30 14:49:17 +01:00
David Sehnal
163929477e Merge pull request #106 from molstar/cylinders
Cylinders geomery and link visual improvements
2021-01-30 14:45:26 +01:00
dsehnal
c10a8369e8 Canvas3d: force render on viewport resize 2021-01-30 14:10:50 +01:00
dsehnal
8fbba52de8 PD.normalizeParams update 2021-01-30 13:41:22 +01:00
dsehnal
ca3174b2c3 Fix computeUnitGaussianDensity 2021-01-30 13:08:42 +01:00
dsehnal
b9864fba80 Fix loci label custom text 2021-01-27 18:01:47 +01:00
Alexander Rose
f8e9bc1e7f limit max resolution for (gpu) gaussian-surface 2021-01-24 21:45:01 -08:00
Alexander Rose
f79f1507f7 dispose of volume & repr associated textures 2021-01-24 21:44:02 -08:00
Alexander Rose
61ab205a5d recreate visuals based on param changes
- impostor/mesh (spacefill, ball+stick, ellipsoids)
- gpu/cpu mc (isosurface, gaussian surface)
2021-01-23 16:57:21 -08:00
Alexander Rose
2c65260a4f Merge remote-tracking branch 'origin/param-normalization' into cylinders 2021-01-23 16:39:23 -08:00
Alexander Rose
0597a1ef24 Merge branch 'master' into cylinders 2021-01-23 16:35:18 -08:00
Alexander Rose
8d6557e51c moved label-options out of palette-params 2021-01-23 16:25:32 -08:00
Alexander Rose
5cff0dff3d wip, gpu mc
- int float div
- clamp 2d texture access to 3d grid bounds
2021-01-23 15:46:29 -08:00
Alexander Rose
93206e76d7 fix DVR with orthographic projection 2021-01-23 13:36:28 -08:00
dsehnal
40933a8539 1.2.12 2021-01-20 14:18:55 +01:00
dsehnal
989800783b dihedral visual update 2021-01-20 14:13:57 +01:00
dsehnal
d83b0d2c4d better typing for PD.MultiSelect & StructureMeasurementManager visualParams support 2021-01-20 10:30:59 +01:00
dsehnal
5e5d5a63dc mol-plugin: subscribe to events in initViewer
+ handle plugin resize in main render loop
2021-01-19 15:09:57 +01:00
Alexander Rose
b1755604e2 fix webgl context loss handling 2021-01-18 19:48:55 -08:00
Alexander Rose
e58da9b574 add Canvas3DContext
- can be used to create multiple Canvas3D objects
2021-01-18 11:30:42 -08:00
dsehnal
f5d6498601 ParamDefinition.normalizeParams tweaks 2021-01-17 11:50:15 +01:00
dsehnal
07f351888f add doNotForceWebGLContextLoss option 2021-01-17 11:15:15 +01:00
Alexander Rose
4588fdd5d5 wip, gpu mc webgl1 tweak 2021-01-16 17:24:26 -08:00
Alexander Rose
c3b32baf6a wip, gpu mc & vol isosurface 2021-01-16 15:18:41 -08:00
Alexander Rose
b8d60cea9b Canvas3d.fromCanvas attribs
- simplified antialias handling
- expose preserveDrawingBuffer
2021-01-16 13:20:32 -08:00
Alexander Rose
25b8956712 moved rgba/float conversion to glsl chunks 2021-01-16 11:40:54 -08:00
Alexander Rose
7015309db6 added more interesting pdb entries 2021-01-16 11:37:48 -08:00
Alexander Rose
aad861db37 use var in webpack version template 2021-01-16 11:37:20 -08:00
Alexander Rose
ae7811705d fix volume isosurface picking 2021-01-16 11:35:55 -08:00
dsehnal
7e26dac50b 1.2.11 2021-01-15 18:40:05 +01:00
dsehnal
75f43d038c PluginConfig.General.ForceWboitAntialiasing 2021-01-15 18:37:29 +01:00
dsehnal
b9ba940510 dihedral visual updates
- fix "extenders"
- add "arms" visual
2021-01-15 15:10:33 +01:00
dsehnal
35603baaaa 1.2.10 2021-01-15 13:28:01 +01:00
dsehnal
19dc32c491 Canvas3D.dispose lose webgl context 2021-01-15 13:21:34 +01:00
dsehnal
95997e6a61 PickScale plugin config 2021-01-15 12:54:02 +01:00
dsehnal
03e19a2ad7 css tweaks 2021-01-15 12:50:30 +01:00
dsehnal
765b133369 Merge branch 'master' into cylinders 2021-01-13 16:50:11 +01:00
dsehnal
703e729514 PD.normalizeParams options 2021-01-13 16:49:33 +01:00
dsehnal
b0216c4ce6 Merge branch 'master' into cylinders 2021-01-13 16:10:39 +01:00
dsehnal
6796fc1cd4 use PD.normalizeParams in canvas3d.setProps 2021-01-13 16:09:48 +01:00
dsehnal
87c504f9a8 mol-state: use PD.normalizeParams first time a cell is evaluated 2021-01-13 15:59:50 +01:00
dsehnal
2e770cb733 ParamDefinition.normalizeParams 2021-01-13 15:44:05 +01:00
David Sehnal
9f440f68e0 Merge pull request #118 from JonStargaryen/modelserverfixes
ModelServer: filename parameter
2021-01-13 12:57:01 +01:00
JonStargaryen
40028b27ba cleanup 2021-01-12 10:47:18 -08:00
JonStargaryen
4676ad8738 gracefully handle empty param category 2021-01-12 10:44:47 -08:00
JonStargaryen
e1c7833826 filename param 2021-01-12 10:25:55 -08:00
Alexander Rose
dd1bca0fee Merge branch 'master' into cylinders 2021-01-10 00:45:18 -08:00
Alexander Rose
c38ab2c638 use existing gl context for capability testing
- fixes unit-test
2021-01-10 00:38:16 -08:00
Alexander Rose
459c5aa5a7 wip, gpu mc
- reduced texture sizes
- structure gaussian surface texture-mesh
2021-01-10 00:17:41 -08:00
Alexander Rose
b8bf07d393 byto-count info for webgl resources 2021-01-10 00:09:20 -08:00
Alexander Rose
ea87ac2094 add and use gpu half-float support
- add texture_half_float, texture_half_float_linear, color_buffer_half_float
- use in multie-sample, gaussian-density
2021-01-10 00:07:12 -08:00
Alexander Rose
e1b830a59d improved atomicDetail preset 2021-01-09 14:23:19 -08:00
Alexander Rose
41e1ac76c0 improve peptide entity-subtype derivation 2021-01-09 12:51:34 -08:00
Alexander Rose
98b118fd1e parse pdb conect records
- heuristic to test if list of bonds is exhaustive to skip auto-bonding
2021-01-09 12:49:44 -08:00
Alexander Rose
5f691913e4 fix webgl1 screendoor transparency
- webgl1 only allows const array access
2021-01-09 11:16:32 -08:00
Alexander Rose
26e2516097 fix traceOnly param getting ignored 2021-01-09 11:15:13 -08:00
dsehnal
3d2e4115ed 1.2.9 2021-01-08 15:17:42 +01:00
dsehnal
dbce1ccb3d alpha-orbitals: ability to clamp volume values 2021-01-08 15:16:08 +01:00
David Sehnal
03aa2be978 Merge pull request #116 from JonStargaryen/modelserverfixes
ModelServer: Add option to download text files
2021-01-07 17:33:38 +01:00
JonStargaryen
8dfc52e1ab cleanup 2021-01-07 14:49:32 +01:00
JonStargaryen
6058179f10 cleanup 2021-01-07 14:36:11 +01:00
JonStargaryen
ea9e25b03c ResultWriterParams 2021-01-07 14:30:01 +01:00
JonStargaryen
d60c3ddce3 handle non-string params faithfully 2021-01-07 14:21:32 +01:00
JonStargaryen
724e79bddf version/changelog 2021-01-07 12:53:44 +01:00
JonStargaryen
2de61215c4 better description 2021-01-07 12:35:16 +01:00
JonStargaryen
e783d9a9f1 Merge remote-tracking branch 'upstream/master' into modelserverfixes 2021-01-07 12:31:26 +01:00
JonStargaryen
e9e971d4f3 lock 2021-01-07 12:31:20 +01:00
JonStargaryen
96dea14cb1 cleanup 2021-01-07 12:26:50 +01:00
JonStargaryen
04fc157340 ModelServer: Save As param 2021-01-07 11:53:24 +01:00
dsehnal
cfc24fa99e SequenceView: fix polymers & everything modes 2021-01-07 11:22:57 +01:00
dsehnal
19c1088209 1.2.8 2021-01-06 15:29:29 +01:00
dsehnal
ee6c2e0841 canvas3d: add commited event 2021-01-06 15:22:54 +01:00
Alexander Rose
20ee659b00 wip, gaussian surface
- fix webgl1 gaussian volume broken
- fix 2d volume slice missing first row
- use scissor test to avoid useless calculations
- reduce radius for which gaussians are calculated
-
2021-01-03 15:15:03 -08:00
Alexander Rose
b6514a4a50 fix structure bond count calculation 2021-01-03 14:57:18 -08:00
Alexander Rose
07b8bdb951 noClip option for renderables
- exclude handle and axes helper from clip objects
2021-01-03 14:56:40 -08:00
Alexander Rose
afd18cabd4 tweaked renderer params to enable AO by default 2021-01-03 14:42:35 -08:00
Alexander Rose
1117ce05d5 improved picking alpha and fog shader code 2021-01-03 14:37:49 -08:00
Alexander Rose
fc15e952bf fix smaa viewport handling 2021-01-03 14:34:34 -08:00
dsehnal
249e5a3e0b SequenceView improvements
- add ability to show all polymers/chains at once
- do not redraw when structure list doesn't change (saves 3 re-renders for a typical structure load)
2020-12-29 16:29:16 +01:00
Alexander Rose
4bfe3f6bde postprocesing tweaks
- better distingush objects close to far plane from background
- draw outlines last to be cleaner
- allow larger AO radius
2020-12-21 22:38:30 -08:00
Alexander Rose
75b7e0b4d9 support to 'invert' clip object test
- e.g. to cut away everything but a sphere
2020-12-20 20:06:52 -08:00
Alexander Rose
ee4ce2fd7a simplify copy shader 2020-12-20 14:35:17 -08:00
Alexander Rose
db0aa12e75 Merge branch 'master' into cylinders 2020-12-20 13:45:50 -08:00
Alexander Rose
6d2578d3d0 repr/geo param update fixes
- texture-mesh geo
- text visual
2020-12-20 13:40:33 -08:00
Alexander Rose
99d61f48b4 Merge branch 'master' into cylinders 2020-12-20 13:00:18 -08:00
Alexander Rose
146022dc12 wip, gaussian surface & mc
- fix iso-level
- reuse gpu resources for mc (patched many memory leaks)
2020-12-20 12:55:54 -08:00
Alexander Rose
92730cad01 Merge branch 'master' into cylinders 2020-12-19 21:33:24 -08:00
Alexander Rose
d6b68b06da Merge branch 'cylinders' of https://github.com/molstar/molstar into cylinders 2020-12-19 21:31:16 -08:00
Alexander Rose
b174fbf0c6 postprocessing tweaks and fixes/improvements
- AO defaults: darker, larger radius
- handle transparent bg for outlines & AO
- handle fog for AO
- fix fog for outlines
- fragmentDepth for fog (instead of camera distance)
- webgl1 compat
2020-12-19 21:26:06 -08:00
Alexander Rose
fde1557955 Merge branch 'postprocessing' into cylinders
- added AntialiasingPass
2020-12-19 17:38:57 -08:00
AronKovacs
24a0753881 fix fog for outlines 2020-12-19 21:59:56 +01:00
dsehnal
5664e1d8be 1.2.7 2020-12-19 11:53:09 +01:00
dsehnal
4881a41256 set default camera radius/max = 0 2020-12-19 11:46:00 +01:00
dsehnal
235e41ee03 PluginConfig EnableWboit => true 2020-12-19 11:26:13 +01:00
AronKovacs
94d293a4d3 renaming, better defaults, ao bias, better outline thresholding, whitespace changes 2020-12-18 16:10:37 +01:00
AronKovacs
40f1ca207f replaced placeholder value with the correct uniform 2020-12-16 17:58:00 +01:00
AronKovacs
926fb38c1e added contributors 2020-12-16 17:43:20 +01:00
AronKovacs
5a14fcabc5 small ssao changes, e.g. better vec2 noise 2020-12-16 17:38:38 +01:00
AronKovacs
560e40773f added renderBlended postprocessing 2020-12-16 17:21:26 +01:00
AronKovacs
6561732f57 Merge remote-tracking branch 'upstream/master' into postprocessing 2020-12-15 13:30:41 +01:00
AronKovacs
b45cf206fd postprocessing init 2020-12-15 13:27:11 +01:00
Alexander Rose
70e07be64d anvil tweaks
- remove unused/broken bilayer-spheres visual
- ensure anvil prop is calculated
2020-12-12 18:23:07 -08:00
Alexander Rose
f3013f0e46 smaa param tweaks 2020-12-12 17:31:27 -08:00
Alexander Rose
2e7041bd78 remove debug statement 2020-12-12 17:29:19 -08:00
Alexander Rose
5d0447c9bb enable wboit by default 2020-12-12 16:20:59 -08:00
Alexander Rose
9eba0b91a8 add smaa antialiasing option (new default) 2020-12-12 16:13:53 -08:00
Alexander Rose
58bc6722a9 moved fxaa to separate pass 2020-12-12 15:42:56 -08:00
Alexander Rose
1acfed3233 naming and doc tweaks 2020-12-12 15:39:45 -08:00
dsehnal
8147b3aa34 1.2.6 2020-12-10 10:46:26 +01:00
dsehnal
b21552ff36 fix wboit rendering when updating alpha 2020-12-10 10:44:35 +01:00
dsehnal
c683cbe962 1.2.5 2020-12-09 15:06:17 +01:00
dsehnal
bd270e4428 fix pdbx_PDB_ins_code "prefixed" names in CIF exporter 2020-12-09 15:03:41 +01:00
dsehnal
23d942d8a5 updated packages 2020-12-09 14:55:25 +01:00
Alexander Rose
cbcd6b99d2 Merge pull request #107 from molstar/remove-3dg
remove 3dg in favor of g3d (#93)
2020-12-05 20:44:16 -08:00
Alexander Rose
ee5c098a9f remove 3dg in favor of g3d (#93) 2020-12-05 20:38:50 -08:00
Alexander Rose
070a15d679 antialiasing related tweaks (combat blurriness)
- increase default line and point size
- reduce subpixel quality in fxaa
2020-12-05 16:02:08 -08:00
Alexander Rose
befa5174f8 cylinder impostors for bonds
- inter/intra bonds
- ball & stick, ellipsoids
- new link visual helper
2020-12-05 15:49:59 -08:00
Alexander Rose
d6c4366f40 link visual helper improvements
- more configurable dashes
- better cap handling
2020-12-05 15:28:22 -08:00
Alexander Rose
181cfefa63 use bond location for repr bond iterator
- fix themes to handle Bond.Location (some did not)
2020-12-05 15:22:04 -08:00
Alexander Rose
0e7c885961 fix typo, remove unused code 2020-12-05 15:17:14 -08:00
Alexander Rose
d58e90d93f add cylinders geometry and shader 2020-12-05 15:15:29 -08:00
David Sehnal
cd872b47e6 1.2.4 2020-12-03 15:30:30 +01:00
David Sehnal
2683c5b318 Merge pull request #105 from molstar/gpu-grid
GPU grid 3d computation wrapper
2020-12-03 15:24:43 +01:00
David Sehnal
c71f60a164 ParamDefinition.DataRef 2020-12-03 15:21:42 +01:00
David Sehnal
881cbc1947 tweaks 2020-12-03 13:54:51 +01:00
David Sehnal
f3e7febbd1 Merge branch 'master' of https://github.com/molstar/molstar into gpu-grid 2020-12-03 06:33:19 +01:00
David Sehnal
e68ad13031 createGrid3dComputeRenderable yieldPeriod param 2020-12-02 12:29:01 +01:00
Alexander Rose
7fbbe1e63a representation state and hightlight fixes
- recreate state when repr changes
- take repr into account for non-hover hightlights (eg from state tree)
2020-12-01 17:48:40 -08:00
Alexander Rose
a5ca72af3c postprocessing tweaks and fixes
- fix missing enable scissor state
- better antialiasing defaults
- always allow fxaa
2020-12-01 17:46:51 -08:00
David Sehnal
1ce6641eb3 grid3d-compute util code 2020-12-01 20:58:27 +01:00
David Sehnal
5dc413ab8c wip grid3d renderable 2020-12-01 19:33:05 +01:00
David Sehnal
50b615e86c 1.2.3 2020-11-28 14:50:15 +01:00
David Sehnal
5b4c6743e7 GlobalModelTransformInfo
- support in volume streaming
- export in ModelServer if transform param is present
2020-11-28 14:46:58 +01:00
Alexander Rose
99a3906978 1.2.2 2020-11-26 11:17:22 -08:00
Alexander Rose
981db34736 Merge branch 'master' of https://github.com/molstar/molstar 2020-11-26 11:12:35 -08:00
Alexander Rose
c079a8c5a8 fixed triple linkstyle in visuals
- was ignored
2020-11-26 11:12:11 -08:00
Alexander Rose
ad70adf6ce improved & fixed fxaa
- enable linear texture interpolation to actually do subpixel fetches...
- higher quality fxaa profile with edge exploration
- exposed parameters
- enable during temproal multi sampling
2020-11-26 11:11:14 -08:00
David Sehnal
89110b52bd copyright info 2020-11-26 15:55:48 +01:00
256 changed files with 27949 additions and 7119 deletions

View File

@@ -24,4 +24,5 @@
* Close backbone atoms but not linked (e.g. 4HIV)
* Non-standard residues
* Protein (1BRR, 5Z6Y)
* DNA (5D3G)
* DNA (5D3G)
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)

25493
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "1.2.1",
"version": "1.3.2",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -80,70 +80,71 @@
"Alexander Rose <alexander.rose@weirdbyte.de>",
"David Sehnal <david.sehnal@gmail.com>",
"Sebastian Bittrich <sebastian.bittrich@rcsb.org>",
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
"Ludovic Autin <autin@scripps.edu>",
"Michal Malý <michal.maly@ibt.cas.cz>",
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^1.17.7",
"@graphql-codegen/cli": "^1.17.8",
"@graphql-codegen/time": "^1.17.10",
"@graphql-codegen/typescript": "^1.17.9",
"@graphql-codegen/typescript-graphql-files-modules": "^1.17.8",
"@graphql-codegen/typescript-graphql-request": "^1.17.7",
"@graphql-codegen/typescript-operations": "^1.17.8",
"@types/cors": "^2.8.7",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"@graphql-codegen/add": "^2.0.2",
"@graphql-codegen/cli": "^1.19.4",
"@graphql-codegen/time": "^2.0.2",
"@graphql-codegen/typescript": "^1.19.0",
"@graphql-codegen/typescript-graphql-files-modules": "^1.18.1",
"@graphql-codegen/typescript-graphql-request": "^2.0.3",
"@graphql-codegen/typescript-operations": "^1.17.12",
"@types/cors": "^2.8.8",
"@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/parser": "^4.9.1",
"benchmark": "^2.1.4",
"concurrently": "^5.3.0",
"cpx2": "^2.0.0",
"css-loader": "^3.6.0",
"eslint": "^7.8.1",
"cpx2": "^3.0.0",
"css-loader": "^5.0.1",
"eslint": "^7.15.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.1.0",
"file-loader": "^6.2.0",
"fs-extra": "^9.0.1",
"graphql": "^15.3.0",
"graphql": "^15.4.0",
"http-server": "^0.12.3",
"jest": "^26.4.2",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.14.1",
"raw-loader": "^4.0.1",
"sass-loader": "^8.0.2",
"simple-git": "^2.20.1",
"style-loader": "^1.2.1",
"ts-jest": "^26.3.0",
"typescript": "^4.0.2",
"jest": "^26.6.3",
"mini-css-extract-plugin": "^1.3.2",
"node-sass": "^5.0.0",
"raw-loader": "^4.0.2",
"sass-loader": "^10.1.0",
"simple-git": "^2.25.0",
"style-loader": "^2.0.0",
"ts-jest": "^26.4.4",
"typescript": "^4.1.2",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-version-file-plugin": "^0.4.0"
},
"dependencies": {
"@types/argparse": "^1.0.38",
"@types/benchmark": "^1.0.33",
"@types/benchmark": "^2.1.0",
"@types/compression": "1.7.0",
"@types/express": "^4.17.8",
"@types/jest": "^25.2.3",
"@types/node": "^14.10.1",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.18",
"@types/node": "^14.14.11",
"@types/node-fetch": "^2.5.7",
"@types/react": "^16.9.49",
"@types/react-dom": "^16.9.8",
"@types/swagger-ui-dist": "3.0.5",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/swagger-ui-dist": "3.30.0",
"argparse": "^1.0.10",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"h264-mp4-encoder": "^1.0.12",
"immer": "^7.0.9",
"immer": "^8.0.0",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"node-fetch": "^2.6.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rxjs": "^6.6.3",
"swagger-ui-dist": "^3.33.0",
"tslib": "^2.0.1",
"swagger-ui-dist": "^3.37.2",
"tslib": "^2.0.3",
"util.promisify": "^1.0.1",
"xhr2": "^0.2.0"
}

View File

@@ -46,13 +46,14 @@ function occlusionStyle(plugin: PluginContext) {
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
occlusion: { name: 'on', params: {
kernelSize: 8,
bias: 0.8,
radius: 64
samples: 64,
radius: 8,
bias: 1.0,
blurKernelSize: 13
} },
outline: { name: 'on', params: {
scale: 1.0,
threshold: 0.8
threshold: 0.33
} }
}
} });

View File

@@ -50,14 +50,14 @@
var disableAntialiasing = getParam('disable-antialiasing', '[^&]+').trim() === '1';
var pixelScale = parseFloat(getParam('pixel-scale', '[^&]+').trim() || '1');
var enableWboit = getParam('enable-wboit', '[^&]+').trim() === '1';
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
var viewer = new molstar.Viewer('app', {
disableAntialiasing: disableAntialiasing,
pixelScale: pixelScale,
enableWboit: enableWboit,
enableWboit: !disableWboit,
layoutShowControls: !hideControls,
viewportShowExpand: false,
pdbProvider: pdbProvider || 'pdbe',

View File

@@ -69,7 +69,7 @@ const DefaultViewerOptions = {
layoutShowLeftPanel: true,
disableAntialiasing: false,
pixelScale: 1,
enableWboit: false,
enableWboit: true,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,

View File

@@ -26,7 +26,8 @@ function paramInfo(param: PD.Any, offset: number): string {
case 'file': return `JavaScript File Handle`;
case 'file-list': return `JavaScript FileList Handle`;
case 'select': return `One of ${oToS(param.options)}`;
case 'value-ref': return `Reference to a state object.`;
case 'value-ref': return `Reference to a runtime defined value.`;
case 'data-ref': return `Reference to a computed data value.`;
case 'text': return 'String';
case 'interval': return `Interval [min, max]`;
case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`;

View File

@@ -20,8 +20,8 @@ import { BehaviorSubject } from 'rxjs';
import { debounceTime, skip } from 'rxjs/operators';
import './index.html';
import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model';
import { canComputeAlphaOrbitalsOnGPU } from '../../extensions/alpha-orbitals/gpu/compute';
import { PluginCommands } from '../../mol-plugin/commands';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
require('mol-plugin-ui/skin/light.scss');
interface DemoInput {
@@ -71,7 +71,7 @@ export class AlphaOrbitalsExample {
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
if (!canComputeAlphaOrbitalsOnGPU(this.plugin.canvas3d?.webgl)) {
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`

View File

@@ -24,8 +24,8 @@ const Canvas3DPresets = {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
outline: { name: 'on', params: { scale: 1, threshold: 0.8 } }
occlusion: { name: 'on', params: { samples: 64, radius: 8, bias: 1.0, blurKernelSize: 13 } },
outline: { name: 'on', params: { scale: 1, threshold: 0.33 } }
},
renderer: {
ambientIntensity: 1,
@@ -37,7 +37,7 @@ const Canvas3DPresets = {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
occlusion: { name: 'on', params: { samples: 64, radius: 8, bias: 1.0, blurKernelSize: 13 } },
outline: { name: 'off', params: { } }
},
renderer: {

View File

@@ -7,8 +7,8 @@
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
import { Grid } from '../../mol-model/volume';
import { SphericalBasisOrder } from './spherical-functions';
import { Box3D } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms } from '../../mol-util/array';
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
// Note: generally contracted gaussians are currently not supported.
export interface SphericalElectronShell {
@@ -95,7 +95,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const BohrToAngstromFactor = 0.529177210859;
export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrder: number[]) {
export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) {
const boxSize = Box3D.size(Vec3(), gridInfo.box);
const boxOrigin = Vec3.clone(gridInfo.box.min);
@@ -122,7 +122,7 @@ export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrd
stats: {
min: arrayMin(values),
max: arrayMax(values),
mean: arrayMax(values),
mean: arrayMean(values),
sigma: arrayRms(values),
},
};

View File

@@ -5,10 +5,11 @@
*/
import { sortArray } from '../../mol-data/util';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Task } from '../../mol-task';
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
export function createSphericalCollocationDensityGrid(
params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext
@@ -17,9 +18,9 @@ export function createSphericalCollocationDensityGrid(
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(webgl!, cubeGrid, orbitals, ctx);
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
// console.timeEnd('gpu');
} else {
throw new Error('Missing OES_texture_float WebGL extension.');

View File

@@ -4,46 +4,72 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { QuadSchema, QuadValues } from '../../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../../mol-gl/renderable';
import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../../mol-gl/renderable/schema';
import { ShaderCode } from '../../../mol-gl/shader-code';
import quad_vert from '../../../mol-gl/shader/quad.vert';
import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d';
import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
import { RuntimeContext } from '../../../mol-task';
import { ValueCell } from '../../../mol-util';
import { arrayMin } from '../../../mol-util/array';
import { isLittleEndian } from '../../../mol-util/is-little-endian';
import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model';
import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions';
import shader_frag from './shader.frag';
import { MAIN, UTILS } from './shader.frag';
const AlphaOrbitalsSchema = {
...QuadSchema,
uDimensions: UniformSpec('v3'),
uMin: UniformSpec('v3'),
uDelta: UniformSpec('v3'),
const Schema = {
tCenters: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
tInfo: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
tCoeff: TextureSpec('image-float32', 'rgb', 'float', 'nearest'),
tAlpha: TextureSpec('image-float32', 'alpha', 'float', 'nearest'),
uWidth: UniformSpec('f'),
uNCenters: UniformSpec('i'),
uNAlpha: UniformSpec('i'),
uNCoeff: UniformSpec('i'),
uMaxCoeffs: UniformSpec('i'),
uLittleEndian: UniformSpec('b'),
uDensity: UniformSpec('b'),
uOccupancy: UniformSpec('f'),
tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
};
type AlphaOrbitalsSchema = Values<typeof AlphaOrbitalsSchema>
const AlphaOrbitalsName = 'alpha-orbitals';
const AlphaOrbitalsTex0 = 'alpha-orbitals-0';
const AlphaOrbitalsTex1 = 'alpha-orbitals-1';
const AlphaOrbitalsShaderCode = ShaderCode(AlphaOrbitalsName, quad_vert, shader_frag);
type AlphaOrbitalsRenderable = ComputeRenderable<AlphaOrbitalsSchema>
const Orbitals = createGrid3dComputeRenderable({
schema: Schema,
loopBounds: ['uNCenters', 'uMaxCoeffs'],
mainCode: MAIN,
utilCode: UTILS,
returnCode: 'v',
values(params: { grid: CubeGridInfo, orbital: AlphaOrbital }) {
return createTextureData(params.grid, params.orbital);
}
});
const Density = createGrid3dComputeRenderable({
schema: {
...Schema,
uOccupancy: UniformSpec('f'),
},
loopBounds: ['uNCenters', 'uMaxCoeffs'],
mainCode: MAIN,
utilCode: UTILS,
returnCode: 'current + uOccupancy * v * v',
values(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
return {
...createTextureData(params.grid, params.orbitals[0]),
uOccupancy: 0
};
},
cumulative: {
states(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
return params.orbitals.filter(o => o.occupancy !== 0);
},
update({ grid }, state: AlphaOrbital, values) {
const alpha = getNormalizedAlpha(grid.params.basis, state.alpha, grid.params.sphericalOrder);
ValueCell.updateIfChanged(values.uOccupancy, state.occupancy);
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
}
}
});
export function gpuComputeAlphaOrbitalsGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
return Orbitals(ctx, webgl, grid, { grid, orbital });
}
export function gpuComputeAlphaOrbitalsDensityGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[]) {
return Density(ctx, webgl, grid, { grid, orbitals });
}
function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrder: SphericalBasisOrder) {
const alpha = new Float32Array(alphaOrbitals.length);
@@ -62,7 +88,7 @@ function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrde
return alpha;
}
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) {
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> {
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
let centerCount = 0;
@@ -131,179 +157,14 @@ function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) {
}
}
return { nCenters: centerCount, nAlpha: baseCount, nCoeff: coeffCount, maxCoeffs, centers, info, alpha, coeff };
}
function createAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable {
const data = createTextureData(grid, orbital);
const [nx, ny, nz] = grid.dimensions;
const width = Math.ceil(Math.sqrt(nx * ny * nz));
if (!ctx.namedFramebuffers[AlphaOrbitalsName]) {
ctx.namedFramebuffers[AlphaOrbitalsName] = ctx.resources.framebuffer();
}
if (!ctx.namedTextures[AlphaOrbitalsTex0]) {
ctx.namedTextures[AlphaOrbitalsTex0] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
if (!ctx.namedTextures[AlphaOrbitalsTex1]) {
ctx.namedTextures[AlphaOrbitalsTex1] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
const values: AlphaOrbitalsSchema = {
...QuadValues,
uDimensions: ValueCell.create(grid.dimensions),
uMin: ValueCell.create(grid.box.min),
uDelta: ValueCell.create(grid.delta),
uWidth: ValueCell.create(width),
uNCenters: ValueCell.create(data.nCenters),
uNAlpha: ValueCell.create(data.nAlpha),
uNCoeff: ValueCell.create(data.nCoeff),
uMaxCoeffs: ValueCell.create(data.maxCoeffs),
tCenters: ValueCell.create({ width: data.nCenters, height: 1, array: data.centers }),
tInfo: ValueCell.create({ width: data.nCenters, height: 1, array: data.info }),
tCoeff: ValueCell.create({ width: data.nCoeff, height: 1, array: data.coeff }),
tAlpha: ValueCell.create({ width: data.nAlpha, height: 1, array: data.alpha }),
uLittleEndian: ValueCell.create(isLittleEndian()),
uDensity: ValueCell.create(false),
uOccupancy: ValueCell.create(0),
tCumulativeSum: ValueCell.create(ctx.namedTextures[AlphaOrbitalsTex1])
return {
uNCenters: centerCount,
uNAlpha: baseCount,
uNCoeff: coeffCount,
uMaxCoeffs: maxCoeffs,
tCenters: { width: centerCount, height: 1, array: centers },
tInfo: { width: centerCount, height: 1, array: info },
tCoeff: { width: coeffCount, height: 1, array: coeff },
tAlpha: { width: baseCount, height: 1, array: alpha },
};
const schema = { ...AlphaOrbitalsSchema };
if (!ctx.isWebGL2) {
// workaround for webgl1 limitation that loop counters need to be `const`
(schema.uNCenters as any) = DefineSpec('number');
(schema.uMaxCoeffs as any) = DefineSpec('number');
}
const renderItem = createComputeRenderItem(ctx, 'triangles', AlphaOrbitalsShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function getAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable {
if (ctx.namedComputeRenderables[AlphaOrbitalsName]) {
const v = ctx.namedComputeRenderables[AlphaOrbitalsName].values as AlphaOrbitalsSchema;
const data = createTextureData(grid, orbital);
const [nx, ny, nz] = grid.dimensions;
const width = Math.ceil(Math.sqrt(nx * ny * nz));
ValueCell.update(v.uDimensions, grid.dimensions);
ValueCell.update(v.uMin, grid.box.min);
ValueCell.update(v.uDelta, grid.delta);
ValueCell.updateIfChanged(v.uWidth, width);
ValueCell.updateIfChanged(v.uNCenters, data.nCenters);
ValueCell.updateIfChanged(v.uNAlpha, data.nAlpha);
ValueCell.updateIfChanged(v.uNCoeff, data.nCoeff);
ValueCell.updateIfChanged(v.uMaxCoeffs, data.maxCoeffs);
ValueCell.update(v.tCenters, { width: data.nCenters, height: 1, array: data.centers });
ValueCell.update(v.tInfo, { width: data.nCenters, height: 1, array: data.info });
ValueCell.update(v.tCoeff, { width: data.nCoeff, height: 1, array: data.coeff });
ValueCell.update(v.tAlpha, { width: data.nAlpha, height: 1, array: data.alpha });
ValueCell.updateIfChanged(v.uLittleEndian, isLittleEndian());
ValueCell.updateIfChanged(v.uDensity, false);
ValueCell.updateIfChanged(v.uOccupancy, 0);
ValueCell.updateIfChanged(v.tCumulativeSum, ctx.namedTextures[AlphaOrbitalsTex1]);
ctx.namedComputeRenderables[AlphaOrbitalsName].update();
} else {
ctx.namedComputeRenderables[AlphaOrbitalsName] = createAlphaOrbitalsRenderable(ctx, grid, orbital);
}
return ctx.namedComputeRenderables[AlphaOrbitalsName];
}
export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
const [nx, ny, nz] = grid.dimensions;
const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbital);
const width = renderable.values.uWidth.ref.value;
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
webgl.namedTextures[AlphaOrbitalsTex0].define(width, width);
webgl.namedTextures[AlphaOrbitalsTex0].attachFramebuffer(framebuffer, 'color0');
const { gl, state } = webgl;
framebuffer.bind();
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
renderable.render();
const array = new Uint8Array(width * width * 4);
webgl.readPixels(0, 0, width, width, array);
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
}
export function canComputeAlphaOrbitalsOnGPU(webgl?: WebGLContext) {
return !!webgl?.extensions.textureFloat;
}
export async function gpuComputeAlphaOrbitalsDensityGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[], ctx: RuntimeContext) {
await ctx.update({ message: 'Initializing...', isIndeterminate: true });
const [nx, ny, nz] = grid.dimensions;
const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbitals[0]);
const width = renderable.values.uWidth.ref.value;
if (!webgl.namedFramebuffers[AlphaOrbitalsName]) {
webgl.namedFramebuffers[AlphaOrbitalsName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
const tex = [webgl.namedTextures[AlphaOrbitalsTex0], webgl.namedTextures[AlphaOrbitalsTex1]];
tex[0].define(width, width);
tex[1].define(width, width);
const values = renderable.values as AlphaOrbitalsSchema;
const { gl, state } = webgl;
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
gl.clearColor(0, 0, 0, 0);
tex[0].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
tex[1].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
ValueCell.update(values.uDensity, true);
const nonZero = orbitals.filter(o => o.occupancy !== 0);
await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: nonZero.length });
for (let i = 0; i < nonZero.length; i++) {
const alpha = getNormalizedAlpha(grid.params.basis, nonZero[i].alpha, grid.params.sphericalOrder);
ValueCell.update(values.uOccupancy, nonZero[i].occupancy);
ValueCell.update(values.tCumulativeSum, tex[(i + 1) % 2]);
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
tex[i % 2].attachFramebuffer(framebuffer, 'color0');
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
renderable.update();
renderable.render();
if (i !== nonZero.length - 1 && ctx.shouldUpdate) {
await ctx.update({ current: i + 1 });
}
}
const array = new Uint8Array(width * width * 4);
webgl.readPixels(0, 0, width, width, array);
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
}

View File

@@ -4,145 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default `
precision highp float;
precision highp int;
precision highp sampler2D;
uniform vec2 uQuadShift;
uniform vec3 uDimensions;
uniform vec3 uMin;
uniform vec3 uDelta;
uniform sampler2D tCenters;
uniform sampler2D tInfo;
uniform sampler2D tCoeff;
uniform sampler2D tAlpha;
uniform float uWidth;
#ifndef uNCenters
uniform int uNCenters;
#endif
uniform int uNCoeff;
uniform int uNAlpha;
uniform bool uDensity;
uniform float uOccupancy;
uniform sampler2D tCumulativeSum;
uniform bool uLittleEndian;
float shiftRight (float v, float amt) {
v = floor(v) + 0.5;
return floor(v / exp2(amt));
}
float shiftLeft (float v, float amt) {
return floor(v * exp2(amt) + 0.5);
}
float maskLast (float v, float bits) {
return mod(v, shiftLeft(1.0, bits));
}
float extractBits (float num, float from, float to) {
from = floor(from + 0.5); to = floor(to + 0.5);
return maskLast(shiftRight(num, from), to - from);
}
// Adapted from https://github.com/equinor/glsl-float-to-rgba
// MIT License, Copyright (c) 2020 Equinor
vec4 floatToRgba(float texelFloat) {
if (texelFloat == 0.0) return vec4(0, 0, 0, 0);
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
texelFloat = abs(texelFloat);
float exponent = floor(log2(texelFloat));
float biased_exponent = exponent + 127.0;
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
float t = biased_exponent / 2.0;
float last_bit_of_biased_exponent = fract(t) * 2.0;
float remaining_bits_of_biased_exponent = floor(t);
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
return (
uLittleEndian
? vec4(byte4, byte3, byte2, byte1)
: vec4(byte1, byte2, byte3, byte4)
);
}
///////////////////////////////////////////////////////
ivec4 floatsToBytes(vec4 inputFloats) {
ivec4 bytes = ivec4(inputFloats * 255.0);
return (
uLittleEndian
? bytes.abgr
: bytes
);
}
// Break the four bytes down into an array of 32 bits.
void bytesToBits(const in ivec4 bytes, out bool bits[32]) {
for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {
float acc = float(bytes[channelIndex]);
for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {
float powerOfTwo = exp2(float(indexInByte));
bool bit = acc >= powerOfTwo;
bits[channelIndex * 8 + (7 - indexInByte)] = bit;
acc = mod(acc, powerOfTwo);
}
}
}
// Compute the exponent of the 32-bit float.
float getExponent(bool bits[32]) {
const int startIndex = 1;
const int bitStringLength = 8;
const int endBeforeIndex = startIndex + bitStringLength;
float acc = 0.0;
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Compute the mantissa of the 32-bit float.
float getMantissa(bool bits[32], bool subnormal) {
const int startIndex = 9;
const int bitStringLength = 23;
const int endBeforeIndex = startIndex + bitStringLength;
// Leading/implicit/hidden bit convention:
// If the number is not subnormal (with exponent 0), we add a leading 1 digit.
float acc = float(!subnormal) * exp2(float(bitStringLength));
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Parse the float from its 32 bits.
float bitsToFloat(bool bits[32]) {
float signBit = float(bits[0]) * -2.0 + 1.0;
float exponent = getExponent(bits);
bool subnormal = abs(exponent - 0.0) < 0.01;
float mantissa = getMantissa(bits, subnormal);
float exponentBias = 127.0;
return signBit * mantissa * exp2(exponent - exponentBias - 23.0);
}
// Decode a 32-bit float from the RGBA color channels of a texel.
float rgbaToFloat(vec4 texelRGBA) {
ivec4 rgbaBytes = floatsToBytes(texelRGBA);
bool bits[32];
bytesToBits(rgbaBytes, bits);
return bitsToFloat(bits);
}
///////////////////////////////////////////////////////
export const UTILS = `
float L1(vec3 p, float a0, float a1, float a2) {
return a0 * p.z + a1 * p.x + a2 * p.y;
}
@@ -193,12 +55,10 @@ float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, flo
}
float alpha(float offset, float f) {
#ifdef uMaxCoeffs
#ifdef WEBGL1
// in webgl1, the value is in the alpha channel!
return texture2D(tAlpha, vec2(offset * f, 0.5)).a;
#endif
#ifndef uMaxCoeffs
#else
return texture2D(tAlpha, vec2(offset * f, 0.5)).x;
#endif
}
@@ -229,7 +89,7 @@ float Y(int L, vec3 X, float aO, float fA) {
return 0.0;
}
#ifndef uMaxCoeffs
#ifndef WEBGL1
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
for (int i = start; i < end; i++) {
@@ -238,9 +98,7 @@ float Y(int L, vec3 X, float aO, float fA) {
}
return gauss;
}
#endif
#ifdef uMaxCoeffs
#else
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
int o = start;
@@ -254,28 +112,13 @@ float Y(int L, vec3 X, float aO, float fA) {
return gauss;
}
#endif
`;
float intDiv(float a, float b) { return float(int(a) / int(b)); }
float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
void main(void) {
float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth;
// axis order fast to slow Z, Y, X
// TODO: support arbitrary axis orders?
float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z);
float j = intMod(kk, uDimensions.y);
float i = intDiv(kk, uDimensions.y);
vec3 xyz = uMin + uDelta * vec3(i, j, k);
export const MAIN = `
float fCenter = 1.0 / float(uNCenters - 1);
float fCoeff = 1.0 / float(uNCoeff - 1);
float fA = 1.0 / float(uNAlpha - 1);
// gl_FragColor = floatToRgba(offset);
// return;
float v = 0.0;
for (int i = 0; i < uNCenters; i++) {
@@ -299,13 +142,4 @@ void main(void) {
v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA);
}
if (uDensity) {
float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth)));
gl_FragColor = floatToRgba(current + uOccupancy * v * v);
} else {
gl_FragColor = floatToRgba(v);
}
}
`;

View File

@@ -7,11 +7,14 @@
*/
import { sortArray } from '../../mol-data/util';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Task } from '../../mol-task';
import { sphericalCollocation } from './collocation';
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
// setDebugMode(true);
export function createSphericalCollocationGrid(
params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
@@ -20,9 +23,9 @@ export function createSphericalCollocationGrid(
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = gpuComputeAlphaOrbitalsGridValues(webgl!, cubeGrid, orbital);
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
// console.timeEnd('gpu');
} else {
// console.time('cpu');

View File

@@ -19,6 +19,7 @@ import { Theme } from '../../mol-theme/theme';
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
import { createSphericalCollocationDensityGrid } from './density';
import { Tensor } from '../../mol-math/linear-algebra';
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
@@ -49,9 +50,43 @@ const CreateOrbitalVolumeParamBase = {
{ atomCount: 25, spacing: 0.4 },
{ atomCount: 0, spacing: 0.35 },
]
}),
clampValues: PD.MappedStatic('off', {
off: PD.EmptyGroup(),
on: PD.Group({
sigma: PD.Numeric(8, { min: 1, max: 20 }, { description: 'Clamp values to range [sigma * negIsoValue, sigma * posIsoValue].' })
})
})
};
function clampData(matrix: Tensor.Data, min: number, max: number) {
for (let i = 0, _i = matrix.length; i < _i; i++) {
const v = matrix[i];
if (v < min) matrix[i] = min;
else if (v > max) matrix[i] = max;
}
}
function clampGrid(data: CubeGrid, v: number) {
const grid = data.grid;
const min = (data.isovalues?.negative ?? data.grid.stats.min) * v;
const max = (data.isovalues?.positive ?? data.grid.stats.max) * v;
// clamp values for better direct volume resolution
// current implementation uses Byte array for values
// if this is not enough, update mol* to use float
// textures instead
if (grid.stats.min < min || grid.stats.max > max) {
clampData(data.grid.cells.data, min, max);
if (grid.stats.min < min) {
(grid.stats.min as number) = min;
}
if (grid.stats.max > max) {
(grid.stats.max as number) = max;
}
}
}
export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
name: 'create-orbital-volume',
display: 'Orbital Volume',
@@ -84,6 +119,10 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}
@@ -112,6 +151,10 @@ export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}

View File

@@ -10,7 +10,6 @@ import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
import { Structure } from '../../mol-model/structure';
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation';
import { MembraneOrientation } from './prop';
import { ThemeRegistryContext } from '../../mol-theme/theme';
@@ -27,6 +26,7 @@ import { MembraneOrientationProvider } from './prop';
import { MarkerActions } from '../../mol-util/marker-action';
import { lociLabel } from '../../mol-theme/label';
import { ColorNames } from '../../mol-util/color/names';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
const SharedParams = {
color: PD.Color(ColorNames.lightgrey),
@@ -61,7 +61,6 @@ export type BilayerRimsParams = typeof BilayerRimsParams
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
const MembraneOrientationVisuals = {
'bilayer-spheres': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerSpheresParams>) => ShapeRepresentation(getBilayerSpheres, Spheres.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }), modifyProps: p => ({ ...p, alpha: p.sectorOpacity, ignoreLight: true, doubleSided: false }) }),
'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) })
};
@@ -91,9 +90,13 @@ export const MembraneOrientationRepresentationProvider = StructureRepresentation
factory: MembraneOrientationRepresentation,
getParams: getMembraneOrientationParams,
defaultValues: PD.getDefaultValues(MembraneOrientationParams),
defaultColorTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.elementCount > 0
defaultColorTheme: { name: 'shape-group' },
defaultSizeTheme: { name: 'shape-group' },
isApplicable: (structure: Structure) => structure.elementCount > 0,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => MembraneOrientationProvider.attach(ctx, structure, void 0, true),
detach: (data) => MembraneOrientationProvider.ref(data, false)
}
});
function membraneLabel(data: Structure) {
@@ -151,28 +154,3 @@ function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal
MeshBuilder.addPrimitive(state, Mat4.id, circle);
MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);
}
function getBilayerSpheres(ctx: RuntimeContext, data: Structure, props: BilayerSpheresProps, shape?: Shape<Spheres>): Shape<Spheres> {
const { density } = props;
const { radius, planePoint1, planePoint2, normalVector } = MembraneOrientationProvider.get(data).value!;
const scaledRadius = (props.radiusFactor * radius) * (props.radiusFactor * radius);
const spheresBuilder = SpheresBuilder.create(256, 128, shape?.geometry);
getLayerSpheres(spheresBuilder, planePoint1, normalVector, density, scaledRadius);
getLayerSpheres(spheresBuilder, planePoint2, normalVector, density, scaledRadius);
return Shape.create('Bilayer spheres', data, spheresBuilder.getSpheres(), () => props.color, () => props.sphereSize, () => membraneLabel(data));
}
function getLayerSpheres(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, sqRadius: number) {
Vec3.normalize(normalVector, normalVector);
const d = -Vec3.dot(normalVector, point);
const rep = Vec3();
for (let i = -1000, il = 1000; i < il; i += density) {
for (let j = -1000, jl = 1000; j < jl; j += density) {
Vec3.set(rep, i, j, normalVector[2] === 0 ? 0 : -(d + i * normalVector[0] + j * normalVector[1]) / normalVector[2]);
if (Vec3.squaredDistance(rep, point) < sqRadius) {
spheresBuilder.add(rep[0], rep[1], rep[2], 0);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -46,10 +46,9 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
name: 'generate',
params: {
hue, chroma: [30, 80], luminance: [15, 85],
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75,
minLabel: 'Min', maxLabel: 'Max', valueLabel: (i: number) => `${i + 1}`,
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75
}
}});
}}, { minLabel: 'Min', maxLabel: 'Max' });
legend = palette.legend;
const modelColor = new Map<number, Color>();
for (let i = 0, il = models.length; i < il; ++i) {
@@ -89,7 +88,6 @@ export const CellPackGenerateColorThemeProvider: ColorTheme.Provider<CellPackGen
isApplicable: (ctx: ThemeDataContext) => {
return (
!!ctx.structure && ctx.structure.elementCount > 0 &&
Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 &&
!!CellPackInfoProvider.get(ctx.structure).value
);
}

View File

@@ -9,7 +9,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Color } from '../../../mol-util/color';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { StructureElement, Model } from '../../../mol-model/structure';
import { StructureElement, Model, Bond } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { CellPackInfoProvider } from '../property';
@@ -37,9 +37,12 @@ export function CellPackProvidedColorTheme(ctx: ThemeDataContext, props: PD.Valu
}
color = (location: Location): Color => {
return StructureElement.Location.is(location)
? modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!
: DefaultColor;
if (StructureElement.Location.is(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
} else if (Bond.isLocation(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
}
return DefaultColor;
};
} else {
color = () => DefaultColor;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -396,21 +396,25 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
}
if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
const builder = Structure.Builder({ label: name });
const units: Unit[] = [];
let offsetInvariantId = 0;
let offsetChainGroupId = 0;
for (const s of structures) {
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
let maxInvariantId = 0;
let maxChainGroupId = 0;
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId;
const chainGroupId = u.chainGroupId + offsetChainGroupId;
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
units.push(Unit.create(units.length, invariantId, chainGroupId, u.traits, u.kind, u.model, u.conformation.operator, u.elements, u.props));
}
offsetInvariantId += maxInvariantId + 1;
offsetChainGroupId += maxChainGroupId + 1;
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
const structure = builder.getStructure();
const structure = new Structure(units);
for( let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}

View File

@@ -6,7 +6,7 @@
import { StructureQualityReport, StructureQualityReportProvider } from './prop';
import { Location } from '../../../mol-model/location';
import { StructureElement } from '../../../mol-model/structure';
import { Bond, StructureElement } from '../../../mol-model/structure';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
@@ -46,11 +46,16 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReportProvider.descriptor)) {
const getIssues = StructureQualityReport.getIssues;
const l = StructureElement.Location.create(ctx.structure);
if (props.type.name === 'issue-count') {
color = (location: Location) => {
if (StructureElement.Location.is(location)) {
return ValidationColors[Math.min(3, getIssues(location).length) + 1];
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return ValidationColors[Math.min(3, getIssues(l).length) + 1];
}
return ValidationColors[0];
};
@@ -59,6 +64,10 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
color = (location: Location) => {
if (StructureElement.Location.is(location) && getIssues(location).indexOf(issue) >= 0) {
return ValidationColors[4];
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return ValidationColors[Math.min(3, getIssues(l).length) + 1];
}
return ValidationColors[0];
};

View File

@@ -9,7 +9,7 @@ import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { AssemblySymmetryProvider, AssemblySymmetry } from './prop';
import { Color } from '../../../mol-util/color';
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
import { Unit, StructureElement, StructureProperties, Bond } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
@@ -50,6 +50,8 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
const clusters = assemblySymmetry?.value?.clusters;
if (clusters?.length && ctx.structure) {
const l = StructureElement.Location.create(ctx.structure);
const clusterByMember = new Map<string, number>();
for (let i = 0, il = clusters.length; i < il; ++i) {
const { members } = clusters[i]!;
@@ -67,12 +69,20 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
legend = palette.legend;
const _emptyList: any[] = [];
const getColor = (location: StructureElement.Location) => {
const { assembly } = location.unit.conformation.operator;
const asymId = getAsymId(location.unit)(location);
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList));
return cluster !== undefined ? palette.color(cluster) : DefaultColor;
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location)) {
const { assembly } = location.unit.conformation.operator;
const asymId = getAsymId(location.unit)(location);
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList));
return cluster !== undefined ? palette.color(cluster) : DefaultColor;
return getColor(location);
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return getColor(l);
}
return DefaultColor;
};

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color, ColorScale } from '../../../../mol-util/color';
import { StructureElement, Model } from '../../../../mol-model/structure';
import { StructureElement, Model, ElementIndex, Bond } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -37,13 +37,19 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
if (validationReport?.value && model) {
const { rsrz, rscc } = validationReport.value;
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const getColor = (element: ElementIndex) => {
const rsrzValue = rsrz.get(residueIndex[element]);
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue);
const rsccValue = rscc.get(residueIndex[element]);
if (rsccValue !== undefined) return scaleRscc.color(rsccValue);
return DefaultColor;
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const rsrzValue = rsrz.get(residueIndex[location.element]);
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue);
const rsccValue = rscc.get(residueIndex[location.element]);
if (rsccValue !== undefined) return scaleRscc.color(rsccValue);
return DefaultColor;
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color } from '../../../../mol-util/color';
import { StructureElement } from '../../../../mol-model/structure';
import { Bond, ElementIndex, StructureElement } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -59,31 +59,35 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Value
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const { polymerType } = model.atomicHierarchy.derived.residue;
const ignore = new Set(props.ignore);
const getColor = (element: ElementIndex) => {
const rI = residueIndex[element];
const value = geometryIssues.get(rI);
if (value === undefined) return DefaultColor;
let count = SetUtils.differenceSize(value, ignore);
if (count > 0 && polymerType[rI] === PolymerType.NA) {
count = 0;
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1;
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1;
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1;
}
switch (count) {
case undefined: return DefaultColor;
case 0: return NoIssuesColor;
case 1: return OneIssueColor;
case 2: return TwoIssuesColor;
default: return ThreeOrMoreIssuesColor;
}
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const { element } = location;
const rI = residueIndex[element];
const value = geometryIssues.get(rI);
if (value === undefined) return DefaultColor;
let count = SetUtils.differenceSize(value, ignore);
if (count > 0 && polymerType[rI] === PolymerType.NA) {
count = 0;
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1;
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1;
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1;
}
switch (count) {
case undefined: return DefaultColor;
case 0: return NoIssuesColor;
case 1: return OneIssueColor;
case 2: return TwoIssuesColor;
default: return ThreeOrMoreIssuesColor;
}
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color, ColorScale } from '../../../../mol-util/color';
import { StructureElement, Model } from '../../../../mol-model/structure';
import { StructureElement, Model, ElementIndex, Bond } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -31,10 +31,16 @@ export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): Col
if (rci && model) {
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const getColor = (element: ElementIndex) => {
const value = rci.get(residueIndex[element]);
return value === undefined ? DefaultColor : scale.color(value);
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const value = rci.get(residueIndex[location.element]);
return value === undefined ? DefaultColor : scale.color(value);
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};

View File

@@ -209,8 +209,8 @@ namespace Camera {
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
radius: 10,
radiusMax: 10,
radius: 0,
radiusMax: 0,
fog: 50,
clipFar: true
};
@@ -319,8 +319,8 @@ function updateClip(camera: Camera) {
let far = cameraDistance + normalizedFar;
const fogNearFactor = -(50 - fog) / 50;
let fogNear = cameraDistance - (normalizedFar * fogNearFactor);
let fogFar = far;
const fogNear = cameraDistance - (normalizedFar * fogNearFactor);
const fogFar = far;
if (mode === 'perspective') {
// set at least to 5 to avoid slow sphere impostor rendering
@@ -337,7 +337,7 @@ function updateClip(camera: Camera) {
}
camera.near = near;
camera.far = far;
camera.far = 2 * far; // avoid precision issues distingushing far objects from background
camera.fogNear = fogNear;
camera.fogFar = fogFar;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -24,7 +24,7 @@ import { ParamDefinition as PD } from '../mol-util/param-definition';
import { DebugHelperParams } from './helper/bounding-sphere-helper';
import { SetUtils } from '../mol-util/set';
import { Canvas3dInteractionHelper } from './helper/interaction-events';
import { PostprocessingParams, PostprocessingPass } from './passes/postprocessing';
import { PostprocessingParams } from './passes/postprocessing';
import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
import { PickData } from './passes/pick';
import { PickHelper } from './passes/pick';
@@ -47,11 +47,11 @@ export const Canvas3DParams = {
on: PD.Group(StereoCameraParams),
off: PD.Group({})
}, { cycle: true, hideIf: p => p?.mode !== 'perspective' }),
manualReset: PD.Boolean(false, { isHidden: true })
manualReset: PD.Boolean(false, { isHidden: true }),
}, { pivot: 'mode' }),
cameraFog: PD.MappedStatic('on', {
on: PD.Group({
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
intensity: PD.Numeric(15, { min: 1, max: 100, step: 1 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Show fog in the distance' }),
@@ -85,6 +85,110 @@ export type PartialCanvas3DProps = {
[K in keyof Canvas3DProps]?: Canvas3DProps[K] extends { name: string, params: any } ? Canvas3DProps[K] : Partial<Canvas3DProps[K]>
}
export { Canvas3DContext };
/** Can be used to create multiple Canvas3D objects */
interface Canvas3DContext {
readonly canvas: HTMLCanvasElement
readonly webgl: WebGLContext
readonly input: InputObserver
readonly passes: Passes
readonly attribs: Readonly<Canvas3DContext.Attribs>
readonly contextLost: BehaviorSubject<now.Timestamp>
readonly contextRestored: BehaviorSubject<now.Timestamp>
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
}
namespace Canvas3DContext {
const DefaultAttribs = {
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
/** true to support multiple Canvas3D objects with a single context */
preserveDrawingBuffer: true,
pixelScale: 1,
pickScale: 0.25,
enableWboit: true
};
export type Attribs = typeof DefaultAttribs
export function fromCanvas(canvas: HTMLCanvasElement, attribs: Partial<Attribs> = {}): Canvas3DContext {
const a = { ...DefaultAttribs, ...attribs };
const { antialias, preserveDrawingBuffer, pixelScale } = a;
const gl = getGLContext(canvas, {
antialias,
preserveDrawingBuffer,
alpha: true, // the renderer requires an alpha channel
depth: true, // the renderer requires a depth buffer
premultipliedAlpha: true, // the renderer outputs PMA
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const input = InputObserver.fromElement(canvas, { pixelScale });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
if (isDebugMode) {
const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
// Hold down shift+ctrl+alt and press any mouse button to call `loseContext`.
// After 1 second `restoreContext` will be called.
canvas.addEventListener('mousedown', e => {
if (webgl.isContextLost) return;
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
if (isDebugMode) console.log('lose context');
loseContextExt.loseContext();
setTimeout(() => {
if (!webgl.isContextLost) return;
if (isDebugMode) console.log('restore context');
loseContextExt.restoreContext();
}, 1000);
}, false);
}
}
// https://www.khronos.org/webgl/wiki/HandlingContextLost
const contextLost = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const handleWebglContextLost = (e: Event) => {
webgl.setContextLost();
e.preventDefault();
if (isDebugMode) console.log('context lost');
contextLost.next(now());
};
const handlewWebglContextRestored = () => {
if (!webgl.isContextLost) return;
webgl.handleContextRestored(() => {
passes.draw.reset();
});
if (isDebugMode) console.log('context restored');
};
canvas.addEventListener('webglcontextlost', handleWebglContextLost, false);
canvas.addEventListener('webglcontextrestored', handlewWebglContextRestored, false);
return {
canvas,
webgl,
input,
passes,
attribs: a,
contextLost,
contextRestored: webgl.contextRestored,
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => {
input.dispose();
canvas.removeEventListener('webglcontextlost', handleWebglContextLost, false);
canvas.removeEventListener('webglcontextrestored', handlewWebglContextRestored, false);
webgl.destroy(options);
}
};
}
}
export { Canvas3D };
interface Canvas3D {
@@ -117,10 +221,13 @@ interface Canvas3D {
notifyDidDraw: boolean,
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly commited: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
readonly resized: BehaviorSubject<any>
handleResize(): void
/** performs handleResize on the next animation frame */
requestResize(): void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
readonly camera: Camera
@@ -149,58 +256,7 @@ namespace Canvas3D {
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ antialias: boolean, pixelScale: number, pickScale: number, enableWboit: boolean }> = {}) {
const gl = getGLContext(canvas, {
alpha: true,
antialias: (attribs.antialias ?? true) && !attribs.enableWboit,
depth: true,
preserveDrawingBuffer: true,
premultipliedAlpha: true,
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const { pixelScale } = attribs;
const input = InputObserver.fromElement(canvas, { pixelScale });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
if (isDebugMode) {
const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
canvas.addEventListener('mousedown', e => {
if (webgl.isContextLost) return;
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
if (isDebugMode) console.log('lose context');
loseContextExt.loseContext();
setTimeout(() => {
if (!webgl.isContextLost) return;
if (isDebugMode) console.log('restore context');
loseContextExt.restoreContext();
}, 1000);
}, false);
}
}
// https://www.khronos.org/webgl/wiki/HandlingContextLost
canvas.addEventListener('webglcontextlost', e => {
webgl.setContextLost();
e.preventDefault();
if (isDebugMode) console.log('context lost');
}, false);
canvas.addEventListener('webglcontextrestored', () => {
if (!webgl.isContextLost) return;
webgl.handleContextRestored();
if (isDebugMode) console.log('context restored');
}, false);
return create(webgl, input, passes, props, { pixelScale });
}
export function create(webgl: WebGLContext, input: InputObserver, passes: Passes, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ pixelScale: number }>): Canvas3D {
export function create({ webgl, input, passes, attribs }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
@@ -209,6 +265,7 @@ namespace Canvas3D {
let startTime = now();
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const commited = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const { gl, contextRestored } = webgl;
@@ -240,6 +297,7 @@ namespace Canvas3D {
let cameraResetRequested = false;
let nextCameraResetDuration: number | undefined = void 0;
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
let resizeRequested = false;
let notifyDidDraw = true;
@@ -282,6 +340,14 @@ namespace Canvas3D {
function render(force: boolean) {
if (webgl.isContextLost) return false;
let resized = false;
if (resizeRequested) {
handleResize(false);
resizeRequested = false;
resized = true;
}
if (x > gl.drawingBufferWidth || x + width < 0 ||
y > gl.drawingBufferHeight || y + height < 0
) return false;
@@ -291,7 +357,7 @@ namespace Canvas3D {
const cameraChanged = camera.update();
const multiSampleChanged = multiSampleHelper.update(force || cameraChanged, p.multiSample);
if (force || cameraChanged || multiSampleChanged) {
if (resized || force || cameraChanged || multiSampleChanged) {
let cam: Camera | StereoCamera = camera;
if (p.camera.stereo.name === 'on') {
stereoCamera.update();
@@ -301,9 +367,7 @@ namespace Canvas3D {
if (MultiSamplePass.isEnabled(p.multiSample)) {
multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
} else {
const toDrawingBuffer = !PostprocessingPass.isEnabled(p.postprocessing) && scene.volumes.renderables.length === 0 && !passes.draw.wboitEnabled;
passes.draw.render(renderer, cam, scene, helper, toDrawingBuffer, p.transparentBackground);
if (!toDrawingBuffer) passes.postprocessing.render(cam, true, p.postprocessing);
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing);
}
pickHelper.dirty = true;
didRender = true;
@@ -382,6 +446,7 @@ namespace Canvas3D {
draw(true);
forceDrawAfterAllCommited = false;
}
commited.next(now());
}
}
@@ -455,8 +520,16 @@ namespace Canvas3D {
drawCount: r.values.drawCount.ref.value,
instanceCount: r.values.instanceCount.ref.value,
materialId: r.materialId,
renderItemId: r.id,
})));
console.log(webgl.stats);
const { texture, attribute, elements } = webgl.resources.getByteCounts();
console.log({
texture: `${(texture / 1024 / 1024).toFixed(3)} MiB`,
attribute: `${(attribute / 1024 / 1024).toFixed(3)} MiB`,
elements: `${(elements / 1024 / 1024).toFixed(3)} MiB`,
});
}
function add(repr: Representation.Any) {
@@ -541,10 +614,22 @@ namespace Canvas3D {
const contextRestoredSub = contextRestored.subscribe(() => {
pickHelper.dirty = true;
draw(true);
// Unclear why, but in Chrome with wboit enabled the first `draw` only clears
// the drawingBuffer. Note that in Firefox the drawingBuffer is preserved after
// context loss so it is unclear if it behaves the same.
draw(true);
});
const resized = new BehaviorSubject<any>(0);
function handleResize(draw = true) {
passes.updateSize();
updateViewport();
syncViewport();
if (draw) requestDraw(true);
resized.next(+new Date());
}
return {
webgl,
@@ -590,12 +675,9 @@ namespace Canvas3D {
mark,
getLoci,
handleResize: () => {
passes.updateSize();
updateViewport();
syncViewport();
requestDraw(true);
resized.next(+new Date());
handleResize,
requestResize: () => {
resizeRequested = true;
},
requestCameraReset: options => {
nextCameraResetDuration = options?.durationMs;
@@ -607,6 +689,7 @@ namespace Canvas3D {
get notifyDidDraw() { return notifyDidDraw; },
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
didDraw,
commited,
reprCount,
resized,
setProps: (properties, doNotRequestDraw = false) => {
@@ -688,7 +771,6 @@ namespace Canvas3D {
scene.clear();
helper.debug.clear();
input.dispose();
controls.dispose();
renderer.dispose();
interactionHelper.dispose();

View File

@@ -36,8 +36,8 @@ export const DefaultTrackballBindings = {
export const TrackballControlsParams = {
noScroll: PD.Boolean(true, { isHidden: true }),
rotateSpeed: PD.Numeric(3.0, { min: 0.1, max: 10, step: 0.1 }),
zoomSpeed: PD.Numeric(6.0, { min: 0.1, max: 10, step: 0.1 }),
rotateSpeed: PD.Numeric(5.0, { min: 1, max: 10, step: 1 }),
zoomSpeed: PD.Numeric(7.0, { min: 1, max: 15, step: 1 }),
panSpeed: PD.Numeric(1.0, { min: 0.1, max: 5, step: 0.1 }),
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
@@ -138,7 +138,8 @@ namespace TrackballControls {
const dy = _rotCurr[1] - _rotPrev[1];
Vec3.set(rotMoveDir, dx, dy, 0);
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio;
const aspectRatio = input.width / input.height;
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio * aspectRatio;
if (angle) {
Vec3.sub(_eye, camera.position, camera.target);

View File

@@ -160,5 +160,5 @@ const instanceMaterialId = getNextMaterialId();
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false }, materialId);
return createRenderObject('mesh', values, { disposed: false, visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false, noClip: false }, materialId);
}

View File

@@ -70,6 +70,7 @@ export class CameraHelper {
this.scene.clear();
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
this.renderObject = createAxesRenderObject(params);
this.renderObject.state.noClip = true;
this.scene.add(this.renderObject);
this.scene.commit();

View File

@@ -72,6 +72,7 @@ export class HandleHelper {
this.scene.clear();
const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
this.renderObject = createHandleRenderObject(params);
this.renderObject.state.noClip = true;
this.scene.add(this.renderObject);
this.scene.commit();

View File

@@ -22,8 +22,10 @@ import { Helper } from '../helper/helper';
import quad_vert from '../../mol-gl/shader/quad.vert';
import depthMerge_frag from '../../mol-gl/shader/depth-merge.frag';
import { copy_frag } from '../../mol-gl/shader/copy.frag';
import { StereoCamera } from '../camera/stereo';
import { WboitPass } from './wboit';
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
const DepthMergeSchema = {
...QuadSchema,
@@ -50,6 +52,27 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
return createComputeRenderable(renderItem, values);
}
const CopySchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
function getCopyRenderable(ctx: WebGLContext, colorTexture: Texture): CopyRenderable {
const values: Values<typeof CopySchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
};
const schema = { ...CopySchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export class DrawPass {
private readonly drawTarget: RenderTarget
@@ -57,17 +80,23 @@ export class DrawPass {
readonly depthTexture: Texture
readonly depthTexturePrimitives: Texture
private readonly packedDepth: boolean
readonly packedDepth: boolean
private depthTarget: RenderTarget
private depthTargetPrimitives: RenderTarget | null
private depthTargetVolumes: RenderTarget | null
private depthTextureVolumes: Texture
private depthMerge: DepthMergeRenderable
private copyFboTarget: CopyRenderable
private copyFboPostprocessing: CopyRenderable
private wboit: WboitPass | undefined
readonly postprocessing: PostprocessingPass
private readonly antialiasing: AntialiasingPass
get wboitEnabled() {
return !!this.wboit?.enabled;
return !!this.wboit?.supported;
}
constructor(private webgl: WebGLContext, width: number, height: number, enableWboit: boolean) {
@@ -75,7 +104,7 @@ export class DrawPass {
this.drawTarget = createNullRenderTarget(webgl.gl);
this.colorTarget = webgl.createRenderTarget(width, height);
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
this.packedDepth = !extensions.depthTexture;
this.depthTarget = webgl.createRenderTarget(width, height);
@@ -93,6 +122,15 @@ export class DrawPass {
this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth);
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
this.postprocessing = new PostprocessingPass(webgl, this);
this.antialiasing = new AntialiasingPass(webgl, this);
this.copyFboTarget = getCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = getCopyRenderable(webgl, this.postprocessing.target.texture);
}
reset() {
this.wboit?.reset();
}
setSize(width: number, height: number) {
@@ -117,9 +155,15 @@ export class DrawPass {
ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, width, height));
if (this.wboit?.enabled) {
ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height));
ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height));
if (this.wboit?.supported) {
this.wboit.setSize(width, height);
}
this.postprocessing.setSize(width, height);
this.antialiasing.setSize(width, height);
}
}
@@ -137,41 +181,50 @@ export class DrawPass {
this.depthMerge.render();
}
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean) {
if (!this.wboit?.enabled) throw new Error('expected wboit to be enabled');
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
const renderTarget = toDrawingBuffer ? this.drawTarget : this.colorTarget;
renderTarget.bind();
this.colorTarget.bind();
renderer.clear(true);
// render opaque primitives
this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth');
renderTarget.bind();
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
renderer.clearDepth();
renderer.renderWboitOpaque(scene.primitives, camera, null);
// render opaque volumes
this.depthTextureVolumes.attachFramebuffer(renderTarget.framebuffer, 'depth');
renderTarget.bind();
this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
renderer.clearDepth();
renderer.renderWboitOpaque(scene.volumes, camera, this.depthTexturePrimitives);
// merge depth of opaque primitives and volumes
this._depthMerge();
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
}
// render transparent primitives and volumes
this.wboit.bind();
renderer.renderWboitTransparent(scene.primitives, camera, this.depthTexture);
renderer.renderWboitTransparent(scene.volumes, camera, this.depthTexture);
// evaluate wboit
this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth');
renderTarget.bind();
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.depthTexturePrimitives.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
this.postprocessing.target.bind();
} else {
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
this.colorTarget.bind();
}
this.wboit.render();
}
private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean) {
private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (toDrawingBuffer) {
this.webgl.unbindFramebuffer();
this.drawTarget.bind();
} else {
this.colorTarget.bind();
if (!this.packedDepth) {
@@ -182,22 +235,23 @@ export class DrawPass {
renderer.clear(true);
renderer.renderBlendedOpaque(scene.primitives, camera, null);
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (!toDrawingBuffer && this.depthTargetPrimitives) {
this.depthTargetPrimitives.bind();
renderer.clear(false);
renderer.renderDepth(scene.primitives, camera, null);
this.colorTarget.bind();
}
// do direct-volume rendering
if (!toDrawingBuffer) {
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (this.depthTargetPrimitives) {
this.depthTargetPrimitives.bind();
renderer.clear(false);
// TODO: this should only render opaque
renderer.renderDepth(scene.primitives, camera, null);
this.colorTarget.bind();
}
// do direct-volume rendering
if (!this.packedDepth) {
this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
renderer.clearDepth(); // from previous frame
}
renderer.renderBlendedVolume(scene.volumes, camera, this.depthTexturePrimitives);
renderer.renderBlendedVolumeOpaque(scene.volumes, camera, this.depthTexturePrimitives);
// do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (this.depthTargetVolumes) {
@@ -207,29 +261,47 @@ export class DrawPass {
this.colorTarget.bind();
}
if (!this.packedDepth) {
this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
// merge depths from primitive and volume rendering
this._depthMerge();
this.colorTarget.bind();
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
}
renderer.renderBlendedVolumeTransparent(scene.volumes, camera, this.depthTexturePrimitives);
const target = PostprocessingPass.isEnabled(postprocessingProps)
? this.postprocessing.target : this.colorTarget;
if (!this.packedDepth) {
this.depthTexturePrimitives.attachFramebuffer(target.framebuffer, 'depth');
}
target.bind();
}
renderer.renderBlendedTransparent(scene.primitives, camera, null);
// merge depths from primitive and volume rendering
if (!toDrawingBuffer) {
this._depthMerge();
this.colorTarget.bind();
}
}
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean) {
private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
const volumeRendering = scene.volumes.renderables.length > 0;
const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps);
const { x, y, width, height } = camera.viewport;
renderer.setViewport(x, y, width, height);
renderer.update(camera);
if (this.wboitEnabled) {
this._renderWboit(renderer, camera, scene, toDrawingBuffer);
this._renderWboit(renderer, camera, scene, transparentBackground, postprocessingProps);
} else {
this._renderBlended(renderer, camera, scene, toDrawingBuffer);
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, postprocessingProps);
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.postprocessing.target.bind();
} else if (!toDrawingBuffer || volumeRendering || this.wboitEnabled) {
this.colorTarget.bind();
} else {
this.drawTarget.bind();
}
if (helper.debug.isEnabled) {
@@ -245,18 +317,40 @@ export class DrawPass {
renderer.renderBlended(helper.camera.scene, helper.camera.camera, null);
}
if (antialiasingEnabled) {
this.antialiasing.render(camera, toDrawingBuffer, postprocessingProps);
} else if (toDrawingBuffer) {
this.drawTarget.bind();
this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.copyFboPostprocessing.render();
} else if (volumeRendering || this.wboitEnabled) {
this.copyFboTarget.render();
}
}
this.webgl.gl.flush();
}
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean) {
render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
renderer.setTransparentBackground(transparentBackground);
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
if (StereoCamera.is(camera)) {
this._render(renderer, camera.left, scene, helper, toDrawingBuffer);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer);
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
} else {
this._render(renderer, camera, scene, helper, toDrawingBuffer);
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps);
}
}
getColorTarget(postprocessingProps: PostprocessingProps): RenderTarget {
if (AntialiasingPass.isEnabled(postprocessingProps)) {
return this.antialiasing.target;
} else if (PostprocessingPass.isEnabled(postprocessingProps)) {
return this.postprocessing.target;
}
return this.colorTarget;
}
}

View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { TextureSpec, UniformSpec, DefineSpec, Values } from '../../mol-gl/renderable/schema';
import { ShaderCode } from '../../mol-gl/shader-code';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { Texture } from '../../mol-gl/webgl/texture';
import { Vec2 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import quad_vert from '../../mol-gl/shader/quad.vert';
import fxaa_frag from '../../mol-gl/shader/fxaa.frag';
import { Viewport } from '../camera/util';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
export const FxaaParams = {
edgeThresholdMin: PD.Numeric(0.0312, { min: 0.0312, max: 0.0833, step: 0.0001 }, { description: 'Trims the algorithm from processing darks.' }),
edgeThresholdMax: PD.Numeric(0.063, { min: 0.063, max: 0.333, step: 0.001 }, { description: 'The minimum amount of local contrast required to apply algorithm.' }),
iterations: PD.Numeric(12, { min: 0, max: 16, step: 1 }, { description: 'Number of edge exploration steps.' }),
subpixelQuality: PD.Numeric(0.30, { min: 0.00, max: 1.00, step: 0.01 }, { description: 'Choose the amount of sub-pixel aliasing removal.' }),
};
export type FxaaProps = PD.Values<typeof FxaaParams>
export class FxaaPass {
private readonly renderable: FxaaRenderable
constructor(private webgl: WebGLContext, input: Texture) {
this.renderable = getFxaaRenderable(webgl, input);
}
private updateState(viewport: Viewport) {
const { gl, state } = this.webgl;
state.enable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
const { x, y, width, height } = viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
setSize(width: number, height: number) {
ValueCell.update(this.renderable.values.uTexSizeInv, Vec2.set(this.renderable.values.uTexSizeInv.ref.value, 1 / width, 1 / height));
}
update(input: Texture, props: FxaaProps) {
const { values } = this.renderable;
const { edgeThresholdMin, edgeThresholdMax, iterations, subpixelQuality } = props;
let needsUpdate = false;
if (values.tColor.ref.value !== input) {
ValueCell.update(this.renderable.values.tColor, input);
needsUpdate = true;
}
if (values.dEdgeThresholdMin.ref.value !== edgeThresholdMin) needsUpdate = true;
ValueCell.updateIfChanged(values.dEdgeThresholdMin, edgeThresholdMin);
if (values.dEdgeThresholdMax.ref.value !== edgeThresholdMax) needsUpdate = true;
ValueCell.updateIfChanged(values.dEdgeThresholdMax, edgeThresholdMax);
if (values.dIterations.ref.value !== iterations) needsUpdate = true;
ValueCell.updateIfChanged(values.dIterations, iterations);
if (values.dSubpixelQuality.ref.value !== subpixelQuality) needsUpdate = true;
ValueCell.updateIfChanged(values.dSubpixelQuality, subpixelQuality);
if (needsUpdate) {
this.renderable.update();
}
}
render(viewport: Viewport, target: RenderTarget | undefined) {
if (target) {
target.bind();
} else {
this.webgl.unbindFramebuffer();
}
this.updateState(viewport);
this.renderable.render();
}
}
//
const FxaaSchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uTexSizeInv: UniformSpec('v2'),
dEdgeThresholdMin: DefineSpec('number'),
dEdgeThresholdMax: DefineSpec('number'),
dIterations: DefineSpec('number'),
dSubpixelQuality: DefineSpec('number'),
};
const FxaaShaderCode = ShaderCode('fxaa', quad_vert, fxaa_frag);
type FxaaRenderable = ComputeRenderable<Values<typeof FxaaSchema>>
function getFxaaRenderable(ctx: WebGLContext, colorTexture: Texture): FxaaRenderable {
const width = colorTexture.getWidth();
const height = colorTexture.getHeight();
const values: Values<typeof FxaaSchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)),
dEdgeThresholdMin: ValueCell.create(0.0312),
dEdgeThresholdMax: ValueCell.create(0.125),
dIterations: ValueCell.create(12),
dSubpixelQuality: ValueCell.create(0.3),
};
const schema = { ...FxaaSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', FxaaShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}

View File

@@ -10,7 +10,7 @@ import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { DrawPass } from './draw';
import { PostprocessingPass, PostprocessingParams } from './postprocessing';
import { PostprocessingParams } from './postprocessing';
import { MultiSamplePass, MultiSampleParams, MultiSampleHelper } from './multi-sample';
import { Camera } from '../camera';
import { Viewport } from '../camera/util';
@@ -38,7 +38,6 @@ export class ImagePass {
get colorTarget() { return this._colorTarget; }
private readonly drawPass: DrawPass
private readonly postprocessingPass: PostprocessingPass
private readonly multiSamplePass: MultiSamplePass
private readonly multiSampleHelper: MultiSampleHelper
private readonly helper: Helper
@@ -50,8 +49,7 @@ export class ImagePass {
this.props = { ...PD.getDefaultValues(ImageParams), ...props };
this.drawPass = new DrawPass(webgl, 128, 128, enableWboit);
this.postprocessingPass = new PostprocessingPass(webgl, this.drawPass);
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass, this.postprocessingPass);
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
this.helper = {
@@ -70,7 +68,6 @@ export class ImagePass {
this._height = height;
this.drawPass.setSize(width, height);
this.postprocessingPass.syncSize();
this.multiSamplePass.syncSize();
}
@@ -88,13 +85,8 @@ export class ImagePass {
this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
this._colorTarget = this.multiSamplePass.colorTarget;
} else {
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground);
if (PostprocessingPass.isEnabled(this.props.postprocessing)) {
this.postprocessingPass.render(this._camera, false, this.props.postprocessing);
this._colorTarget = this.postprocessingPass.target;
} else {
this._colorTarget = this.drawPass.colorTarget;
}
this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing);
this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -16,7 +16,7 @@ import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/rendera
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { Camera } from '../../mol-canvas3d/camera';
import { PostprocessingPass, PostprocessingProps } from './postprocessing';
import { PostprocessingProps } from './postprocessing';
import { DrawPass } from './draw';
import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
@@ -68,12 +68,14 @@ export class MultiSamplePass {
private holdTarget: RenderTarget
private compose: ComposeRenderable
constructor(private webgl: WebGLContext, private drawPass: DrawPass, private postprocessing: PostprocessingPass) {
const { colorBufferFloat, textureFloat } = webgl.extensions;
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
const { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } = webgl.extensions;
const width = drawPass.colorTarget.getWidth();
const height = drawPass.colorTarget.getHeight();
this.colorTarget = webgl.createRenderTarget(width, height, false);
this.composeTarget = webgl.createRenderTarget(width, height, false, colorBufferFloat && textureFloat ? 'float32' : 'uint8');
const type = colorBufferHalfFloat && textureHalfFloat ? 'fp16' :
colorBufferFloat && textureFloat ? 'float32' : 'uint8';
this.composeTarget = webgl.createRenderTarget(width, height, false, type);
this.holdTarget = webgl.createRenderTarget(width, height, false);
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture);
}
@@ -109,7 +111,7 @@ export class MultiSamplePass {
}
private renderMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
const { compose, composeTarget, drawPass, postprocessing, webgl } = this;
const { compose, composeTarget, drawPass, webgl } = this;
const { gl, state } = webgl;
// based on the Multisample Anti-Aliasing Render Pass
@@ -123,10 +125,8 @@ export class MultiSamplePass {
const baseSampleWeight = 1.0 / offsetList.length;
const roundingRange = 1 / 32;
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
camera.viewOffset.enabled = true;
ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
compose.update();
// render the scene multiple times, each slightly jitter offset
@@ -143,9 +143,8 @@ export class MultiSamplePass {
const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution;
ValueCell.update(compose.values.uWeight, sampleWeight);
// render scene and optionally postprocess
drawPass.render(renderer, camera, scene, helper, false, transparentBackground);
if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing);
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
// compose rendered scene with compose target
composeTarget.bind();
@@ -179,7 +178,7 @@ export class MultiSamplePass {
}
private renderTemporalMultiSample(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
const { compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this;
const { compose, composeTarget, holdTarget, drawPass, webgl } = this;
const { gl, state } = webgl;
// based on the Multisample Anti-Aliasing Render Pass
@@ -193,13 +192,11 @@ export class MultiSamplePass {
const { x, y, width, height } = camera.viewport;
const sampleWeight = 1.0 / offsetList.length;
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
if (sampleIndex === -1) {
drawPass.render(renderer, camera, scene, helper, false, transparentBackground);
if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing);
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
ValueCell.update(compose.values.uWeight, 1.0);
ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
compose.update();
holdTarget.bind();
@@ -212,7 +209,7 @@ export class MultiSamplePass {
sampleIndex += 1;
} else {
camera.viewOffset.enabled = true;
ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
ValueCell.update(compose.values.uWeight, sampleWeight);
compose.update();
@@ -224,9 +221,8 @@ export class MultiSamplePass {
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
camera.update();
// render scene and optionally postprocess
drawPass.render(renderer, camera, scene, helper, false, transparentBackground);
if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing);
// render scene
drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing);
// compose rendered scene with compose target
composeTarget.bind();

View File

@@ -6,29 +6,25 @@
import { DrawPass } from './draw';
import { PickPass } from './pick';
import { PostprocessingPass } from './postprocessing';
import { MultiSamplePass } from './multi-sample';
import { WebGLContext } from '../../mol-gl/webgl/context';
export class Passes {
readonly draw: DrawPass
readonly pick: PickPass
readonly postprocessing: PostprocessingPass
readonly multiSample: MultiSamplePass
constructor(private webgl: WebGLContext, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
const { gl } = webgl;
this.draw = new DrawPass(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25);
this.postprocessing = new PostprocessingPass(webgl, this.draw);
this.multiSample = new MultiSamplePass(webgl, this.draw, this.postprocessing);
this.multiSample = new MultiSamplePass(webgl, this.draw);
}
updateSize() {
const { gl } = this.webgl;
this.draw.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight);
this.pick.syncSize();
this.postprocessing.syncSize();
this.multiSample.syncSize();
}
}

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
*/
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
@@ -12,20 +13,174 @@ import { Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { DrawPass } from './draw';
import { Camera, ICamera } from '../../mol-canvas3d/camera';
import { ICamera } from '../../mol-canvas3d/camera';
import quad_vert from '../../mol-gl/shader/quad.vert';
import outlines_frag from '../../mol-gl/shader/outlines.frag';
import ssao_frag from '../../mol-gl/shader/ssao.frag';
import ssao_blur_frag from '../../mol-gl/shader/ssao-blur.frag';
import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag';
import fxaa_frag from '../../mol-gl/shader/fxaa.frag';
import { StereoCamera } from '../camera/stereo';
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
import { Color } from '../../mol-util/color';
import { FxaaParams, FxaaPass } from './fxaa';
import { SmaaParams, SmaaPass } from './smaa';
const OutlinesSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
dOrthographic: DefineSpec('number'),
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
uMaxPossibleViewZDiff: UniformSpec('f'),
};
type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
function getOutlinesRenderable(ctx: WebGLContext, depthTexture: Texture): OutlinesRenderable {
const values: Values<typeof OutlinesSchema> = {
...QuadValues,
tDepth: ValueCell.create(depthTexture),
uTexSize: ValueCell.create(Vec2.create(depthTexture.getWidth(), depthTexture.getHeight())),
dOrthographic: ValueCell.create(0),
uNear: ValueCell.create(1),
uFar: ValueCell.create(10000),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
};
const schema = { ...OutlinesSchema };
const shaderCode = ShaderCode('outlines', quad_vert, outlines_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
const SsaoSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uSamples: UniformSpec('v3[]'),
dNSamples: DefineSpec('number'),
uProjection: UniformSpec('m4'),
uInvProjection: UniformSpec('m4'),
uTexSize: UniformSpec('v2'),
uRadius: UniformSpec('f'),
uBias: UniformSpec('f'),
};
type SsaoRenderable = ComputeRenderable<Values<typeof SsaoSchema>>
function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRenderable {
const values: Values<typeof SsaoSchema> = {
...QuadValues,
tDepth: ValueCell.create(depthTexture),
uSamples: ValueCell.create([0.0, 0.0, 1.0]),
dNSamples: ValueCell.create(1),
uProjection: ValueCell.create(Mat4.identity()),
uInvProjection: ValueCell.create(Mat4.identity()),
uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
uRadius: ValueCell.create(8.0),
uBias: ValueCell.create(0.025),
};
const schema = { ...SsaoSchema };
const shaderCode = ShaderCode('ssao', quad_vert, ssao_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
const SsaoBlurSchema = {
...QuadSchema,
tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
uKernel: UniformSpec('f[]'),
dOcclusionKernelSize: DefineSpec('number'),
uBlurDirectionX: UniformSpec('f'),
uBlurDirectionY: UniformSpec('f'),
uMaxPossibleViewZDiff: UniformSpec('f'),
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
dOrthographic: DefineSpec('number'),
};
type SsaoBlurRenderable = ComputeRenderable<Values<typeof SsaoBlurSchema>>
function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, direction: 'horizontal' | 'vertical'): SsaoBlurRenderable {
const values: Values<typeof SsaoBlurSchema> = {
...QuadValues,
tSsaoDepth: ValueCell.create(ssaoDepthTexture),
uTexSize: ValueCell.create(Vec2.create(ssaoDepthTexture.getWidth(), ssaoDepthTexture.getHeight())),
uKernel: ValueCell.create([0.0]),
dOcclusionKernelSize: ValueCell.create(1),
uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0),
uBlurDirectionY: ValueCell.create(direction === 'vertical' ? 1 : 0),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
uNear: ValueCell.create(0.0),
uFar: ValueCell.create(10000.0),
dOrthographic: ValueCell.create(0),
};
const schema = { ...SsaoBlurSchema };
const shaderCode = ShaderCode('ssao_blur', quad_vert, ssao_blur_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function getBlurKernel(kernelSize: number): number[] {
let sigma = kernelSize / 3.0;
let halfKernelSize = Math.floor((kernelSize + 1) / 2);
let kernel = [];
for (let x = 0; x < halfKernelSize; x++) {
kernel.push((1.0 / ((Math.sqrt(2 * Math.PI)) * sigma)) * Math.exp(-x * x / (2 * sigma * sigma)));
}
return kernel;
}
function getSamples(vectorSamples: Vec3[], nSamples: number): number[] {
let samples = [];
for (let i = 0; i < nSamples; i++) {
let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples);
scale = 0.1 + scale * (1.0 - 0.1);
samples.push(vectorSamples[i][0] * scale);
samples.push(vectorSamples[i][1] * scale);
samples.push(vectorSamples[i][2] * scale);
}
return samples;
}
const PostprocessingSchema = {
...QuadSchema,
tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tPackedDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
dOrthographic: DefineSpec('number'),
@@ -34,24 +189,25 @@ const PostprocessingSchema = {
uFogNear: UniformSpec('f'),
uFogFar: UniformSpec('f'),
uFogColor: UniformSpec('v3'),
uTransparentBackground: UniformSpec('b'),
uMaxPossibleViewZDiff: UniformSpec('f'),
dOcclusionEnable: DefineSpec('boolean'),
dOcclusionKernelSize: DefineSpec('number'),
uOcclusionBias: UniformSpec('f'),
uOcclusionRadius: UniformSpec('f'),
dOutlineEnable: DefineSpec('boolean'),
uOutlineScale: UniformSpec('f'),
dOutlineScale: DefineSpec('number'),
uOutlineThreshold: UniformSpec('f'),
};
const PostprocessingShaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag);
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture): PostprocessingRenderable {
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
const values: Values<typeof PostprocessingSchema> = {
...QuadValues,
tSsaoDepth: ValueCell.create(ssaoDepthTexture),
tColor: ValueCell.create(colorTexture),
tPackedDepth: ValueCell.create(depthTexture),
tDepth: ValueCell.create(depthTexture),
tOutlines: ValueCell.create(outlinesTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
dOrthographic: ValueCell.create(0),
@@ -60,40 +216,46 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
uFogNear: ValueCell.create(10000),
uFogFar: ValueCell.create(10000),
uFogColor: ValueCell.create(Vec3.create(1, 1, 1)),
uTransparentBackground: ValueCell.create(false),
uMaxPossibleViewZDiff: ValueCell.create(0.5),
dOcclusionEnable: ValueCell.create(false),
dOcclusionKernelSize: ValueCell.create(4),
uOcclusionBias: ValueCell.create(0.5),
uOcclusionRadius: ValueCell.create(64),
dOutlineEnable: ValueCell.create(false),
uOutlineScale: ValueCell.create(1 * ctx.pixelRatio),
uOutlineThreshold: ValueCell.create(0.8),
dOutlineScale: ValueCell.create(1),
uOutlineThreshold: ValueCell.create(0.33),
};
const schema = { ...PostprocessingSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', PostprocessingShaderCode, schema, values);
const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export const PostprocessingParams = {
occlusion: PD.MappedStatic('off', {
occlusion: PD.MappedStatic('on', {
on: PD.Group({
kernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
bias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
radius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
samples: PD.Numeric(32, {min: 1, max: 256, step: 1}),
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
outline: PD.MappedStatic('off', {
on: PD.Group({
scale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
threshold: PD.Numeric(0.8, { min: 0, max: 5, step: 0.01 }),
scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Draw outline around 3D objects' }),
antialiasing: PD.Boolean(true, { description: 'Fast Approximate Anti-Aliasing (FXAA)' })
antialiasing: PD.MappedStatic('smaa', {
fxaa: PD.Group(FxaaParams),
smaa: PD.Group(SmaaParams),
off: PD.Group({})
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
};
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
@@ -104,38 +266,188 @@ export class PostprocessingPass {
readonly target: RenderTarget
private readonly tmpTarget: RenderTarget
private readonly renderable: PostprocessingRenderable
private readonly fxaa: FxaaRenderable
private readonly outlinesTarget: RenderTarget
private readonly outlinesRenderable: OutlinesRenderable
private readonly randomHemisphereVector: Vec3[]
private readonly ssaoFramebuffer: Framebuffer
private readonly ssaoBlurFirstPassFramebuffer: Framebuffer
private readonly ssaoBlurSecondPassFramebuffer: Framebuffer
private readonly ssaoDepthTexture: Texture
private readonly ssaoDepthBlurProxyTexture: Texture
private readonly ssaoRenderable: SsaoRenderable
private readonly ssaoBlurFirstPassRenderable: SsaoBlurRenderable
private readonly ssaoBlurSecondPassRenderable: SsaoBlurRenderable
private nSamples: number
private blurKernelSize: number
private readonly renderable: PostprocessingRenderable
private scale: number
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
this.scale = 1 / this.webgl.pixelRatio;
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
const { colorTarget, depthTexture } = drawPass;
const width = colorTarget.getWidth();
const height = colorTarget.getHeight();
this.target = webgl.createRenderTarget(width, height, false);
this.tmpTarget = webgl.createRenderTarget(width, height, false);
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture);
this.fxaa = getFxaaRenderable(webgl, this.tmpTarget.texture);
this.nSamples = 1;
this.blurKernelSize = 1;
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
this.randomHemisphereVector = [];
for (let i = 0; i < 256; i++) {
let v = Vec3();
v[0] = Math.random() * 2.0 - 1.0;
v[1] = Math.random() * 2.0 - 1.0;
v[2] = Math.random();
Vec3.normalize(v, v);
Vec3.scale(v, v, Math.random());
this.randomHemisphereVector.push(v);
}
this.ssaoFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthBlurProxyTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture);
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
}
syncSize() {
const width = this.drawPass.colorTarget.getWidth();
const height = this.drawPass.colorTarget.getHeight();
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
this.target.setSize(width, height);
this.tmpTarget.setSize(width, height);
this.outlinesTarget.setSize(width, height);
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.define(sw, sh);
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.fxaa.values.uTexSize, Vec2.set(this.fxaa.values.uTexSize.ref.value, width, height));
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
}
}
private updateState(camera: ICamera) {
private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
let needsUpdateMain = false;
let needsUpdateSsao = false;
let needsUpdateSsaoBlur = false;
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
const outlinesEnabled = props.outline.name === 'on';
const occlusionEnabled = props.occlusion.name === 'on';
let invProjection = Mat4.identity();
Mat4.invert(invProjection, camera.projection);
if (props.occlusion.name === 'on') {
ValueCell.updateIfChanged(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.updateIfChanged(this.ssaoRenderable.values.uInvProjection, invProjection);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateSsaoBlur = true; }
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
if (this.nSamples !== props.occlusion.params.samples) {
needsUpdateSsao = true;
this.nSamples = props.occlusion.params.samples;
ValueCell.updateIfChanged(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
}
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
ValueCell.updateIfChanged(this.ssaoRenderable.values.uBias, props.occlusion.params.bias);
if (this.blurKernelSize !== props.occlusion.params.blurKernelSize) {
needsUpdateSsaoBlur = true;
this.blurKernelSize = props.occlusion.params.blurKernelSize;
let kernel = getBlurKernel(this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
}
}
if (props.outline.name === 'on') {
const factor = Math.pow(1000, props.outline.params.threshold) / 1000;
const maxPossibleViewZDiff = factor * (camera.far - camera.near);
const outlineScale = props.outline.params.scale - 1;
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold);
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
}
ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.renderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.renderable.values.uFogFar, camera.fogFar);
ValueCell.updateIfChanged(this.renderable.values.uFogNear, camera.fogNear);
ValueCell.update(this.renderable.values.uFogColor, Color.toVec3Normalized(this.renderable.values.uFogColor.ref.value, backgroundColor));
ValueCell.updateIfChanged(this.renderable.values.uTransparentBackground, transparentBackground);
if (this.renderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOrthographic, orthographic);
if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, outlinesEnabled);
if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
if (needsUpdateSsao) {
this.ssaoRenderable.update();
}
if (needsUpdateSsaoBlur) {
this.ssaoBlurFirstPassRenderable.update();
this.ssaoBlurSecondPassRenderable.update();
}
if (needsUpdateMain) {
this.renderable.update();
}
const { gl, state } = this.webgl;
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
@@ -143,66 +455,36 @@ export class PostprocessingPass {
const { x, y, width, height } = camera.viewport;
gl.viewport(x, y, width, height);
gl.scissor(x, y, width, height);
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
private _renderPostprocessing(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
const { values } = this.renderable;
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
this.updateState(camera, transparentBackground, backgroundColor, props);
ValueCell.updateIfChanged(values.uFar, camera.far);
ValueCell.updateIfChanged(values.uNear, camera.near);
ValueCell.updateIfChanged(values.uFogFar, camera.fogFar);
ValueCell.updateIfChanged(values.uFogNear, camera.fogNear);
let needsUpdate = false;
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
if (values.dOrthographic.ref.value !== orthographic) needsUpdate = true;
ValueCell.updateIfChanged(values.dOrthographic, orthographic);
const occlusion = props.occlusion.name === 'on';
if (values.dOcclusionEnable.ref.value !== occlusion) needsUpdate = true;
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusion);
if (props.occlusion.name === 'on') {
const { kernelSize } = props.occlusion.params;
if (values.dOcclusionKernelSize.ref.value !== kernelSize) needsUpdate = true;
ValueCell.updateIfChanged(values.dOcclusionKernelSize, kernelSize);
ValueCell.updateIfChanged(values.uOcclusionBias, props.occlusion.params.bias);
ValueCell.updateIfChanged(values.uOcclusionRadius, props.occlusion.params.radius);
}
const outline = props.outline.name === 'on';
if (values.dOutlineEnable.ref.value !== outline) needsUpdate = true;
ValueCell.updateIfChanged(values.dOutlineEnable, outline);
if (props.outline.name === 'on') {
ValueCell.updateIfChanged(values.uOutlineScale, props.outline.params.scale * this.webgl.pixelRatio);
ValueCell.updateIfChanged(values.uOutlineThreshold, props.outline.params.threshold);
this.outlinesTarget.bind();
this.outlinesRenderable.render();
}
if (needsUpdate) {
this.renderable.update();
}
if (props.occlusion.name === 'on') {
const { x, y, width, height } = camera.viewport;
const sx = Math.floor(x * this.scale);
const sy = Math.floor(y * this.scale);
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
this.webgl.gl.viewport(sx, sy, sw, sh);
this.webgl.gl.scissor(sx, sy, sw, sh);
if (props.antialiasing) {
this.tmpTarget.bind();
} else if (toDrawingBuffer) {
this.webgl.unbindFramebuffer();
} else {
this.target.bind();
}
this.ssaoFramebuffer.bind();
this.ssaoRenderable.render();
this.updateState(camera);
this.renderable.render();
}
this.ssaoBlurFirstPassFramebuffer.bind();
this.ssaoBlurFirstPassRenderable.render();
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
const input = (props.occlusion.name === 'on' || props.outline.name === 'on')
? this.tmpTarget.texture : this.drawPass.colorTarget.texture;
if (this.fxaa.values.tColor.ref.value !== input) {
ValueCell.update(this.fxaa.values.tColor, input);
this.fxaa.update();
this.ssaoBlurSecondPassFramebuffer.bind();
this.ssaoBlurSecondPassRenderable.render();
this.webgl.gl.viewport(x, y, width, height);
this.webgl.gl.scissor(x, y, width, height);
}
if (toDrawingBuffer) {
@@ -211,49 +493,75 @@ export class PostprocessingPass {
this.target.bind();
}
this.updateState(camera);
this.fxaa.render();
const { gl, state } = this.webgl;
state.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
this.renderable.render();
}
}
export class AntialiasingPass {
static isEnabled(props: PostprocessingProps) {
return props.antialiasing.name !== 'off';
}
private _render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.occlusion.name === 'on' || props.outline.name === 'on' || !props.antialiasing) {
this._renderPostprocessing(camera, toDrawingBuffer, props);
}
readonly target: RenderTarget
private readonly fxaa: FxaaPass
private readonly smaa: SmaaPass
if (props.antialiasing) {
constructor(webgl: WebGLContext, private drawPass: DrawPass) {
const { colorTarget } = drawPass;
const width = colorTarget.getWidth();
const height = colorTarget.getHeight();
this.target = webgl.createRenderTarget(width, height, false);
this.fxaa = new FxaaPass(webgl, this.target.texture);
this.smaa = new SmaaPass(webgl, this.target.texture);
}
setSize(width: number, height: number) {
const w = this.target.texture.getWidth();
const h = this.target.texture.getHeight();
if (width !== w || height !== h) {
this.target.setSize(width, height);
this.fxaa.setSize(width, height);
if (this.smaa.supported) this.smaa.setSize(width, height);
}
}
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name !== 'fxaa') return;
const input = PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.fxaa.update(input, props.antialiasing.params);
this.fxaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
}
private _renderSmaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name !== 'smaa') return;
const input = PostprocessingPass.isEnabled(props)
? this.drawPass.postprocessing.target.texture
: this.drawPass.colorTarget.texture;
this.smaa.update(input, props.antialiasing.params);
this.smaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target);
}
render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (props.antialiasing.name === 'off') return;
if (props.antialiasing.name === 'fxaa') {
this._renderFxaa(camera, toDrawingBuffer, props);
}
}
render(camera: Camera | StereoCamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
if (StereoCamera.is(camera)) {
this._render(camera.left, toDrawingBuffer, props);
this._render(camera.right, toDrawingBuffer, props);
} else {
this._render(camera, toDrawingBuffer, props);
} else if (props.antialiasing.name === 'smaa') {
if (!this.smaa.supported) {
throw new Error('SMAA not supported, missing "HTMLImageElement"');
}
this._renderSmaa(camera, toDrawingBuffer, props);
}
}
}
//
const FxaaSchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const FxaaShaderCode = ShaderCode('fxaa', quad_vert, fxaa_frag);
type FxaaRenderable = ComputeRenderable<Values<typeof FxaaSchema>>
function getFxaaRenderable(ctx: WebGLContext, colorTexture: Texture): FxaaRenderable {
const values: Values<typeof FxaaSchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
};
const schema = { ...FxaaSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', FxaaShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}

File diff suppressed because one or more lines are too long

View File

@@ -51,9 +51,9 @@ export class WboitPass {
private readonly textureA: Texture
private readonly textureB: Texture
private _enabled = false;
get enabled() {
return this._enabled;
private _supported = false;
get supported() {
return this._supported;
}
bind() {
@@ -89,13 +89,44 @@ export class WboitPass {
}
}
constructor(private webgl: WebGLContext, width: number, height: number) {
const { resources, extensions } = webgl;
const { drawBuffers, textureFloat, colorBufferFloat, depthTexture } = extensions;
reset() {
if (this._supported) this._init();
}
private _init() {
const { extensions: { drawBuffers } } = this.webgl;
this.framebuffer.bind();
drawBuffers!.drawBuffers([
drawBuffers!.COLOR_ATTACHMENT0,
drawBuffers!.COLOR_ATTACHMENT1,
]);
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
}
static isSupported(webgl: WebGLContext) {
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, depthTexture } } = webgl;
if (!textureFloat || !colorBufferFloat || !depthTexture || !drawBuffers) {
if (isDebugMode) console.log('Missing extensions required for "wboit"');
return;
if (isDebugMode) {
const missing: string[] = [];
if (!textureFloat) missing.push('textureFloat');
if (!colorBufferFloat) missing.push('colorBufferFloat');
if (!depthTexture) missing.push('depthTexture');
if (!drawBuffers) missing.push('drawBuffers');
console.log(`Missing "${missing.join('", "')}" extensions required for "wboit"`);
}
return false;
} else {
return true;
}
}
constructor(private webgl: WebGLContext, width: number, height: number) {
if (!WboitPass.isSupported(webgl)) return;
const { resources } = webgl;
this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest');
this.textureA.define(width, height);
@@ -104,17 +135,9 @@ export class WboitPass {
this.textureB.define(width, height);
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
this.framebuffer = resources.framebuffer();
this.framebuffer.bind();
drawBuffers.drawBuffers([
drawBuffers.COLOR_ATTACHMENT0,
drawBuffers.COLOR_ATTACHMENT1,
]);
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
this._enabled = true;
this._supported = true;
this._init();
}
}

View File

@@ -12,13 +12,13 @@ export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height:
}
/** Resize canvas to container element taking `devicePixelRatio` into account */
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element, scale = 1) {
export function resizeCanvas (canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
let width = window.innerWidth;
let height = window.innerHeight;
if (container !== document.body) {
let bounds = container.getBoundingClientRect();
width = bounds.right - bounds.left;
height = bounds.bottom - bounds.top;
// fixes issue #molstar/molstar#147, offsetWidth/offsetHeight is correct size when css transform:scale is used
width = container.offsetWidth;
height = container.offsetHeight;
}
setCanvasSize(canvas, width, height, scale);
}

View File

@@ -364,7 +364,7 @@ function isIdentity(map: ArrayLike<number>, rowCount: number) {
}
function columnView<T>(c: Column<T>, map: ArrayLike<number>, checkIdentity: boolean): Column<T> {
if (!c.isDefined || c.rowCount === 0) return c;
if (c.rowCount === 0) return c;
if (checkIdentity && isIdentity(map, c.rowCount)) return c;
if (!!c.__array && typeof c.value(0) === typeof c.__array[0]) return arrayView(c, map);
return viewFull(c, map);

View File

@@ -75,12 +75,14 @@ export namespace BaseGeometry {
export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {
const opaque = props.alpha === undefined ? true : props.alpha === 1;
return {
disposed: false,
visible: true,
alphaFactor: 1,
pickable: true,
colorOnly: false,
opaque,
writeDepth: opaque,
noClip: false,
};
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ChunkedArray } from '../../../mol-data/util';
import { Cylinders } from './cylinders';
import { Vec3 } from '../../../mol-math/linear-algebra';
export interface CylindersBuilder {
add(startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number): void
addFixedCountDashes(start: Vec3, end: Vec3, segmentCount: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number): void
addFixedLengthDashes(start: Vec3, end: Vec3, segmentLength: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number): void
getCylinders(): Cylinders
}
const tmpVecA = Vec3();
const tmpVecB = Vec3();
const tmpDir = Vec3();
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const caAdd = ChunkedArray.add;
const caAdd3 = ChunkedArray.add3;
export namespace CylindersBuilder {
export function create(initialCount = 2048, chunkSize = 1024, cylinders?: Cylinders): CylindersBuilder {
const groups = ChunkedArray.create(Float32Array, 1, chunkSize, cylinders ? cylinders.groupBuffer.ref.value : initialCount);
const starts = ChunkedArray.create(Float32Array, 3, chunkSize, cylinders ? cylinders.startBuffer.ref.value : initialCount);
const ends = ChunkedArray.create(Float32Array, 3, chunkSize, cylinders ? cylinders.endBuffer.ref.value : initialCount);
const scales = ChunkedArray.create(Float32Array, 1, chunkSize, cylinders ? cylinders.scaleBuffer.ref.value : initialCount);
const caps = ChunkedArray.create(Float32Array, 1, chunkSize, cylinders ? cylinders.capBuffer.ref.value : initialCount);
const add = (startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number) => {
for (let i = 0; i < 6; ++i) {
caAdd3(starts, startX, startY, startZ);
caAdd3(ends, endX, endY, endZ);
caAdd(groups, group);
caAdd(scales, radiusScale);
caAdd(caps, (topCap ? 1 : 0) + (bottomCap ? 2 : 0));
}
};
const addFixedCountDashes = (start: Vec3, end: Vec3, segmentCount: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number) => {
const d = Vec3.distance(start, end);
const s = Math.floor(segmentCount / 2);
const step = 1 / segmentCount;
Vec3.sub(tmpDir, end, start);
for (let j = 0; j < s; ++j) {
const f = step * (j * 2 + 1);
Vec3.setMagnitude(tmpDir, tmpDir, d * f);
Vec3.add(tmpVecA, start, tmpDir);
Vec3.setMagnitude(tmpDir, tmpDir, d * step * ((j + 1) * 2));
Vec3.add(tmpVecB, start, tmpDir);
add(tmpVecA[0], tmpVecA[1], tmpVecA[2], tmpVecB[0], tmpVecB[1], tmpVecB[2], radiusScale, topCap, bottomCap, group);
}
};
return {
add,
addFixedCountDashes,
addFixedLengthDashes: (start: Vec3, end: Vec3, segmentLength: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number) => {
const d = Vec3.distance(start, end);
addFixedCountDashes(start, end, d / segmentLength, radiusScale, topCap, bottomCap, group);
},
getCylinders: () => {
const cylinderCount = groups.elementCount / 6;
const gb = ChunkedArray.compact(groups, true) as Float32Array;
const sb = ChunkedArray.compact(starts, true) as Float32Array;
const eb = ChunkedArray.compact(ends, true) as Float32Array;
const ab = ChunkedArray.compact(scales, true) as Float32Array;
const cb = ChunkedArray.compact(caps, true) as Float32Array;
const mb = cylinders && cylinderCount <= cylinders.cylinderCount ? cylinders.mappingBuffer.ref.value : new Float32Array(cylinderCount * 18);
const ib = cylinders && cylinderCount <= cylinders.cylinderCount ? cylinders.indexBuffer.ref.value : new Uint32Array(cylinderCount * 12);
if (!cylinders || cylinderCount > cylinders.cylinderCount) fillMappingAndIndices(cylinderCount, mb, ib);
return Cylinders.create(mb, ib, gb, sb, eb, ab, cb, cylinderCount, cylinders);
}
};
}
}
function fillMappingAndIndices(n: number, mb: Float32Array, ib: Uint32Array) {
for (let i = 0; i < n; ++i) {
const mo = i * 18;
mb[mo] = -1; mb[mo + 1] = 1; mb[mo + 2] = -1;
mb[mo + 3] = -1; mb[mo + 4] = -1; mb[mo + 5] = -1;
mb[mo + 6] = 1; mb[mo + 7] = 1; mb[mo + 8] = -1;
mb[mo + 9] = 1; mb[mo + 10] = 1; mb[mo + 11] = 1;
mb[mo + 12] = 1; mb[mo + 13] = -1; mb[mo + 14] = -1;
mb[mo + 15] = 1; mb[mo + 16] = -1; mb[mo + 17] = 1;
}
for (let i = 0; i < n; ++i) {
const o = i * 6;
const io = i * 12;
ib[io] = o; ib[io + 1] = o + 1; ib[io + 2] = o + 2;
ib[io + 3] = o + 1; ib[io + 4] = o + 4; ib[io + 5] = o + 2;
ib[io + 6] = o + 2; ib[io + 7] = o + 4; ib[io + 8] = o + 3;
ib[io + 9] = o + 4; ib[io + 10] = o + 5; ib[io + 11] = o + 3;
}
}

View File

@@ -0,0 +1,278 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ValueCell } from '../../../mol-util';
import { Mat4, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util';
import { GeometryUtils } from '../geometry';
import { createColors } from '../color-data';
import { createMarkers } from '../marker-data';
import { createSizes, getMaxSize } from '../size-data';
import { TransformData } from '../transform-data';
import { LocationIterator, PositionLocation } from '../../util/location-iterator';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
import { Sphere3D } from '../../../mol-math/geometry';
import { Theme } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
import { BaseGeometry } from '../base';
import { createEmptyOverpaint } from '../overpaint-data';
import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { CylindersValues } from '../../../mol-gl/renderable/cylinders';
import { RenderableState } from '../../../mol-gl/renderable';
export interface Cylinders {
readonly kind: 'cylinders',
/** Number of cylinders */
cylinderCount: number,
/** Mapping buffer as array of uvw values wrapped in a value cell */
readonly mappingBuffer: ValueCell<Float32Array>,
/** Index buffer as array of vertex index triplets wrapped in a value cell */
readonly indexBuffer: ValueCell<Uint32Array>,
/** Group buffer as array of group ids for each vertex wrapped in a value cell */
readonly groupBuffer: ValueCell<Float32Array>,
/** Cylinder start buffer as array of xyz values wrapped in a value cell */
readonly startBuffer: ValueCell<Float32Array>,
/** Cylinder end buffer as array of xyz values wrapped in a value cell */
readonly endBuffer: ValueCell<Float32Array>,
/** Cylinder scale buffer as array of scaling factors wrapped in a value cell */
readonly scaleBuffer: ValueCell<Float32Array>,
/** Cylinder cap buffer as array of cap flags wrapped in a value cell */
readonly capBuffer: ValueCell<Float32Array>,
/** Bounding sphere of the cylinders */
readonly boundingSphere: Sphere3D
/** Maps group ids to cylinder indices */
readonly groupMapping: GroupMapping
setBoundingSphere(boundingSphere: Sphere3D): void
}
export namespace Cylinders {
export function create(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, scales: Float32Array, caps: Float32Array, cylinderCount: number, cylinders?: Cylinders): Cylinders {
return cylinders ?
update(mappings, indices, groups, starts, ends, scales, caps, cylinderCount, cylinders) :
fromArrays(mappings, indices, groups, starts, ends, scales, caps, cylinderCount);
}
export function createEmpty(cylinders?: Cylinders): Cylinders {
const mb = cylinders ? cylinders.mappingBuffer.ref.value : new Float32Array(0);
const ib = cylinders ? cylinders.indexBuffer.ref.value : new Uint32Array(0);
const gb = cylinders ? cylinders.groupBuffer.ref.value : new Float32Array(0);
const sb = cylinders ? cylinders.startBuffer.ref.value : new Float32Array(0);
const eb = cylinders ? cylinders.endBuffer.ref.value : new Float32Array(0);
const ab = cylinders ? cylinders.scaleBuffer.ref.value : new Float32Array(0);
const cb = cylinders ? cylinders.capBuffer.ref.value : new Float32Array(0);
return create(mb, ib, gb, sb, eb, ab, cb, 0, cylinders);
}
function hashCode(cylinders: Cylinders) {
return hashFnv32a([
cylinders.cylinderCount, cylinders.mappingBuffer.ref.version, cylinders.indexBuffer.ref.version,
cylinders.groupBuffer.ref.version, cylinders.startBuffer.ref.version, cylinders.endBuffer.ref.version, cylinders.scaleBuffer.ref.version, cylinders.capBuffer.ref.version
]);
}
function fromArrays(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, scales: Float32Array, caps: Float32Array, cylinderCount: number): Cylinders {
const boundingSphere = Sphere3D();
let groupMapping: GroupMapping;
let currentHash = -1;
let currentGroup = -1;
const cylinders = {
kind: 'cylinders' as const,
cylinderCount,
mappingBuffer: ValueCell.create(mappings),
indexBuffer: ValueCell.create(indices),
groupBuffer: ValueCell.create(groups),
startBuffer: ValueCell.create(starts),
endBuffer: ValueCell.create(ends),
scaleBuffer: ValueCell.create(scales),
capBuffer: ValueCell.create(caps),
get boundingSphere() {
const newHash = hashCode(cylinders);
if (newHash !== currentHash) {
const s = calculateInvariantBoundingSphere(cylinders.startBuffer.ref.value, cylinders.cylinderCount * 6, 6);
const e = calculateInvariantBoundingSphere(cylinders.endBuffer.ref.value, cylinders.cylinderCount * 6, 6);
Sphere3D.expandBySphere(boundingSphere, s, e);
currentHash = newHash;
}
return boundingSphere;
},
get groupMapping() {
if (cylinders.groupBuffer.ref.version !== currentGroup) {
groupMapping = createGroupMapping(cylinders.groupBuffer.ref.value, cylinders.cylinderCount, 6);
currentGroup = cylinders.groupBuffer.ref.version;
}
return groupMapping;
},
setBoundingSphere(sphere: Sphere3D) {
Sphere3D.copy(boundingSphere, sphere);
currentHash = hashCode(cylinders);
}
};
return cylinders;
}
function update(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, scales: Float32Array, caps: Float32Array, cylinderCount: number, cylinders: Cylinders) {
if (cylinderCount > cylinders.cylinderCount) {
ValueCell.update(cylinders.mappingBuffer, mappings);
ValueCell.update(cylinders.indexBuffer, indices);
}
cylinders.cylinderCount = cylinderCount;
ValueCell.update(cylinders.groupBuffer, groups);
ValueCell.update(cylinders.startBuffer, starts);
ValueCell.update(cylinders.endBuffer, ends);
ValueCell.update(cylinders.scaleBuffer, scales);
ValueCell.update(cylinders.capBuffer, caps);
return cylinders;
}
export function transform(cylinders: Cylinders, t: Mat4) {
const start = cylinders.startBuffer.ref.value;
transformPositionArray(t, start, 0, cylinders.cylinderCount * 6);
ValueCell.update(cylinders.startBuffer, start);
const end = cylinders.endBuffer.ref.value;
transformPositionArray(t, end, 0, cylinders.cylinderCount * 6);
ValueCell.update(cylinders.endBuffer, end);
}
//
export const Params = {
...BaseGeometry.Params,
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
sizeAspectRatio: PD.Numeric(1, { min: 0, max: 3, step: 0.01 }),
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
};
export type Params = typeof Params
export const Utils: GeometryUtils<Cylinders, Params> = {
Params,
createEmpty,
createValues,
createValuesSimple,
updateValues,
updateBoundingSphere,
createRenderableState,
updateRenderableState,
createPositionIterator
};
function createPositionIterator(cylinders: Cylinders, transform: TransformData): LocationIterator {
const groupCount = cylinders.cylinderCount * 6;
const instanceCount = transform.instanceCount.ref.value;
const location = PositionLocation();
const p = location.position;
const s = cylinders.startBuffer.ref.value;
const e = cylinders.endBuffer.ref.value;
const m = transform.aTransform.ref.value;
const getLocation = (groupIndex: number, instanceIndex: number) => {
const v = groupIndex % 6 === 0 ? s : e;
if (instanceIndex < 0) {
Vec3.fromArray(p, v, groupIndex * 3);
} else {
Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16);
}
return location;
};
return LocationIterator(groupCount, instanceCount, 2, getLocation);
}
function createValues(cylinders: Cylinders, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): CylindersValues {
const { instanceCount, groupCount } = locationIt;
const positionIt = createPositionIterator(cylinders, transform);
const color = createColors(locationIt, positionIt, theme.color);
const size = createSizes(locationIt, theme.size);
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const clipping = createEmptyClipping();
const counts = { drawCount: cylinders.cylinderCount * 4 * 3, vertexCount: cylinders.cylinderCount * 6, groupCount, instanceCount };
const padding = getMaxSize(size) * props.sizeFactor;
const invariantBoundingSphere = Sphere3D.clone(cylinders.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
aMapping: cylinders.mappingBuffer,
aGroup: cylinders.groupBuffer,
aStart: cylinders.startBuffer,
aEnd: cylinders.endBuffer,
aScale: cylinders.scaleBuffer,
aCap: cylinders.capBuffer,
elements: cylinders.indexBuffer,
boundingSphere: ValueCell.create(boundingSphere),
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
...color,
...size,
...marker,
...overpaint,
...transparency,
...clipping,
...transform,
padding: ValueCell.create(padding),
...BaseGeometry.createValues(props, counts),
uSizeFactor: ValueCell.create(props.sizeFactor * props.sizeAspectRatio),
dDoubleSided: ValueCell.create(props.doubleSided),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
};
}
function createValuesSimple(cylinders: Cylinders, props: Partial<PD.Values<Params>>, colorValue: Color, sizeValue: number, transform?: TransformData) {
const s = BaseGeometry.createSimple(colorValue, sizeValue, transform);
const p = { ...PD.getDefaultValues(Params), ...props };
return createValues(cylinders, s.transform, s.locationIterator, s.theme, p);
}
function updateValues(values: CylindersValues, props: PD.Values<Params>) {
BaseGeometry.updateValues(values, props);
ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor * props.sizeAspectRatio);
ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
}
function updateBoundingSphere(values: CylindersValues, cylinders: Cylinders) {
const invariantBoundingSphere = Sphere3D.clone(cylinders.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);
}
if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) {
ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere);
ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere));
}
}
function createRenderableState(props: PD.Values<Params>): RenderableState {
const state = BaseGeometry.createRenderableState(props);
updateRenderableState(state, props);
return state;
}
function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
BaseGeometry.updateRenderableState(state, props);
state.opaque = state.opaque && !props.xrayShaded;
state.writeDepth = state.opaque;
}
}

View File

@@ -22,28 +22,31 @@ import { Theme } from '../../mol-theme/theme';
import { RenderObjectValues } from '../../mol-gl/render-object';
import { TextureMesh } from './texture-mesh/texture-mesh';
import { Image } from './image/image';
import { Cylinders } from './cylinders/cylinders';
export type GeometryKind = 'mesh' | 'points' | 'spheres' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh'
export type GeometryKind = 'mesh' | 'points' | 'spheres' | 'cylinders' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh'
export type Geometry<T extends GeometryKind = GeometryKind> =
T extends 'mesh' ? Mesh :
T extends 'points' ? Points :
T extends 'spheres' ? Spheres :
T extends 'text' ? Text :
T extends 'lines' ? Lines :
T extends 'direct-volume' ? DirectVolume :
T extends 'image' ? Image :
T extends 'texture-mesh' ? TextureMesh : never
T extends 'cylinders' ? Cylinders :
T extends 'text' ? Text :
T extends 'lines' ? Lines :
T extends 'direct-volume' ? DirectVolume :
T extends 'image' ? Image :
T extends 'texture-mesh' ? TextureMesh : never
type GeometryParams<T extends GeometryKind> =
T extends 'mesh' ? Mesh.Params :
T extends 'points' ? Points.Params :
T extends 'spheres' ? Spheres.Params :
T extends 'text' ? Text.Params :
T extends 'lines' ? Lines.Params :
T extends 'direct-volume' ? DirectVolume.Params :
T extends 'image' ? Image.Params :
T extends 'texture-mesh' ? TextureMesh.Params : never
T extends 'cylinders' ? Cylinders.Params :
T extends 'text' ? Text.Params :
T extends 'lines' ? Lines.Params :
T extends 'direct-volume' ? DirectVolume.Params :
T extends 'image' ? Image.Params :
T extends 'texture-mesh' ? TextureMesh.Params : never
export interface GeometryUtils<G extends Geometry, P extends PD.Params = GeometryParams<G['kind']>, V = RenderObjectValues<G['kind']>> {
Params: P
@@ -65,6 +68,7 @@ export namespace Geometry {
case 'mesh': return geometry.triangleCount * 3;
case 'points': return geometry.pointCount;
case 'spheres': return geometry.sphereCount * 2 * 3;
case 'cylinders': return geometry.cylinderCount * 4 * 3;
case 'text': return geometry.charCount * 2 * 3;
case 'lines': return geometry.lineCount * 2 * 3;
case 'direct-volume': return 12 * 3;
@@ -78,13 +82,14 @@ export namespace Geometry {
case 'mesh': return geometry.vertexCount;
case 'points': return geometry.pointCount;
case 'spheres': return geometry.sphereCount * 4;
case 'cylinders': return geometry.cylinderCount * 6;
case 'text': return geometry.charCount * 4;
case 'lines': return geometry.lineCount * 4;
case 'direct-volume':
const [x, y, z] = geometry.gridDimension.ref.value;
return x * y * z;
case 'image': return 4;
case 'texture-mesh': return geometry.vertexCount / 3;
case 'texture-mesh': return geometry.vertexCount;
}
}
@@ -93,6 +98,7 @@ export namespace Geometry {
case 'mesh':
case 'points':
case 'spheres':
case 'cylinders':
case 'text':
case 'lines':
return getDrawCount(geometry) === 0 ? 0 : (arrayMax(geometry.groupBuffer.ref.value) + 1);
@@ -111,6 +117,7 @@ export namespace Geometry {
case 'mesh': return Mesh.Utils as any;
case 'points': return Points.Utils as any;
case 'spheres': return Spheres.Utils as any;
case 'cylinders': return Cylinders.Utils as any;
case 'text': return Text.Utils as any;
case 'lines': return Lines.Utils as any;
case 'direct-volume': return DirectVolume.Utils as any;

View File

@@ -11,6 +11,7 @@ import { Cage } from '../../../mol-geo/primitive/cage';
export interface LinesBuilder {
add(startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number): void
addVec(start: Vec3, end: Vec3, group: number): void
addFixedCountDashes(start: Vec3, end: Vec3, segmentCount: number, group: number): void
addFixedLengthDashes(start: Vec3, end: Vec3, segmentLength: number, group: number): void
addCage(t: Mat4, cage: Cage, group: number): void
@@ -39,6 +40,14 @@ export namespace LinesBuilder {
}
};
const addVec = (start: Vec3, end: Vec3, group: number) => {
for (let i = 0; i < 4; ++i) {
caAdd3(starts, start[0], start[1], start[2]);
caAdd3(ends, end[0], end[1], end[2]);
caAdd(groups, group);
}
};
const addFixedCountDashes = (start: Vec3, end: Vec3, segmentCount: number, group: number) => {
const d = Vec3.distance(start, end);
const s = Math.floor(segmentCount / 2);
@@ -57,6 +66,7 @@ export namespace LinesBuilder {
return {
add,
addVec,
addFixedCountDashes,
addFixedLengthDashes: (start: Vec3, end: Vec3, segmentLength: number, group: number) => {
const d = Vec3.distance(start, end);

View File

@@ -93,7 +93,7 @@ export namespace Lines {
function hashCode(lines: Lines) {
return hashFnv32a([
lines.lineCount, lines.mappingBuffer.ref.version, lines.indexBuffer.ref.version,
lines.groupBuffer.ref.version, lines.startBuffer.ref.version, lines.startBuffer.ref.version
lines.groupBuffer.ref.version, lines.startBuffer.ref.version, lines.endBuffer.ref.version
]);
}
@@ -164,7 +164,7 @@ export namespace Lines {
export const Params = {
...BaseGeometry.Params,
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
lineSizeAttenuation: PD.Boolean(false),
};
export type Params = typeof Params

View File

@@ -119,7 +119,7 @@ export namespace Points {
export const Params = {
...BaseGeometry.Params,
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
pointSizeAttenuation: PD.Boolean(false),
pointFilledCircle: PD.Boolean(false),
pointEdgeBleach: PD.Numeric(0.2, { min: 0, max: 1, step: 0.05 }),

View File

@@ -21,7 +21,6 @@ import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
import { Texture } from '../../../mol-gl/webgl/texture';
import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
import { fillSerial } from '../../../mol-util/array';
import { createEmptyClipping } from '../clipping-data';
import { NullLocation } from '../../../mol-model/location';
@@ -34,22 +33,22 @@ export interface TextureMesh {
groupCount: number,
readonly geoTextureDim: ValueCell<Vec2>,
/** texture has vertex positions in XYZ and group id in W */
readonly vertexGroupTexture: ValueCell<Texture>,
readonly vertexTexture: ValueCell<Texture>,
readonly groupTexture: ValueCell<Texture>,
readonly normalTexture: ValueCell<Texture>,
readonly boundingSphere: Sphere3D
}
export namespace TextureMesh {
export function create(vertexCount: number, groupCount: number, vertexGroupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
const width = vertexGroupTexture.getWidth();
const height = vertexGroupTexture.getHeight();
export function create(vertexCount: number, groupCount: number, vertexTexture: Texture, groupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
const width = vertexTexture.getWidth();
const height = vertexTexture.getHeight();
if (textureMesh) {
textureMesh.vertexCount = vertexCount;
textureMesh.groupCount = groupCount;
ValueCell.update(textureMesh.geoTextureDim, Vec2.set(textureMesh.geoTextureDim.ref.value, width, height));
ValueCell.update(textureMesh.vertexGroupTexture, vertexGroupTexture);
ValueCell.update(textureMesh.vertexTexture, vertexTexture);
ValueCell.update(textureMesh.normalTexture, normalTexture);
Sphere3D.copy(textureMesh.boundingSphere, boundingSphere);
return textureMesh;
@@ -59,7 +58,8 @@ export namespace TextureMesh {
vertexCount,
groupCount,
geoTextureDim: ValueCell.create(Vec2.create(width, height)),
vertexGroupTexture: ValueCell.create(vertexGroupTexture),
vertexTexture: ValueCell.create(vertexTexture),
groupTexture: ValueCell.create(groupTexture),
normalTexture: ValueCell.create(normalTexture),
boundingSphere: Sphere3D.clone(boundingSphere),
};
@@ -75,6 +75,8 @@ export namespace TextureMesh {
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
};
export type Params = typeof Params
@@ -100,20 +102,20 @@ export namespace TextureMesh {
const transparency = createEmptyTransparency();
const clipping = createEmptyClipping();
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount / 3, groupCount, instanceCount };
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount, groupCount, instanceCount };
const transformBoundingSphere = calculateTransformBoundingSphere(textureMesh.boundingSphere, transform.aTransform.ref.value, transform.instanceCount.ref.value);
const invariantBoundingSphere = Sphere3D.clone(textureMesh.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
return {
uGeoTexDim: textureMesh.geoTextureDim,
tPositionGroup: textureMesh.vertexGroupTexture,
tPosition: textureMesh.vertexTexture,
tGroup: textureMesh.groupTexture,
tNormal: textureMesh.normalTexture,
// aGroup is used as a vertex index here and the group id is retirieved from tPositionGroup
aGroup: ValueCell.create(fillSerial(new Float32Array(textureMesh.vertexCount))),
boundingSphere: ValueCell.create(transformBoundingSphere),
invariantBoundingSphere: ValueCell.create(Sphere3D.clone(textureMesh.boundingSphere)),
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(textureMesh.boundingSphere)),
boundingSphere: ValueCell.create(boundingSphere),
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
...color,
...marker,
@@ -126,6 +128,8 @@ export namespace TextureMesh {
dDoubleSided: ValueCell.create(props.doubleSided),
dFlatShaded: ValueCell.create(props.flatShaded),
dFlipSided: ValueCell.create(props.flipSided),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dGeoTexture: ValueCell.create(true),
};
}
@@ -137,21 +141,18 @@ export namespace TextureMesh {
}
function updateValues(values: TextureMeshValues, props: PD.Values<Params>) {
ValueCell.updateIfChanged(values.alpha, props.alpha); // `uAlpha` is set in renderable.render
BaseGeometry.updateValues(values, props);
ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
if (values.drawCount.ref.value > values.aGroup.ref.value.length) {
// console.log('updating vertex ids in aGroup to handle larger drawCount')
ValueCell.update(values.aGroup, fillSerial(new Float32Array(values.drawCount.ref.value)));
}
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
}
function updateBoundingSphere(values: TextureMeshValues, textureMesh: TextureMesh) {
const invariantBoundingSphere = textureMesh.boundingSphere;
const invariantBoundingSphere = Sphere3D.clone(textureMesh.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere);
}

View File

@@ -89,12 +89,14 @@ function createPoints() {
uPointEdgeBleach: ValueCell.create(0.5),
};
const state: RenderableState = {
disposed: false,
visible: true,
alphaFactor: 1,
pickable: true,
colorOnly: false,
opaque: true,
writeDepth: true
writeDepth: true,
noClip: false,
};
return createRenderObject('points', values, state, -1);

View File

@@ -0,0 +1,223 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { RenderableSchema, Values, UnboxedValues, UniformSpec, TextureSpec, DefineSpec, RenderableValues } from '../renderable/schema';
import { WebGLContext } from '../webgl/context';
import { getRegularGrid3dDelta, RegularGrid3d } from '../../mol-math/geometry/common';
import shader_template from '../shader/util/grid3d-template.frag';
import quad_vert from '../shader/quad.vert';
import { ShaderCode } from '../shader-code';
import { UUID, ValueCell } from '../../mol-util';
import { objectForEach } from '../../mol-util/object';
import { getUniformGlslType, isUniformValueScalar } from '../webgl/uniform';
import { QuadSchema, QuadValues } from './util';
import { createComputeRenderItem } from '../webgl/render-item';
import { createComputeRenderable } from '../renderable';
import { isLittleEndian } from '../../mol-util/is-little-endian';
import { RuntimeContext } from '../../mol-task';
export function canComputeGrid3dOnGPU(webgl?: WebGLContext) {
return !!webgl?.extensions.textureFloat;
}
export interface Grid3DComputeRenderableSpec<S extends RenderableSchema, P, CS> {
schema: S,
// indicate which params are loop bounds for WebGL1 compat
loopBounds?: (keyof S)[]
utilCode?: string,
mainCode: string,
returnCode: string,
values(params: P, grid: RegularGrid3d): UnboxedValues<S>,
cumulative?: {
states(params: P): CS[],
update(params: P, state: CS, values: Values<S>): void,
// call gl.readPixes every 'yieldPeriod' states to split the computation
// into multiple parts, if not set, the computation will be synchronous
yieldPeriod?: number
}
}
const FrameBufferName = 'grid3d-computable' as const;
const Texture0Name = 'grid3d-computable-0' as const;
const Texture1Name = 'grid3d-computable-1' as const;
const SchemaBase = {
...QuadSchema,
uDimensions: UniformSpec('v3'),
uMin: UniformSpec('v3'),
uDelta: UniformSpec('v3'),
uWidth: UniformSpec('f'),
uLittleEndian: UniformSpec('b'),
};
const CumulativeSumSchema = {
tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
};
export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) {
const id = UUID.create22();
const uniforms: string[] = [];
objectForEach(spec.schema, (u, k) => {
if (u.type === 'define') return;
if (u.kind.indexOf('[]') >= 0) throw new Error('array uniforms are not supported');
const isBound = (spec.loopBounds?.indexOf(k) ?? -1) >= 0;
if (isBound) uniforms.push(`#ifndef ${k}`);
if (u.type === 'uniform') uniforms.push(`uniform ${getUniformGlslType(u.kind as any)} ${k};`);
else if (u.type === 'texture') uniforms.push(`uniform sampler2D ${k};`);
if (isBound) uniforms.push(`#endif`);
});
const code = shader_template
.replace('{UNIFORMS}', uniforms.join('\n'))
.replace('{UTILS}', spec.utilCode ?? '')
.replace('{MAIN}', spec.mainCode)
.replace('{RETURN}', spec.returnCode);
const shader = ShaderCode(id, quad_vert, code);
return async (ctx: RuntimeContext, webgl: WebGLContext, grid: RegularGrid3d, params: P) => {
const schema: RenderableSchema = {
...SchemaBase,
...(spec.cumulative ? CumulativeSumSchema : {}),
...spec.schema,
};
if (!webgl.isWebGL2) {
if (spec.loopBounds) {
for (const b of spec.loopBounds) {
(schema as any)[b] = DefineSpec('number');
}
}
(schema as any)['WEBGL1'] = DefineSpec('boolean');
}
if (spec.cumulative) {
(schema as any)['CUMULATIVE'] = DefineSpec('boolean');
}
if (!webgl.namedFramebuffers[FrameBufferName]) {
webgl.namedFramebuffers[FrameBufferName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[FrameBufferName];
if (!webgl.namedTextures[Texture0Name]) {
webgl.namedTextures[Texture0Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
if (spec.cumulative && !webgl.namedTextures[Texture1Name]) {
webgl.namedTextures[Texture1Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
const tex = [webgl.namedTextures[Texture0Name], webgl.namedTextures[Texture1Name]];
const [nx, ny, nz] = grid.dimensions;
const uWidth = Math.ceil(Math.sqrt(nx * ny * nz));
const values: UnboxedValues<S & typeof SchemaBase> = {
uDimensions: grid.dimensions,
uMin: grid.box.min,
uDelta: getRegularGrid3dDelta(grid),
uWidth,
uLittleEndian: isLittleEndian(),
...spec.values(params, grid)
} as any;
if (!webgl.isWebGL2) {
(values as any).WEBGL1 = true;
}
if (spec.cumulative) {
(values as any).tCumulativeSum = tex[0];
(values as any).CUMULATIVE = true;
}
let renderable = webgl.namedComputeRenderables[id];
let cells: RenderableValues;
if (renderable) {
cells = renderable.values as RenderableValues;
objectForEach(values, (c, k) => {
const s = schema[k];
if (s?.type === 'value' || s?.type === 'attribute') return;
if (!s || !isUniformValueScalar(s.kind as any)) {
ValueCell.update(cells[k], c);
} else {
ValueCell.updateIfChanged(cells[k], c);
}
});
} else {
cells = {} as any;
objectForEach(QuadValues, (v, k) => (cells as any)[k] = v);
objectForEach(values, (v, k) => (cells as any)[k] = ValueCell.create(v));
renderable = createComputeRenderable(createComputeRenderItem(webgl, 'triangles', shader, schema, cells), cells);
}
const array = new Uint8Array(uWidth * uWidth * 4);
if (spec.cumulative) {
const { gl } = webgl;
const states = spec.cumulative.states(params);
tex[0].define(uWidth, uWidth);
tex[1].define(uWidth, uWidth);
resetGl(webgl, uWidth);
gl.clearColor(0, 0, 0, 0);
tex[0].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
tex[1].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
if (spec.cumulative.yieldPeriod) {
await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length });
}
const yieldPeriod = Math.max(1, spec.cumulative.yieldPeriod ?? 1 | 0);
for (let i = 0; i < states.length; i++) {
ValueCell.update(cells.tCumulativeSum, tex[(i + 1) % 2]);
tex[i % 2].attachFramebuffer(framebuffer, 'color0');
resetGl(webgl, uWidth);
spec.cumulative.update(params, states[i], cells as any);
renderable.update();
renderable.render();
if (spec.cumulative.yieldPeriod && i !== states.length - 1) {
if (i % yieldPeriod === yieldPeriod - 1) {
webgl.readPixels(0, 0, 1, 1, array);
}
if (ctx.shouldUpdate) {
await ctx.update({ current: i + 1 });
}
}
}
} else {
tex[0].define(uWidth, uWidth);
tex[0].attachFramebuffer(framebuffer, 'color0');
framebuffer.bind();
resetGl(webgl, uWidth);
renderable.update();
renderable.render();
}
webgl.readPixels(0, 0, uWidth, uWidth, array);
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
};
}
function resetGl(webgl: WebGLContext, w: number) {
const { gl, state } = webgl;
gl.viewport(0, 0, w, w);
gl.scissor(0, 0, w, w);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -8,58 +8,74 @@ import { createComputeRenderable, ComputeRenderable } from '../../renderable';
import { WebGLContext } from '../../webgl/context';
import { createComputeRenderItem } from '../../webgl/render-item';
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
import { Texture } from '../../../mol-gl/webgl/texture';
import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
import { ShaderCode } from '../../../mol-gl/shader-code';
import { ValueCell } from '../../../mol-util';
import { QuadSchema, QuadValues } from '../util';
import { Vec2 } from '../../../mol-math/linear-algebra';
import { Vec2, Vec3 } from '../../../mol-math/linear-algebra';
import { getHistopyramidSum } from './sum';
import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
import { isPowerOfTwo } from '../../../mol-math/misc';
import quad_vert from '../../../mol-gl/shader/quad.vert';
import reduction_frag from '../../../mol-gl/shader/histogram-pyramid/reduction.frag';
import { isWebGL2 } from '../../webgl/compat';
const HistopyramidReductionSchema = {
...QuadSchema,
tInputLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tPreviousLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
uSize: UniformSpec('f'),
uTexSize: UniformSpec('f'),
uFirst: UniformSpec('b'),
};
type HistopyramidReductionValues = Values<typeof HistopyramidReductionSchema>
let HistopyramidReductionRenderable: ComputeRenderable<Values<typeof HistopyramidReductionSchema>>;
function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
if (HistopyramidReductionRenderable) {
ValueCell.update(HistopyramidReductionRenderable.values.tPreviousLevel, initialTexture);
HistopyramidReductionRenderable.update();
return HistopyramidReductionRenderable;
const HistogramPyramidName = 'histogram-pyramid';
function getHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Texture, previousLevel: Texture): ComputeRenderable<HistopyramidReductionValues> {
if (ctx.namedComputeRenderables[HistogramPyramidName]) {
const v = ctx.namedComputeRenderables[HistogramPyramidName].values as HistopyramidReductionValues;
ValueCell.update(v.tInputLevel, inputLevel);
ValueCell.update(v.tPreviousLevel, previousLevel);
ctx.namedComputeRenderables[HistogramPyramidName].update();
} else {
const values: Values<typeof HistopyramidReductionSchema> = {
...QuadValues,
tPreviousLevel: ValueCell.create(initialTexture),
uSize: ValueCell.create(0),
uTexSize: ValueCell.create(0),
};
const schema = { ...HistopyramidReductionSchema };
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);
return HistopyramidReductionRenderable;
ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, inputLevel, previousLevel);
}
return ctx.namedComputeRenderables[HistogramPyramidName];
}
function createHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Texture, previousLevel: Texture) {
const values: HistopyramidReductionValues = {
...QuadValues,
tInputLevel: ValueCell.create(inputLevel),
tPreviousLevel: ValueCell.create(previousLevel),
uSize: ValueCell.create(0),
uTexSize: ValueCell.create(0),
uFirst: ValueCell.create(true),
};
const schema = { ...HistopyramidReductionSchema };
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag, {}, { 0: 'ivec4' });
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
const LevelTexturesFramebuffers: TextureFramebuffer[] = [];
function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
let textureFramebuffer = LevelTexturesFramebuffers[level];
let textureFramebuffer = LevelTexturesFramebuffers[level];
const size = Math.pow(2, level);
if (textureFramebuffer === undefined) {
const texture = ctx.resources.texture('image-float32', 'rgba', 'float', 'nearest');
const framebuffer = ctx.resources.framebuffer();
const texture = ctx.isWebGL2
? getTexture(`level${level}`, ctx, 'image-int32', 'alpha', 'int', 'nearest')
: getTexture(`level${level}`, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
texture.define(size, size);
const framebuffer = getFramebuffer(`level${level}`, ctx);
texture.attachFramebuffer(framebuffer, 0);
textureFramebuffer = { texture, framebuffer };
textureFramebuffer.texture.define(size, size);
LevelTexturesFramebuffers[level] = textureFramebuffer;
}
return textureFramebuffer;
@@ -70,12 +86,28 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.disable(gl.CULL_FACE);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.depthMask(false);
state.colorMask(true, true, true, true);
state.clearColor(0, 0, 0, 0);
}
function getFramebuffer(name: string, webgl: WebGLContext): Framebuffer {
const _name = `${HistogramPyramidName}-${name}`;
if (!webgl.namedFramebuffers[_name]) {
webgl.namedFramebuffers[_name] = webgl.resources.framebuffer();
}
return webgl.namedFramebuffers[_name];
}
function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter): Texture {
const _name = `${HistogramPyramidName}-${name}`;
if (!webgl.namedTextures[_name]) {
webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter);
}
return webgl.namedTextures[_name];
}
export interface HistogramPyramid {
pyramidTex: Texture
count: number
@@ -84,30 +116,42 @@ export interface HistogramPyramid {
scale: Vec2
}
export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2): HistogramPyramid {
const { gl, resources } = ctx;
export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2, gridTexDim: Vec3): HistogramPyramid {
const { gl } = ctx;
const w = inputTexture.getWidth();
const h = inputTexture.getHeight();
// printTexture(ctx, inputTexture, 2)
if (inputTexture.getWidth() !== inputTexture.getHeight() || !isPowerOfTwo(inputTexture.getWidth())) {
if (w !== h || !isPowerOfTwo(w)) {
throw new Error('inputTexture must be of square power-of-two size');
}
// This part set the levels
const levels = Math.ceil(Math.log(inputTexture.getWidth()) / Math.log(2));
const levels = Math.ceil(Math.log(w) / Math.log(2));
const maxSize = Math.pow(2, levels);
// console.log('levels', levels, 'maxSize', maxSize)
const maxSizeX = Math.pow(2, levels);
const maxSizeY = Math.pow(2, levels - 1);
// console.log('levels', levels, 'maxSize', maxSize, [maxSizeX, maxSizeY], 'input', w);
const pyramidTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
pyramidTexture.define(maxSize, maxSize);
const pyramidTex = ctx.isWebGL2
? getTexture('pyramid', ctx, 'image-int32', 'alpha', 'int', 'nearest')
: getTexture('pyramid', ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
pyramidTex.define(maxSizeX, maxSizeY);
const framebuffer = resources.framebuffer();
pyramidTexture.attachFramebuffer(framebuffer, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
const framebuffer = getFramebuffer('pyramid', ctx);
pyramidTex.attachFramebuffer(framebuffer, 0);
gl.viewport(0, 0, maxSizeX, maxSizeY);
if (isWebGL2(gl)) {
gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
} else {
gl.clear(gl.COLOR_BUFFER_BIT);
}
const levelTexturesFramebuffers: TextureFramebuffer[] = [];
for (let i = 0; i < levels; ++i) levelTexturesFramebuffers.push(getLevelTextureFramebuffer(ctx, i));
const renderable = getHistopyramidReductionRenderable(ctx, inputTexture);
const renderable = getHistopyramidReductionRenderable(ctx, inputTexture, levelTexturesFramebuffers[0].texture);
ctx.state.currentRenderItemId = -1;
setRenderingDefaults(ctx);
@@ -116,46 +160,46 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
const currLevel = levels - 1 - i;
const tf = levelTexturesFramebuffers[currLevel];
tf.framebuffer.bind();
// levelTextures[currLevel].attachFramebuffer(framebuffer, 0)
const size = Math.pow(2, currLevel);
// console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i)
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, size, size);
// console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i);
ValueCell.update(renderable.values.uSize, Math.pow(2, i + 1) / maxSize);
ValueCell.update(renderable.values.uTexSize, size);
ValueCell.updateIfChanged(renderable.values.uFirst, i === 0);
if (i > 0) {
ValueCell.update(renderable.values.tPreviousLevel, levelTexturesFramebuffers[levels - i].texture);
renderable.update();
}
ctx.state.currentRenderItemId = -1;
gl.viewport(0, 0, size, size);
gl.scissor(0, 0, size, size);
if (isWebGL2(gl)) {
gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
} else {
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
renderable.render();
pyramidTexture.bind(0);
pyramidTex.bind(0);
gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, offset, 0, 0, 0, size, size);
pyramidTexture.unbind(0);
pyramidTex.unbind(0);
offset += size;
}
gl.finish();
// printTexture(ctx, pyramidTexture, 2)
// printTexture(ctx, pyramidTex, 2)
//
const finalCount = getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture);
const height = Math.ceil(finalCount / Math.pow(2, levels));
// const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height)
// console.log('height', height, 'finalCount', finalCount, 'scale', scale)
// return at least a count of one to avoid issues downstram
const count = Math.max(1, getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture));
const height = Math.ceil(count / Math.pow(2, levels));
// const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height);
// console.log('height', height, 'finalCount', count, 'scale', scale);
return {
pyramidTex: pyramidTexture,
count: finalCount,
height,
levels,
scale
};
return { pyramidTex, count, height, levels, scale };
}

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createComputeRenderable, ComputeRenderable } from '../../renderable';
import { ComputeRenderable, createComputeRenderable } from '../../renderable';
import { WebGLContext } from '../../webgl/context';
import { createComputeRenderItem } from '../../webgl/render-item';
import { Values, TextureSpec } from '../../renderable/schema';
@@ -15,39 +15,40 @@ import { decodeFloatRGB } from '../../../mol-util/float-packing';
import { QuadSchema, QuadValues } from '../util';
import quad_vert from '../../../mol-gl/shader/quad.vert';
import sum_frag from '../../../mol-gl/shader/histogram-pyramid/sum.frag';
import { isWebGL2 } from '../../webgl/compat';
const HistopyramidSumSchema = {
...QuadSchema,
tTexture: TextureSpec('texture', 'rgba', 'float', 'nearest'),
};
type HistopyramidSumValues = Values<typeof HistopyramidSumSchema>
let HistopyramidSumRenderable: ComputeRenderable<Values<typeof HistopyramidSumSchema>>;
function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
if (HistopyramidSumRenderable) {
ValueCell.update(HistopyramidSumRenderable.values.tTexture, texture);
HistopyramidSumRenderable.update();
return HistopyramidSumRenderable;
const HistopyramidSumName = 'histopyramid-sum';
function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture): ComputeRenderable<HistopyramidSumValues> {
if (ctx.namedComputeRenderables[HistopyramidSumName]) {
const v = ctx.namedComputeRenderables[HistopyramidSumName].values as HistopyramidSumValues;
ValueCell.update(v.tTexture, texture);
ctx.namedComputeRenderables[HistopyramidSumName].update();
} else {
const values: Values<typeof HistopyramidSumSchema> = {
...QuadValues,
tTexture: ValueCell.create(texture),
};
const schema = { ...HistopyramidSumSchema };
const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
HistopyramidSumRenderable = createComputeRenderable(renderItem, values);
return HistopyramidSumRenderable;
ctx.namedComputeRenderables[HistopyramidSumName] = createHistopyramidSumRenderable(ctx, texture);
}
return ctx.namedComputeRenderables[HistopyramidSumName];
}
let SumTexture: Texture;
function getSumTexture(ctx: WebGLContext) {
if (SumTexture) return SumTexture;
SumTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
SumTexture.define(1, 1);
return SumTexture;
function createHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
const values: HistopyramidSumValues = {
...QuadValues,
tTexture: ValueCell.create(texture),
};
const schema = { ...HistopyramidSumSchema };
const shaderCode = ShaderCode('sum', quad_vert, sum_frag, {}, { 0: 'ivec4' });
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function setRenderingDefaults(ctx: WebGLContext) {
@@ -61,15 +62,27 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.clearColor(0, 0, 0, 0);
}
const sumArray = new Uint8Array(4);
const sumBytes = new Uint8Array(4);
const sumInts = new Int32Array(4);
export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture) {
const { gl, resources } = ctx;
const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture);
ctx.state.currentRenderItemId = -1;
const framebuffer = resources.framebuffer();
const sumTexture = getSumTexture(ctx);
if (!ctx.namedFramebuffers[HistopyramidSumName]) {
ctx.namedFramebuffers[HistopyramidSumName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[HistopyramidSumName];
if (!ctx.namedTextures[HistopyramidSumName]) {
ctx.namedTextures[HistopyramidSumName] = isWebGL2(gl)
? resources.texture('image-int32', 'rgba', 'int', 'nearest')
: resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
ctx.namedTextures[HistopyramidSumName].define(1, 1);
}
const sumTexture = ctx.namedTextures[HistopyramidSumName];
sumTexture.attachFramebuffer(framebuffer, 0);
setRenderingDefaults(ctx);
@@ -77,8 +90,11 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
gl.viewport(0, 0, 1, 1);
renderable.render();
gl.finish();
ctx.readPixels(0, 0, 1, 1, sumArray);
ctx.readPixels(0, 0, 1, 1, isWebGL2(gl) ? sumInts : sumBytes);
ctx.unbindFramebuffer();
return decodeFloatRGB(sumArray[0], sumArray[1], sumArray[2]);
return isWebGL2(gl)
? sumInts[0]
: decodeFloatRGB(sumBytes[0], sumBytes[1], sumBytes[2]);
}

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createComputeRenderable } from '../../renderable';
import { ComputeRenderable, createComputeRenderable } from '../../renderable';
import { WebGLContext } from '../../webgl/context';
import { createComputeRenderItem } from '../../webgl/render-item';
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
@@ -29,19 +29,38 @@ const ActiveVoxelsSchema = {
uScale: UniformSpec('v2'),
};
type ActiveVoxelsValues = Values<typeof ActiveVoxelsSchema>
function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
const values: Values<typeof ActiveVoxelsSchema> = {
const ActiveVoxelsName = 'active-voxels';
function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2): ComputeRenderable<ActiveVoxelsValues> {
if (ctx.namedComputeRenderables[ActiveVoxelsName]) {
const v = ctx.namedComputeRenderables[ActiveVoxelsName].values as ActiveVoxelsValues;
ValueCell.update(v.uQuadScale, scale);
ValueCell.update(v.tVolumeData, volumeData);
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
ValueCell.update(v.uGridDim, gridDim);
ValueCell.update(v.uGridTexDim, gridTexDim);
ValueCell.update(v.uScale, scale);
ctx.namedComputeRenderables[ActiveVoxelsName].update();
} else {
ctx.namedComputeRenderables[ActiveVoxelsName] = createActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, scale);
}
return ctx.namedComputeRenderables[ActiveVoxelsName];
}
function createActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
const values: ActiveVoxelsValues = {
...QuadValues,
uQuadScale: ValueCell.create(scale),
tTriCount: ValueCell.create(getTriCount()),
uQuadScale: ValueCell.create(scale),
tVolumeData: ValueCell.create(volumeData),
uIsoValue: ValueCell.create(isoValue),
uGridDim: ValueCell.create(gridDim),
uGridTexDim: ValueCell.create(gridTexDim),
uScale: ValueCell.create(scale),
};
@@ -57,7 +76,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.disable(gl.CULL_FACE);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.depthMask(false);
state.colorMask(true, true, true, true);
state.clearColor(0, 0, 0, 0);
@@ -68,10 +87,16 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
const width = volumeData.getWidth();
const height = volumeData.getHeight();
const framebuffer = resources.framebuffer();
if (!ctx.namedFramebuffers[ActiveVoxelsName]) {
ctx.namedFramebuffers[ActiveVoxelsName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[ActiveVoxelsName];
framebuffer.bind();
const activeVoxelsTex = resources.texture('image-float32', 'rgba', 'float', 'nearest');
if (!ctx.namedTextures[ActiveVoxelsName]) {
ctx.namedTextures[ActiveVoxelsName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
const activeVoxelsTex = ctx.namedTextures[ActiveVoxelsName];
activeVoxelsTex.define(width, height);
const renderable = getActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, gridScale);
@@ -80,11 +105,14 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
activeVoxelsTex.attachFramebuffer(framebuffer, 0);
setRenderingDefaults(ctx);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
renderable.render();
// console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim)
// console.log('volumeData', volumeData)
// console.log('at', readTexture(ctx, activeVoxelsTex))
// console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim);
// console.log('volumeData', volumeData);
// console.log('at', readTexture(ctx, activeVoxelsTex));
gl.finish();

View File

@@ -1,22 +1,24 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createComputeRenderable } from '../../renderable';
import { ComputeRenderable, createComputeRenderable } from '../../renderable';
import { WebGLContext } from '../../webgl/context';
import { createComputeRenderItem } from '../../webgl/render-item';
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
import { Values, TextureSpec, UniformSpec, DefineSpec } from '../../renderable/schema';
import { Texture } from '../../../mol-gl/webgl/texture';
import { ShaderCode } from '../../../mol-gl/shader-code';
import { ValueCell } from '../../../mol-util';
import { Vec3, Vec2, Mat4 } from '../../../mol-math/linear-algebra';
import { QuadSchema, QuadValues } from '../util';
import { HistogramPyramid } from '../histogram-pyramid/reduction';
import { createHistogramPyramid, HistogramPyramid } from '../histogram-pyramid/reduction';
import { getTriIndices } from './tables';
import quad_vert from '../../../mol-gl/shader/quad.vert';
import isosurface_frag from '../../../mol-gl/shader/marching-cubes/isosurface.frag';
import { calcActiveVoxels } from './active-voxels';
import { isWebGL2 } from '../../webgl/compat';
const IsosurfaceSchema = {
...QuadSchema,
@@ -34,22 +36,52 @@ const IsosurfaceSchema = {
uGridDim: UniformSpec('v3'),
uGridTexDim: UniformSpec('v3'),
uGridTransform: UniformSpec('m4'),
uScale: UniformSpec('v2'),
dPackedGroup: DefineSpec('boolean')
};
type IsosurfaceValues = Values<typeof IsosurfaceSchema>
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, height: number) {
const IsosurfaceName = 'isosurface';
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
if (ctx.namedComputeRenderables[IsosurfaceName]) {
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
ValueCell.update(v.tVolumeData, volumeData);
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
ValueCell.updateIfChanged(v.uLevels, levels);
ValueCell.updateIfChanged(v.uCount, count);
ValueCell.update(v.uGridDim, gridDim);
ValueCell.update(v.uGridTexDim, gridTexDim);
ValueCell.update(v.uGridTransform, transform);
ValueCell.update(v.uScale, scale);
ValueCell.update(v.dPackedGroup, packedGroup);
ctx.namedComputeRenderables[IsosurfaceName].update();
} else {
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
}
return ctx.namedComputeRenderables[IsosurfaceName];
}
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean) {
// console.log('uSize', Math.pow(2, levels))
const values: Values<typeof IsosurfaceSchema> = {
const values: IsosurfaceValues = {
...QuadValues,
uQuadScale: ValueCell.create(Vec2.create(1, height / Math.pow(2, levels))),
tTriIndices: ValueCell.create(getTriIndices()),
tActiveVoxelsPyramid: ValueCell.create(activeVoxelsPyramid),
tActiveVoxelsBase: ValueCell.create(activeVoxelsBase),
tVolumeData: ValueCell.create(volumeData),
uIsoValue: ValueCell.create(isoValue),
uIsoValue: ValueCell.create(isoValue),
uSize: ValueCell.create(Math.pow(2, levels)),
uLevels: ValueCell.create(levels),
uCount: ValueCell.create(count),
@@ -57,8 +89,9 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
uGridDim: ValueCell.create(gridDim),
uGridTexDim: ValueCell.create(gridTexDim),
uGridTransform: ValueCell.create(transform),
uScale: ValueCell.create(scale),
dPackedGroup: ValueCell.create(packedGroup)
};
const schema = { ...IsosurfaceSchema };
@@ -79,119 +112,101 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.clearColor(0, 0, 0, 0);
}
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, vertexGroupTexture?: Texture, normalTexture?: Texture) {
const { gl, resources } = ctx;
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
const { gl, resources, extensions } = ctx;
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
const width = pyramidTex.getWidth();
// console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
// console.log('iso volumeData', volumeData)
// console.log('width', width, 'height', height);
// console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim);
// console.log('iso volumeData', volumeData);
const framebuffer = resources.framebuffer();
if (!ctx.namedFramebuffers[IsosurfaceName]) {
ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
let needsClear = false;
if (isWebGL2(gl)) {
if (!vertexTexture) {
vertexTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!vertexGroupTexture) {
vertexGroupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
} else if (vertexGroupTexture.getWidth() !== pyramidTex.getWidth() || vertexGroupTexture.getHeight() !== pyramidTex.getHeight()) {
vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
if (!groupTexture) {
groupTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
if (!normalTexture) {
normalTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
} else {
needsClear = true;
// in webgl1 drawbuffers must be in the same format for some reason
// this is quite wasteful but good enough for medium size meshes
if (!vertexTexture) {
vertexTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!groupTexture) {
groupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!normalTexture) {
normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
}
if (!normalTexture) {
normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
} else if (normalTexture.getWidth() !== pyramidTex.getWidth() || normalTexture.getHeight() !== pyramidTex.getHeight()) {
normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
} else {
needsClear = true;
}
vertexTexture.define(width, height);
groupTexture.define(width, height);
normalTexture.define(width, height);
// const infoTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
// infoTex.define(pyramidTex.width, pyramidTex.height)
vertexTexture.attachFramebuffer(framebuffer, 0);
groupTexture.attachFramebuffer(framebuffer, 1);
normalTexture.attachFramebuffer(framebuffer, 2);
// const pointTexA = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
// pointTexA.define(pyramidTex.width, pyramidTex.height)
// const pointTexB = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
// pointTexB.define(pyramidTex.width, pyramidTex.height)
// const coordTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
// coordTex.define(pyramidTex.width, pyramidTex.height)
// const indexTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
// indexTex.define(pyramidTex.width, pyramidTex.height)
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
ctx.state.currentRenderItemId = -1;
vertexGroupTexture.attachFramebuffer(framebuffer, 0);
normalTexture.attachFramebuffer(framebuffer, 1);
// infoTex.attachFramebuffer(framebuffer, 1)
// pointTexA.attachFramebuffer(framebuffer, 2)
// pointTexB.attachFramebuffer(framebuffer, 3)
// coordTex.attachFramebuffer(framebuffer, 4)
// indexTex.attachFramebuffer(framebuffer, 5)
const { drawBuffers } = ctx.extensions;
if (!drawBuffers) throw new Error('need WebGL draw buffers');
framebuffer.bind();
drawBuffers.drawBuffers([
drawBuffers.COLOR_ATTACHMENT0,
drawBuffers.COLOR_ATTACHMENT1,
// drawBuffers.COLOR_ATTACHMENT2,
// drawBuffers.COLOR_ATTACHMENT3,
// drawBuffers.COLOR_ATTACHMENT4,
// drawBuffers.COLOR_ATTACHMENT5
drawBuffers.COLOR_ATTACHMENT2,
]);
setRenderingDefaults(ctx);
gl.viewport(0, 0, pyramidTex.getWidth(), pyramidTex.getHeight());
if (needsClear) gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
renderable.render();
gl.finish();
gl.flush();
// const vgt = readTexture(ctx, vertexGroupTexture, pyramidTex.width, height)
// console.log('vertexGroupTexture', vgt.array.subarray(0, 4 * count))
return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
}
// const vt = readTexture(ctx, verticesTex, pyramidTex.width, height)
// console.log('vt', vt)
// const vertices = new Float32Array(3 * compacted.count)
// for (let i = 0; i < compacted.count; ++i) {
// vertices[i * 3] = vt.array[i * 4]
// vertices[i * 3 + 1] = vt.array[i * 4 + 1]
// vertices[i * 3 + 2] = vt.array[i * 4 + 2]
// }
// console.log('vertices', vertices)
//
// const it = readTexture(ctx, infoTex, pyramidTex.width, height)
// console.log('info', it.array.subarray(0, 4 * compacted.count))
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
// console.time('calcActiveVoxels');
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
// ctx.waitForGpuCommandsCompleteSync();
// console.timeEnd('calcActiveVoxels');
// const pat = readTexture(ctx, pointTexA, pyramidTex.width, height)
// console.log('point a', pat.array.subarray(0, 4 * compacted.count))
// console.time('createHistogramPyramid');
const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
// ctx.waitForGpuCommandsCompleteSync();
// console.timeEnd('createHistogramPyramid');
// const pbt = readTexture(ctx, pointTexB, pyramidTex.width, height)
// console.log('point b', pbt.array.subarray(0, 4 * compacted.count))
// console.time('createIsosurfaceBuffers');
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, packedGroup, vertexTexture, groupTexture, normalTexture);
// ctx.waitForGpuCommandsCompleteSync();
// console.timeEnd('createIsosurfaceBuffers');
// const ct = readTexture(ctx, coordTex, pyramidTex.width, height)
// console.log('coord', ct.array.subarray(0, 4 * compacted.count))
// const idxt = readTexture(ctx, indexTex, pyramidTex.width, height)
// console.log('index', idxt.array.subarray(0, 4 * compacted.count))
// const { field, idField } = await fieldFromTexture2d(ctx, volumeData, gridDimensions)
// console.log({ field, idField })
// const valuesA = new Float32Array(compacted.count)
// const valuesB = new Float32Array(compacted.count)
// for (let i = 0; i < compacted.count; ++i) {
// valuesA[i] = field.space.get(field.data, pat.array[i * 4], pat.array[i * 4 + 1], pat.array[i * 4 + 2])
// valuesB[i] = field.space.get(field.data, pbt.array[i * 4], pbt.array[i * 4 + 1], pbt.array[i * 4 + 2])
// }
// console.log('valuesA', valuesA)
// console.log('valuesB', valuesB)
return { vertexGroupTexture, normalTexture, vertexCount: count };
return gv;
}

View File

@@ -15,6 +15,7 @@ import { SpheresValues, SpheresRenderable } from './renderable/spheres';
import { TextValues, TextRenderable } from './renderable/text';
import { TextureMeshValues, TextureMeshRenderable } from './renderable/texture-mesh';
import { ImageValues, ImageRenderable } from './renderable/image';
import { CylindersRenderable, CylindersValues } from './renderable/cylinders';
const getNextId = idFactory(0, 0x7FFFFFFF);
@@ -28,17 +29,18 @@ export interface GraphicsRenderObject<T extends RenderObjectType = RenderObjectT
readonly materialId: number
}
export type RenderObjectType = 'mesh' | 'points' | 'spheres' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh'
export type RenderObjectType = 'mesh' | 'points' | 'spheres' | 'cylinders' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh'
export type RenderObjectValues<T extends RenderObjectType> =
T extends 'mesh' ? MeshValues :
T extends 'points' ? PointsValues :
T extends 'spheres' ? SpheresValues :
T extends 'text' ? TextValues :
T extends 'lines' ? LinesValues :
T extends 'direct-volume' ? DirectVolumeValues :
T extends 'image' ? ImageValues :
T extends 'texture-mesh' ? TextureMeshValues : never
T extends 'cylinders' ? CylindersValues :
T extends 'text' ? TextValues :
T extends 'lines' ? LinesValues :
T extends 'direct-volume' ? DirectVolumeValues :
T extends 'image' ? ImageValues :
T extends 'texture-mesh' ? TextureMeshValues : never
//
@@ -51,6 +53,7 @@ export function createRenderable<T extends RenderObjectType>(ctx: WebGLContext,
case 'mesh': return MeshRenderable(ctx, o.id, o.values as MeshValues, o.state, o.materialId);
case 'points': return PointsRenderable(ctx, o.id, o.values as PointsValues, o.state, o.materialId);
case 'spheres': return SpheresRenderable(ctx, o.id, o.values as SpheresValues, o.state, o.materialId);
case 'cylinders': return CylindersRenderable(ctx, o.id, o.values as CylindersValues, o.state, o.materialId);
case 'text': return TextRenderable(ctx, o.id, o.values as TextValues, o.state, o.materialId);
case 'lines': return LinesRenderable(ctx, o.id, o.values as LinesValues, o.state, o.materialId);
case 'direct-volume': return DirectVolumeRenderable(ctx, o.id, o.values as DirectVolumeValues, o.state, o.materialId);

View File

@@ -15,12 +15,14 @@ import { Textures } from './webgl/texture';
const getNextRenderableId = idFactory();
export type RenderableState = {
disposed: boolean
visible: boolean
alphaFactor: number
pickable: boolean
colorOnly: boolean
opaque: boolean
writeDepth: boolean
noClip: boolean
}
export interface Renderable<T extends RenderableValues> {

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Renderable, RenderableState, createRenderable } from '../renderable';
import { WebGLContext } from '../webgl/context';
import { createGraphicsRenderItem } from '../webgl/render-item';
import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema } from './schema';
import { CylindersShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
export const CylindersSchema = {
...BaseSchema,
...SizeSchema,
aGroup: AttributeSpec('float32', 1, 0),
aStart: AttributeSpec('float32', 3, 0),
aEnd: AttributeSpec('float32', 3, 0),
aMapping: AttributeSpec('float32', 3, 0),
aScale: AttributeSpec('float32', 1, 0),
aCap: AttributeSpec('float32', 1, 0),
elements: ElementsSpec('uint32'),
padding: ValueSpec('number'),
dDoubleSided: DefineSpec('boolean'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
};
export type CylindersSchema = typeof CylindersSchema
export type CylindersValues = Values<CylindersSchema>
export function CylindersRenderable(ctx: WebGLContext, id: number, values: CylindersValues, state: RenderableState, materialId: number): Renderable<CylindersValues> {
const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...CylindersSchema };
const internalValues: InternalValues = {
uObjectId: ValueCell.create(id),
};
const shaderCode = CylindersShaderCode;
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
return createRenderable(renderItem, values, state);
}

View File

@@ -7,59 +7,20 @@
import { Renderable, RenderableState, createRenderable } from '../renderable';
import { WebGLContext } from '../webgl/context';
import { createGraphicsRenderItem } from '../webgl/render-item';
import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema, TextureSpec, ValueSpec, ElementsSpec, DefineSpec, InternalValues, GlobalTextureSchema } from './schema';
import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema, TextureSpec, ElementsSpec, DefineSpec, InternalValues, GlobalTextureSchema, BaseSchema } from './schema';
import { DirectVolumeShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
export const DirectVolumeSchema = {
uColor: UniformSpec('v3'),
uColorTexDim: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
uMarkerTexDim: UniformSpec('v2'),
tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
uOverpaintTexDim: UniformSpec('v2'),
tOverpaint: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
dOverpaint: DefineSpec('boolean'),
uTransparencyTexDim: UniformSpec('v2'),
tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
dTransparency: DefineSpec('boolean'),
transparencyAverage: ValueSpec('number'),
dClipObjectCount: DefineSpec('number'),
dClipVariant: DefineSpec('string', ['instance', 'pixel']),
uClippingTexDim: UniformSpec('v2'),
tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
dClipping: DefineSpec('boolean'),
uVertexCount: UniformSpec('i'),
uInstanceCount: UniformSpec('i'),
uGroupCount: UniformSpec('i'),
uInvariantBoundingSphere: UniformSpec('v4'),
aInstance: AttributeSpec('float32', 1, 1),
aTransform: AttributeSpec('float32', 16, 1),
drawCount: ValueSpec('number'),
instanceCount: ValueSpec('number'),
alpha: ValueSpec('number'),
matrix: ValueSpec('m4'),
transform: ValueSpec('float32'),
extraTransform: ValueSpec('float32'),
hasReflection: ValueSpec('boolean'),
boundingSphere: ValueSpec('sphere'),
invariantBoundingSphere: ValueSpec('sphere'),
...BaseSchema,
aPosition: AttributeSpec('float32', 3, 0),
elements: ElementsSpec('uint32'),
uAlpha: UniformSpec('f'),
uColor: UniformSpec('v3'),
uColorTexDim: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
uIsoValue: UniformSpec('v2'),
uBboxMin: UniformSpec('v3'),

View File

@@ -15,9 +15,9 @@ import { InterpolationTypeNames } from '../../mol-geo/geometry/image/image';
export const ImageSchema = {
...BaseSchema,
aGroup: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
aUv: AttributeSpec('float32', 2, 0),
elements: ElementsSpec('uint32'),
uImageTexDim: UniformSpec('v2'),

View File

@@ -14,6 +14,7 @@ import { LinesShaderCode } from '../shader-code';
export const LinesSchema = {
...BaseSchema,
...SizeSchema,
aGroup: AttributeSpec('float32', 1, 0),
aMapping: AttributeSpec('float32', 2, 0),
aStart: AttributeSpec('float32', 3, 0),
aEnd: AttributeSpec('float32', 3, 0),

View File

@@ -13,6 +13,7 @@ import { ValueCell } from '../../mol-util';
export const MeshSchema = {
...BaseSchema,
aGroup: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
aNormal: AttributeSpec('float32', 3, 0),
elements: ElementsSpec('uint32'),

View File

@@ -14,6 +14,7 @@ import { ValueCell } from '../../mol-util';
export const PointsSchema = {
...BaseSchema,
...SizeSchema,
aGroup: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
dPointSizeAttenuation: DefineSpec('boolean'),
dPointFilledCircle: DefineSpec('boolean'),

View File

@@ -29,6 +29,7 @@ export type ValueKind = keyof ValueKindType
export type KindValue = UniformKindValue & DataTypeArrayType & TextureKindValue & ValueKindType
export type Values<S extends RenderableSchema> = { readonly [k in keyof S]: ValueCell<KindValue[S[k]['kind']]> }
export type UnboxedValues<S extends RenderableSchema> = { readonly [k in keyof S]: KindValue[S[k]['kind']] }
export function splitValues(schema: RenderableSchema, values: RenderableValues) {
const attributeValues: AttributeValues = {};
@@ -102,6 +103,7 @@ export type RenderableSchema = {
}
export type RenderableValues = { readonly [k: string]: ValueCell<any> }
//
export const GlobalUniformSchema = {
@@ -133,6 +135,7 @@ export const GlobalUniformSchema = {
uTransparentBackground: UniformSpec('b'),
uClipObjectType: UniformSpec('i[]'),
uClipObjectInvert: UniformSpec('b[]'),
uClipObjectPosition: UniformSpec('v3[]'),
uClipObjectRotation: UniformSpec('v4[]'),
uClipObjectScale: UniformSpec('v3[]'),
@@ -156,6 +159,8 @@ export const GlobalUniformSchema = {
uHighlightColor: UniformSpec('v3'),
uSelectColor: UniformSpec('v3'),
uXrayEdgeFalloff: UniformSpec('f'),
uRenderWboit: UniformSpec('b'),
} as const;
export type GlobalUniformSchema = typeof GlobalUniformSchema
@@ -237,7 +242,6 @@ export const BaseSchema = {
...ClippingSchema,
aInstance: AttributeSpec('float32', 1, 1),
aGroup: AttributeSpec('float32', 1, 0),
/**
* final per-instance transform calculated for instance `i` as
* `aTransform[i] = matrix * transform[i] * extraTransform[i]`

View File

@@ -14,6 +14,7 @@ import { ValueCell } from '../../mol-util';
export const SpheresSchema = {
...BaseSchema,
...SizeSchema,
aGroup: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
aMapping: AttributeSpec('float32', 2, 0),
elements: ElementsSpec('uint32'),

View File

@@ -14,6 +14,7 @@ import { ValueCell } from '../../mol-util';
export const TextSchema = {
...BaseSchema,
...SizeSchema,
aGroup: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
aMapping: AttributeSpec('float32', 2, 0),
aDepth: AttributeSpec('float32', 1, 0),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -13,15 +13,16 @@ import { ValueCell } from '../../mol-util';
export const TextureMeshSchema = {
...BaseSchema,
uGeoTexDim: UniformSpec('v2'),
/** texture has vertex positions in XYZ and group id in W */
tPositionGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tNormal: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tPosition: TextureSpec('texture', 'rgb', 'float', 'nearest'),
tGroup: TextureSpec('texture', 'alpha', 'float', 'nearest'),
tNormal: TextureSpec('texture', 'rgb', 'float', 'nearest'),
dFlatShaded: DefineSpec('boolean'),
dDoubleSided: DefineSpec('boolean'),
dFlipSided: DefineSpec('boolean'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dGeoTexture: DefineSpec('boolean'),
};
export type TextureMeshSchema = typeof TextureMeshSchema

View File

@@ -17,7 +17,7 @@ export function calculateTextureInfo (n: number, itemSize: number) {
return { width, height, length: width * height * itemSize };
}
export interface TextureImage<T extends Uint8Array | Float32Array> {
export interface TextureImage<T extends Uint8Array | Float32Array | Int32Array> {
readonly array: T
readonly width: number
readonly height: number
@@ -76,6 +76,7 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
img.style.top = '0px';
img.style.left = '0px';
img.style.border = 'solid grey';
img.style.pointerEvents = 'none';
document.body.appendChild(img);
}, 'image/png');
}

View File

@@ -20,6 +20,7 @@ import { stringToWords } from '../mol-util/string';
import { degToRad } from '../mol-math/misc';
import { createNullTexture, Texture, Textures } from './webgl/texture';
import { arrayMapUpsert } from '../mol-util/array';
import { clamp } from '../mol-math/interpolate';
export interface RendererStats {
programCount: number
@@ -50,7 +51,8 @@ interface Renderer {
renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlendedVolumeOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderBlendedVolumeTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
@@ -75,6 +77,8 @@ export const RendererParams = {
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
style: PD.MappedStatic('matte', {
custom: PD.Group({
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
@@ -94,10 +98,11 @@ export const RendererParams = {
variant: PD.Select('instance', PD.arrayToOptions<Clipping.Variant>(['instance', 'pixel'])),
objects: PD.ObjectList({
type: PD.Select('plane', PD.objectToOptions(Clipping.Type, t => stringToWords(t))),
invert: PD.Boolean(false),
position: PD.Vec3(Vec3()),
rotation: PD.Group({
axis: PD.Vec3(Vec3.create(1, 0, 0)),
angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }, { description: 'Angle in Degrees' }),
angle: PD.Numeric(0, { min: -180, max: 180, step: 1 }, { description: 'Angle in Degrees' }),
}, { isExpanded: true }),
scale: PD.Vec3(Vec3.create(1, 1, 1)),
}, o => stringToWords(o.type))
@@ -116,22 +121,22 @@ function getStyle(props: RendererProps['style']) {
};
case 'matte':
return {
lightIntensity: 0.6, ambientIntensity: 0.4,
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 1, reflectivity: 0.5
};
case 'glossy':
return {
lightIntensity: 0.6, ambientIntensity: 0.4,
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 0.4, reflectivity: 0.5
};
case 'metallic':
return {
lightIntensity: 0.6, ambientIntensity: 0.4,
metalness: 0.4, roughness: 0.6, reflectivity: 0.5
lightIntensity: 0.7, ambientIntensity: 0.7,
metalness: 0.6, roughness: 0.6, reflectivity: 0.5
};
case 'plastic':
return {
lightIntensity: 0.6, ambientIntensity: 0.4,
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 0.2, reflectivity: 0.5
};
}
@@ -142,6 +147,7 @@ type Clip = {
objects: {
count: number
type: number[]
invert: boolean[]
position: number[]
rotation: number[]
scale: number[]
@@ -150,8 +156,9 @@ type Clip = {
const tmpQuat = Quat();
function getClip(props: RendererProps['clip'], clip?: Clip): Clip {
const { type, position, rotation, scale } = clip?.objects || {
const { type, invert, position, rotation, scale } = clip?.objects || {
type: (new Array(5)).fill(1),
invert: (new Array(5)).fill(false),
position: (new Array(5 * 3)).fill(0),
rotation: (new Array(5 * 4)).fill(0),
scale: (new Array(5 * 3)).fill(1),
@@ -159,13 +166,14 @@ function getClip(props: RendererProps['clip'], clip?: Clip): Clip {
for (let i = 0, il = props.objects.length; i < il; ++i) {
const p = props.objects[i];
type[i] = Clipping.Type[p.type];
invert[i] = p.invert;
Vec3.toArray(p.position, position, i * 3);
Quat.toArray(Quat.setAxisAngle(tmpQuat, p.rotation.axis, degToRad(p.rotation.angle)), rotation, i * 4);
Vec3.toArray(p.scale, scale, i * 3);
}
return {
variant: props.variant,
objects: { count: props.objects.length, type, position, rotation, scale }
objects: { count: props.objects.length, type, invert, position, rotation, scale }
};
}
@@ -230,6 +238,7 @@ namespace Renderer {
uTransparentBackground: ValueCell.create(false),
uClipObjectType: ValueCell.create(clip.objects.type),
uClipObjectInvert: ValueCell.create(clip.objects.invert),
uClipObjectPosition: ValueCell.create(clip.objects.position),
uClipObjectRotation: ValueCell.create(clip.objects.rotation),
uClipObjectScale: ValueCell.create(clip.objects.scale),
@@ -250,24 +259,33 @@ namespace Renderer {
uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
uXrayEdgeFalloff: ValueCell.create(p.xrayEdgeFalloff),
};
const globalUniformList = Object.entries(globalUniforms);
let globalUniformsNeedUpdate = true;
const renderObject = (r: GraphicsRenderable, variant: GraphicsRenderVariant) => {
if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) {
if (r.state.disposed || !r.state.visible || (!r.state.pickable && variant[0] === 'p')) {
return;
}
let definesNeedUpdate = false;
if (r.values.dClipObjectCount.ref.value !== clip.objects.count) {
ValueCell.update(r.values.dClipObjectCount, clip.objects.count);
definesNeedUpdate = true;
}
if (r.values.dClipVariant.ref.value !== clip.variant) {
ValueCell.update(r.values.dClipVariant, clip.variant);
definesNeedUpdate = true;
if (r.state.noClip) {
if (r.values.dClipObjectCount.ref.value !== 0) {
ValueCell.update(r.values.dClipObjectCount, 0);
definesNeedUpdate = true;
}
} else {
if (r.values.dClipObjectCount.ref.value !== clip.objects.count) {
ValueCell.update(r.values.dClipObjectCount, clip.objects.count);
definesNeedUpdate = true;
}
if (r.values.dClipVariant.ref.value !== clip.variant) {
ValueCell.update(r.values.dClipVariant, clip.variant);
definesNeedUpdate = true;
}
}
if (definesNeedUpdate) r.update();
@@ -451,7 +469,7 @@ namespace Renderer {
}
};
const renderBlendedVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
const renderBlendedVolumeOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
state.enable(gl.BLEND);
@@ -460,7 +478,32 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
renderObject(r, 'colorBlended');
// TODO: simplify, handle on renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorBlended');
}
}
};
const renderBlendedVolumeTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
state.enable(gl.BLEND);
updateInternal(group, camera, depthTexture, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle on renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorBlended');
}
}
};
@@ -474,8 +517,11 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle on renderable.state???
if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) {
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorWboit');
}
}
@@ -487,8 +533,11 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle on renderable.state???
if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorWboit');
}
}
@@ -523,7 +572,8 @@ namespace Renderer {
renderBlended,
renderBlendedOpaque,
renderBlendedTransparent,
renderBlendedVolume,
renderBlendedVolumeOpaque,
renderBlendedVolumeTransparent,
renderWboitOpaque,
renderWboitTransparent,
@@ -561,6 +611,11 @@ namespace Renderer {
ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor));
}
if (props.xrayEdgeFalloff !== undefined && props.xrayEdgeFalloff !== p.xrayEdgeFalloff) {
p.xrayEdgeFalloff = props.xrayEdgeFalloff;
ValueCell.update(globalUniforms.uXrayEdgeFalloff, p.xrayEdgeFalloff);
}
if (props.style !== undefined) {
p.style = props.style;
Object.assign(style, getStyle(props.style));
@@ -598,9 +653,7 @@ namespace Renderer {
}
},
get props() {
return p;
},
props: p,
get stats(): RendererStats {
return {
programCount: ctx.stats.resourceCounts.program,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -23,12 +23,16 @@ export interface ShaderExtensions {
readonly shaderTextureLod?: ShaderExtensionsValue
}
type FragOutTypes = { [k in number]: 'vec4' | 'ivec4' }
export interface ShaderCode {
readonly id: number
readonly name: string
readonly vert: string
readonly frag: string
readonly extensions: ShaderExtensions
/** Fragment shader output type only applicable for webgl2 */
readonly outTypes: FragOutTypes
}
import apply_fog from './shader/chunks/apply-fog.glsl';
@@ -51,10 +55,12 @@ import common_clip from './shader/chunks/common-clip.glsl';
import common_frag_params from './shader/chunks/common-frag-params.glsl';
import common_vert_params from './shader/chunks/common-vert-params.glsl';
import common from './shader/chunks/common.glsl';
import float_to_rgba from './shader/chunks/float-to-rgba.glsl';
import light_frag_params from './shader/chunks/light-frag-params.glsl';
import matrix_scale from './shader/chunks/matrix-scale.glsl';
import normal_frag_params from './shader/chunks/normal-frag-params.glsl';
import read_from_texture from './shader/chunks/read-from-texture.glsl';
import rgba_to_float from './shader/chunks/rgba-to-float.glsl';
import size_vert_params from './shader/chunks/size-vert-params.glsl';
import texture3d_from_1d_trilinear from './shader/chunks/texture3d-from-1d-trilinear.glsl';
import texture3d_from_2d_linear from './shader/chunks/texture3d-from-2d-linear.glsl';
@@ -83,10 +89,12 @@ const ShaderChunks: { [k: string]: string } = {
common_frag_params,
common_vert_params,
common,
float_to_rgba,
light_frag_params,
matrix_scale,
normal_frag_params,
read_from_texture,
rgba_to_float,
size_vert_params,
texture3d_from_1d_trilinear,
texture3d_from_2d_linear,
@@ -113,10 +121,12 @@ function addIncludes(text: string) {
.replace(reMultipleLinebreaks, '\n');
}
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions };
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}, outTypes: FragOutTypes = {}): ShaderCode {
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions, outTypes };
}
// Note: `drawBuffers` need to be 'optional' for wboit
import points_vert from './shader/points.vert';
import points_frag from './shader/points.frag';
export const PointsShaderCode = ShaderCode('points', points_vert, points_frag, { drawBuffers: 'optional' });
@@ -125,6 +135,10 @@ import spheres_vert from './shader/spheres.vert';
import spheres_frag from './shader/spheres.frag';
export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: 'required', drawBuffers: 'optional' });
import cylinders_vert from './shader/cylinders.vert';
import cylinders_frag from './shader/cylinders.frag';
export const CylindersShaderCode = ShaderCode('cylinders', cylinders_vert, cylinders_frag, { fragDepth: 'required', drawBuffers: 'optional' });
import text_vert from './shader/text.vert';
import text_frag from './shader/text.frag';
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required', drawBuffers: 'optional' });
@@ -151,7 +165,7 @@ export type ShaderDefines = {
[k: string]: ValueCell<DefineType>
}
function getDefinesCode (defines: ShaderDefines) {
function getDefinesCode(defines: ShaderDefines) {
if (defines === undefined) return '';
const lines = [];
for (const name in defines) {
@@ -216,8 +230,6 @@ const glsl300VertPrefix = `#version 300 es
`;
const glsl300FragPrefixCommon = `
layout(location = 0) out highp vec4 out_FragData0;
#define varying in
#define texture2D texture
#define texture2DLodEXT textureLod
@@ -228,8 +240,12 @@ layout(location = 0) out highp vec4 out_FragData0;
#define depthTextureSupport
`;
function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) {
const prefix = [ '#version 300 es' ];
function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExtensions, shaderExtensions: ShaderExtensions, outTypes: FragOutTypes) {
const prefix = [
'#version 300 es',
`layout(location = 0) out highp ${outTypes[0] || 'vec4'} out_FragData0;`
];
if (shaderExtensions.standardDerivatives) {
prefix.push('#define enabledStandardDerivatives');
}
@@ -240,7 +256,7 @@ function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExten
prefix.push('#define requiredDrawBuffers');
const maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS) as number;
for (let i = 1, il = maxDrawBuffers; i < il; ++i) {
prefix.push(`layout(location = ${i}) out highp vec4 out_FragData${i};`);
prefix.push(`layout(location = ${i}) out highp ${outTypes[i] || 'vec4'} out_FragData${i};`);
}
}
if (shaderExtensions.shaderTextureLod) {
@@ -258,7 +274,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
const header = getDefinesCode(defines);
const vertPrefix = isWebGL2(gl) ? glsl300VertPrefix : '';
const fragPrefix = isWebGL2(gl)
? getGlsl300FragPrefix(gl, extensions, shaders.extensions)
? getGlsl300FragPrefix(gl, extensions, shaders.extensions, shaders.outTypes)
: getGlsl100FragPrefix(extensions, shaders.extensions);
const frag = isWebGL2(gl) ? transformGlsl300Frag(shaders.frag) : shaders.frag;
return {
@@ -266,6 +282,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
name: shaders.name,
vert: `${vertPrefix}${header}${shaders.vert}`,
frag: `${fragPrefix}${header}${frag}`,
extensions: shaders.extensions
extensions: shaders.extensions,
outTypes: shaders.outTypes
};
}

View File

@@ -1,6 +1,6 @@
export default `
float fogDepth = length(vViewPosition);
float fogFactor = smoothstep(uFogNear, uFogFar, fogDepth);
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
float preFogAlpha = gl_FragColor.a;
if (!uTransparentBackground) {

View File

@@ -49,6 +49,6 @@ vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffu
gl_FragColor = vec4(outgoingLight, color.a);
#ifdef dXrayShaded
gl_FragColor.a *= 1.0 - max(0.001, abs(dot(normal, vec3(0, 0, 1))));
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0, 0, 1))), uXrayEdgeFalloff);
#endif
`;

View File

@@ -9,9 +9,9 @@ export default `
#elif defined(dColorType_groupInstance)
vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
#elif defined(dColorType_vertex)
vColor.rgb = readFromTexture(tColor, aVertex, uColorTexDim).rgb;
vColor.rgb = readFromTexture(tColor, VertexID, uColorTexDim).rgb;
#elif defined(dColorType_vertexInstance)
vColor.rgb = readFromTexture(tColor, aInstance * float(uVertexCount) + aVertex, uColorTexDim).rgb;
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
#endif
#ifdef dOverpaint

View File

@@ -1,7 +1,6 @@
export default `
#ifdef dGeoTexture
// aGroup is used as a vertex index here and the group id is retirieved from tPositionGroup
float group = readFromTexture(tPositionGroup, aGroup, uGeoTexDim).w;
float group = decodeFloatRGB(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
#else
float group = aGroup;
#endif

View File

@@ -40,7 +40,18 @@ export default `
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
);
at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))];
int ci = int(intMod(coord.x, 4.0));
int ri = int(intMod(coord.y, 4.0));
#if __VERSION__ == 100
vec4 i = vec4(float(ci * 4 + ri));
vec4 v = thresholdMatrix[0] * vec4(equal(i, vec4(0.0, 1.0, 2.0, 3.0))) +
thresholdMatrix[1] * vec4(equal(i, vec4(4.0, 5.0, 6.0, 7.0))) +
thresholdMatrix[2] * vec4(equal(i, vec4(8.0, 9.0, 10.0, 11.0))) +
thresholdMatrix[3] * vec4(equal(i, vec4(12.0, 13.0, 14.0, 15.0)));
at = v.x + v.y + v.z + v.w;
#else
at = thresholdMatrix[ci][ri];
#endif
if (ta < 0.99 && (ta < 0.01 || ta < at)) {
discard;

View File

@@ -2,7 +2,7 @@ export default `
mat4 model = uModel * aTransform;
mat4 modelView = uView * model;
#ifdef dGeoTexture
vec3 position = readFromTexture(tPositionGroup, aGroup, uGeoTexDim).xyz;
vec3 position = readFromTexture(tPosition, VertexID, uGeoTexDim).xyz;
#else
vec3 position = aPosition;
#endif

View File

@@ -1,6 +1,6 @@
export default `
float depth = length(vViewPosition);
float fogFactor = smoothstep(uFogNear, uFogFar, depth);
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
float alpha = (1.0 - fogFactor) * uAlpha;
if (uAlpha < uPickingAlphaThreshold || alpha < 0.1)
discard; // ignore so the element below can be picked

View File

@@ -10,7 +10,7 @@ export default `
varying vec4 vOverpaint;
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ != 300
#if __VERSION__ == 100
varying vec4 vColor;
#else
flat in vec4 vColor;

View File

@@ -11,21 +11,13 @@ export default `
uniform sampler2D tColor;
#endif
#if defined(dColorType_vertex) || defined(dColorType_vertexInstance)
#if __VERSION__ != 300
attribute float aVertex;
#else
#define aVertex float(gl_VertexID)
#endif
#endif
#ifdef dOverpaint
varying vec4 vOverpaint;
uniform vec2 uOverpaintTexDim;
uniform sampler2D tOverpaint;
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ != 300
#if __VERSION__ == 100
varying vec4 vColor;
#else
flat out vec4 vColor;

View File

@@ -63,7 +63,7 @@ export default `
}
}
#if __VERSION__ != 300
#if __VERSION__ == 100
// 8-bit
int bitwiseAnd(int a, int b) {
int d = 128;
@@ -92,8 +92,10 @@ export default `
for (int i = 0; i < dClipObjectCount; ++i) {
if (flag == 0 || hasBit(flag, i + 1)) {
// TODO take sphere radius into account?
if (getSignedDistance(sphere.xyz, uClipObjectType[i], uClipObjectPosition[i], uClipObjectRotation[i], uClipObjectScale[i]) <= 0.0)
bool test = getSignedDistance(sphere.xyz, uClipObjectType[i], uClipObjectPosition[i], uClipObjectRotation[i], uClipObjectScale[i]) <= 0.0;
if ((!uClipObjectInvert[i] && test) || (uClipObjectInvert[i] && !test)) {
return true;
}
}
}
return false;

View File

@@ -5,12 +5,13 @@ uniform int uGroupCount;
#if dClipObjectCount != 0
uniform int uClipObjectType[dClipObjectCount];
uniform bool uClipObjectInvert[dClipObjectCount];
uniform vec3 uClipObjectPosition[dClipObjectCount];
uniform vec4 uClipObjectRotation[dClipObjectCount];
uniform vec3 uClipObjectScale[dClipObjectCount];
#if defined(dClipping)
#if __VERSION__ != 300
#if __VERSION__ == 100
varying float vClipping;
#else
flat in float vClipping;
@@ -20,7 +21,7 @@ uniform int uGroupCount;
uniform vec3 uHighlightColor;
uniform vec3 uSelectColor;
#if __VERSION__ != 300
#if __VERSION__ == 100
varying float vMarker;
#else
flat in float vMarker;
@@ -31,6 +32,10 @@ varying vec3 vViewPosition;
uniform vec2 uViewOffset;
uniform float uNear;
uniform float uFar;
uniform float uIsOrtho;
uniform float uFogNear;
uniform float uFogFar;
uniform vec3 uFogColor;
@@ -44,5 +49,7 @@ uniform bool uInteriorColorFlag;
uniform vec3 uInteriorColor;
bool interior;
uniform float uXrayEdgeFalloff;
uniform mat4 uProjection;
`;

View File

@@ -10,6 +10,7 @@ uniform vec4 uInvariantBoundingSphere;
#if dClipObjectCount != 0
uniform int uClipObjectType[dClipObjectCount];
uniform bool uClipObjectInvert[dClipObjectCount];
uniform vec3 uClipObjectPosition[dClipObjectCount];
uniform vec4 uClipObjectRotation[dClipObjectCount];
uniform vec3 uClipObjectScale[dClipObjectCount];
@@ -17,7 +18,7 @@ uniform vec4 uInvariantBoundingSphere;
#if defined(dClipping)
uniform vec2 uClippingTexDim;
uniform sampler2D tClipping;
#if __VERSION__ != 300
#if __VERSION__ == 100
varying float vClipping;
#else
flat out float vClipping;
@@ -27,7 +28,7 @@ uniform vec4 uInvariantBoundingSphere;
uniform vec2 uMarkerTexDim;
uniform sampler2D tMarker;
#if __VERSION__ != 300
#if __VERSION__ == 100
varying float vMarker;
#else
flat out float vMarker;
@@ -35,4 +36,17 @@ uniform sampler2D tMarker;
varying vec3 vModelPosition;
varying vec3 vViewPosition;
#if __VERSION__ == 100
attribute float aVertex;
#define VertexID int(aVertex)
#else
// not using gl_VertexID but aVertex to ensure there is an active attribute with divisor 0
// since FF 85 this is not needed anymore but lets keep it for backwards compatibility
// https://bugzilla.mozilla.org/show_bug.cgi?id=1679693
// see also note in src/mol-gl/webgl/render-item.ts
attribute float aVertex;
#define VertexID int(aVertex)
// #define VertexID gl_VertexID
#endif
`;

View File

@@ -26,9 +26,11 @@ export default `
#define saturate(a) clamp(a, 0.0, 1.0)
float intDiv(const in float a, const in float b) { return float(int(a) / int(b)); }
vec2 ivec2Div(const in vec2 a, const in vec2 b) { return vec2(ivec2(a) / ivec2(b)); }
float intMod(const in float a, const in float b) { return a - b * float(int(a) / int(b)); }
int imod(const in int a, const in int b) { return a - b * (a / b); }
float pow2(const in float x) { return x*x; }
float pow2(const in float x) { return x * x; }
const float maxFloat = 10000.0; // NOTE constant also set in TypeScript
const float floatLogFactor = 9.210440366976517; // log(maxFloat + 1.0);
@@ -49,6 +51,25 @@ float decodeFloatRGB(const in vec3 rgb) {
return (rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0) - 1.0;
}
vec2 packUnitIntervalToRG(const in float v) {
vec2 enc;
enc.xy = vec2(fract(v * 256.0), v);
enc.y -= enc.x * (1.0 / 256.0);
enc.xy *= 256.0 / 255.0;
return enc;
}
float unpackRGToUnitInterval(const in vec2 enc) {
return dot(enc, vec2(255.0 / (256.0 * 256.0), 255.0 / 256.0));
}
vec3 screenSpaceToViewSpace(const in vec3 ssPos, const in mat4 invProjection) {
vec4 p = vec4(ssPos * 2.0 - 1.0, 1.0);
p = invProjection * p;
return p.xyz / p.w;
}
const float PackUpscale = 256.0 / 255.0; // fraction -> 0..1 (including 1)
const float UnpackDownscale = 255.0 / 256.0; // 0..1 -> fraction (excluding 1)
const vec3 PackFactors = vec3(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0);
@@ -71,11 +92,23 @@ vec4 linearTosRGB(const in vec4 c) {
return vec4(mix(pow(c.rgb, vec3(0.41666)) * 1.055 - vec3(0.055), c.rgb * 12.92, vec3(lessThanEqual(c.rgb, vec3(0.0031308)))), c.a);
}
float linearizeDepth(in float depth, in float near, in float far) {
float linearizeDepth(const in float depth, const in float near, const in float far) {
return (2.0 * near) / (far + near - depth * (far - near));
}
#if __VERSION__ != 300
float perspectiveDepthToViewZ(const in float invClipZ, const in float near, const in float far) {
return (near * far) / ((far - near) * invClipZ - far);
}
float orthographicDepthToViewZ(const in float linearClipZ, const in float near, const in float far) {
return linearClipZ * (near - far) - near;
}
float depthToViewZ(const in float isOrtho, const in float linearClipZ, const in float near, const in float far) {
return isOrtho == 1.0 ? orthographicDepthToViewZ(linearClipZ, near, far) : perspectiveDepthToViewZ(linearClipZ, near, far);
}
#if __VERSION__ == 100
// transpose
float transpose(const in float m) {

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default `
// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba
// MIT License, Copyright (c) 2020 Equinor
float shiftRight (float v, float amt) {
v = floor(v) + 0.5;
return floor(v / exp2(amt));
}
float shiftLeft (float v, float amt) {
return floor(v * exp2(amt) + 0.5);
}
float maskLast (float v, float bits) {
return mod(v, shiftLeft(1.0, bits));
}
float extractBits (float num, float from, float to) {
from = floor(from + 0.5); to = floor(to + 0.5);
return maskLast(shiftRight(num, from), to - from);
}
vec4 floatToRgba(float texelFloat, bool littleEndian) {
if (texelFloat == 0.0) return vec4(0.0, 0.0, 0.0, 0.0);
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
texelFloat = abs(texelFloat);
float exponent = floor(log2(texelFloat));
float biased_exponent = exponent + 127.0;
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
float t = biased_exponent / 2.0;
float last_bit_of_biased_exponent = fract(t) * 2.0;
float remaining_bits_of_biased_exponent = floor(t);
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
return (
littleEndian
? vec4(byte4, byte3, byte2, byte1)
: vec4(byte1, byte2, byte3, byte4)
);
}
`;

View File

@@ -15,97 +15,97 @@ uniform float uMetalness;
uniform float uRoughness;
struct PhysicalMaterial {
vec3 diffuseColor;
float specularRoughness;
vec3 specularColor;
vec3 diffuseColor;
float specularRoughness;
vec3 specularColor;
};
struct IncidentLight {
vec3 color;
vec3 direction;
vec3 color;
vec3 direction;
};
struct ReflectedLight {
vec3 directDiffuse;
vec3 directSpecular;
vec3 indirectDiffuse;
vec3 directDiffuse;
vec3 directSpecular;
vec3 indirectDiffuse;
};
struct GeometricContext {
vec3 position;
vec3 normal;
vec3 viewDir;
vec3 position;
vec3 normal;
vec3 viewDir;
};
vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotLH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
float fresnel = exp2((-5.55473 * dotLH - 6.98316) * dotLH);
return (1.0 - specularColor) * fresnel + specularColor;
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotLH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
float fresnel = exp2((-5.55473 * dotLH - 6.98316) * dotLH);
return (1.0 - specularColor) * fresnel + specularColor;
}
// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
float a2 = pow2(alpha);
// dotNL and dotNV are explicitly swapped. This is not a mistake.
float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));
float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));
return 0.5 / max(gv + gl, EPSILON);
float a2 = pow2(alpha);
// dotNL and dotNV are explicitly swapped. This is not a mistake.
float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));
float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));
return 0.5 / max(gv + gl, EPSILON);
}
// Microfacet Models for Refraction through Rough Surfaces - equation (33)
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
// alpha is "roughness squared" in Disneys reparameterization
float D_GGX(const in float alpha, const in float dotNH) {
float a2 = pow2(alpha);
float denom = pow2(dotNH) * (a2 - 1.0) + 1.0; // avoid alpha = 0 with dotNH = 1
return RECIPROCAL_PI * a2 / pow2(denom);
float a2 = pow2(alpha);
float denom = pow2(dotNH) * (a2 - 1.0) + 1.0; // avoid alpha = 0 with dotNH = 1
return RECIPROCAL_PI * a2 / pow2(denom);
}
vec3 BRDF_Diffuse_Lambert(const in vec3 diffuseColor) {
return RECIPROCAL_PI * diffuseColor;
return RECIPROCAL_PI * diffuseColor;
}
// GGX Distribution, Schlick Fresnel, GGX-Smith Visibility
vec3 BRDF_Specular_GGX(const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
float alpha = pow2(roughness); // UE4's roughness
vec3 halfDir = normalize(incidentLight.direction + geometry.viewDir);
float alpha = pow2(roughness); // UE4's roughness
vec3 halfDir = normalize(incidentLight.direction + geometry.viewDir);
float dotNL = saturate(dot(geometry.normal, incidentLight.direction));
float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
float dotNH = saturate(dot(geometry.normal, halfDir));
float dotLH = saturate(dot(incidentLight.direction, halfDir));
float dotNL = saturate(dot(geometry.normal, incidentLight.direction));
float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
float dotNH = saturate(dot(geometry.normal, halfDir));
float dotLH = saturate(dot(incidentLight.direction, halfDir));
vec3 F = F_Schlick(specularColor, dotLH);
float G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV);
float D = D_GGX(alpha, dotNH);
return F * (G * D);
vec3 F = F_Schlick(specularColor, dotLH);
float G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV);
float D = D_GGX(alpha, dotNH);
return F * (G * D);
}
// ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile
vec3 BRDF_Specular_GGX_Environment(const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
const vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022);
const vec4 c1 = vec4(1, 0.0425, 1.04, -0.04);
vec4 r = roughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
return specularColor * AB.x + AB.y;
float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
const vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022);
const vec4 c1 = vec4(1, 0.0425, 1.04, -0.04);
vec4 r = roughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
return specularColor * AB.x + AB.y;
}
void RE_Direct_Physical(const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
float dotNL = saturate(dot(geometry.normal, directLight.direction));
float dotNL = saturate(dot(geometry.normal, directLight.direction));
vec3 irradiance = dotNL * directLight.color;
irradiance *= PI; // punctual light
irradiance *= PI; // punctual light
reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX(directLight, geometry, material.specularColor, material.specularRoughness);
reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX(directLight, geometry, material.specularColor, material.specularRoughness);
reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
}
void RE_IndirectDiffuse_Physical(const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
}
`;

View File

@@ -5,10 +5,17 @@
*/
export default `
vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 dim) {
vec4 readFromTexture(const in sampler2D tex, const in float i, const in vec2 dim) {
float x = intMod(i, dim.x);
float y = floor(intDiv(i, dim.x));
vec2 uv = (vec2(x, y) + 0.5) / dim;
return texture2D(tex, uv);
}
vec4 readFromTexture(const in sampler2D tex, const in int i, const in vec2 dim) {
int x = imod(i, int(dim.x));
int y = i / int(dim.x);
vec2 uv = (vec2(x, y) + 0.5) / dim;
return texture2D(tex, uv);
}
`;

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default `
// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float
// BSD 3-Clause License
//
// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved.
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
ivec4 floatsToBytes(vec4 inputFloats, bool littleEndian) {
ivec4 bytes = ivec4(inputFloats * 255.0);
return (
littleEndian
? bytes.abgr
: bytes
);
}
// Break the four bytes down into an array of 32 bits.
void bytesToBits(const in ivec4 bytes, out bool bits[32]) {
for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {
float acc = float(bytes[channelIndex]);
for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {
float powerOfTwo = exp2(float(indexInByte));
bool bit = acc >= powerOfTwo;
bits[channelIndex * 8 + (7 - indexInByte)] = bit;
acc = mod(acc, powerOfTwo);
}
}
}
// Compute the exponent of the 32-bit float.
float getExponent(bool bits[32]) {
const int startIndex = 1;
const int bitStringLength = 8;
const int endBeforeIndex = startIndex + bitStringLength;
float acc = 0.0;
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Compute the mantissa of the 32-bit float.
float getMantissa(bool bits[32], bool subnormal) {
const int startIndex = 9;
const int bitStringLength = 23;
const int endBeforeIndex = startIndex + bitStringLength;
// Leading/implicit/hidden bit convention:
// If the number is not subnormal (with exponent 0), we add a leading 1 digit.
float acc = float(!subnormal) * exp2(float(bitStringLength));
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Parse the float from its 32 bits.
float bitsToFloat(bool bits[32]) {
float signBit = float(bits[0]) * -2.0 + 1.0;
float exponent = getExponent(bits);
bool subnormal = abs(exponent - 0.0) < 0.01;
float mantissa = getMantissa(bits, subnormal);
float exponentBias = 127.0;
return signBit * mantissa * exp2(exponent - exponentBias - 23.0);
}
float rgbaToFloat(vec4 texelRGBA, bool littleEndian) {
ivec4 rgbaBytes = floatsToBytes(texelRGBA, littleEndian);
bool bits[32];
bytesToBits(rgbaBytes, bits);
return bitsToFloat(bits);
}
`;

View File

@@ -1,3 +1,10 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
*/
export default `
#if defined(dRenderVariant_colorWboit)
if (!uRenderWboit) {

View File

@@ -0,0 +1,12 @@
export const copy_frag = `
precision highp float;
precision highp sampler2D;
uniform sampler2D tColor;
uniform vec2 uTexSize;
void main() {
vec2 coords = gl_FragCoord.xy / uTexSize;
gl_FragColor = texture2D(tColor, coords);
}
`;

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
export default `
precision highp float;
precision highp int;
uniform mat4 uView;
varying mat4 vTransform;
varying vec3 vStart;
varying vec3 vEnd;
varying float vSize;
varying float vCap;
uniform vec3 uCameraDir;
uniform vec3 uCameraPosition;
#include common
#include common_frag_params
#include color_frag_params
#include light_frag_params
#include common_clip
#include wboit_params
// adapted from https://www.shadertoy.com/view/4lcSRn
// The MIT License, Copyright 2016 Inigo Quilez
bool CylinderImpostor(
in vec3 rayOrigin, in vec3 rayDir,
in vec3 start, in vec3 end, in float radius,
out vec4 intersection, out bool interior
){
vec3 ba = end - start;
vec3 oc = rayOrigin - start;
float baba = dot(ba, ba);
float bard = dot(ba, rayDir);
float baoc = dot(ba, oc);
float k2 = baba - bard*bard;
float k1 = baba * dot(oc, rayDir) - baoc * bard;
float k0 = baba * dot(oc, oc) - baoc * baoc - radius * radius * baba;
float h = k1 * k1 - k2 * k0;
if (h < 0.0) return false;
bool topCap = (vCap > 0.9 && vCap < 1.1) || vCap >= 2.9;
bool bottomCap = (vCap > 1.9 && vCap < 2.1) || vCap >= 2.9;
// body outside
h = sqrt(h);
float t = (-k1 - h) / k2;
float y = baoc + t * bard;
if (y > 0.0 && y < baba) {
interior = false;
intersection = vec4(t, (oc + t * rayDir - ba * y / baba) / radius);
return true;
}
if (topCap && y < 0.0) {
// top cap
t = -baoc / bard;
if (abs(k1 + k2 * t) < h) {
interior = false;
intersection = vec4(t, ba * sign(y) / baba);
return true;
}
} else if(bottomCap && y >= 0.0) {
// bottom cap
t = (baba - baoc) / bard;
if (abs(k1 + k2 * t) < h) {
interior = false;
intersection = vec4(t, ba * sign(y) / baba);
return true;
}
}
#ifdef dDoubleSided
// body inside
h = -h;
t = (-k1 - h) / k2;
y = baoc + t * bard;
if (y > 0.0 && y < baba) {
interior = true;
intersection = vec4(t, (oc + t * rayDir - ba * y / baba) / radius);
return true;
}
// TODO: handle inside caps???
#endif
return false;
}
void main() {
#include clip_pixel
vec3 rayDir = mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho);
vec4 intersection;
bool interior;
bool hit = CylinderImpostor(vModelPosition, rayDir, vStart, vEnd, vSize, intersection, interior);
if (!hit) discard;
vec3 vViewPosition = vModelPosition + intersection.x * rayDir;
vViewPosition = (uView * vec4(vViewPosition, 1.0)).xyz;
gl_FragDepthEXT = calcDepth(vViewPosition);
// bugfix (mac only?)
if (gl_FragDepthEXT < 0.0) discard;
if (gl_FragDepthEXT > 1.0) discard;
float fragmentDepth = gl_FragDepthEXT;
#include assign_material_color
#if defined(dRenderVariant_pick)
#include check_picking_alpha
gl_FragColor = material;
#elif defined(dRenderVariant_depth)
gl_FragColor = material;
#elif defined(dRenderVariant_color)
#ifdef dIgnoreLight
gl_FragColor = material;
#else
mat3 normalMatrix = transpose3(inverse3(mat3(uView)));
vec3 normal = normalize(normalMatrix * -normalize(intersection.yzw));
#include apply_light_color
#endif
#include apply_interior_color
#include apply_marker_color
#include apply_fog
#include wboit_write
#endif
}
`;

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
export default `
precision highp float;
precision highp int;
#include common
#include read_from_texture
#include common_vert_params
#include color_vert_params
#include size_vert_params
#include common_clip
uniform mat4 uModelView;
attribute mat4 aTransform;
attribute float aInstance;
attribute float aGroup;
attribute vec3 aMapping;
attribute vec3 aStart;
attribute vec3 aEnd;
attribute float aScale;
attribute float aCap;
varying mat4 vTransform;
varying vec3 vStart;
varying vec3 vEnd;
varying float vSize;
varying float vCap;
uniform float uIsOrtho;
uniform vec3 uCameraDir;
void main() {
#include assign_group
#include assign_color_varying
#include assign_marker_varying
#include assign_clipping_varying
#include assign_size
mat4 modelTransform = uModel * aTransform;
vTransform = aTransform;
vStart = (modelTransform * vec4(aStart, 1.0)).xyz;
vEnd = (modelTransform * vec4(aEnd, 1.0)).xyz;
vSize = size * aScale;
vCap = aCap;
vModelPosition = (vStart + vEnd) * 0.5;
vec3 camDir = -mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho);
vec3 dir = vEnd - vStart;
// ensure cylinder 'dir' is pointing towards the camera
if(dot(camDir, dir) < 0.0) dir = -dir;
vec3 left = cross(camDir, dir);
vec3 up = cross(left, dir);
left = vSize * normalize(left);
up = vSize * normalize(up);
// move vertex in object-space from center to corner
vModelPosition += aMapping.x * dir + aMapping.y * left + aMapping.z * up;
vec4 mvPosition = uView * vec4(vModelPosition, 1.0);
vViewPosition = mvPosition.xyz;
gl_Position = uProjection * mvPosition;
#include clip_instance
}
`;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -14,6 +14,7 @@ precision highp int;
#if dClipObjectCount != 0
uniform int uClipObjectType[dClipObjectCount];
uniform bool uClipObjectInvert[dClipObjectCount];
uniform vec3 uClipObjectPosition[dClipObjectCount];
uniform vec4 uClipObjectRotation[dClipObjectCount];
uniform vec3 uClipObjectScale[dClipObjectCount];
@@ -30,8 +31,6 @@ uniform vec3 uCameraDir;
uniform sampler2D tDepth;
uniform vec2 uDrawingBufferSize;
uniform float uNear;
uniform float uFar;
varying vec3 vOrigPos;
varying float vInstance;
@@ -70,13 +69,15 @@ uniform bool uInteriorColorFlag;
uniform vec3 uInteriorColor;
bool interior;
uniform float uNear;
uniform float uFar;
uniform float uIsOrtho;
uniform vec3 uCellDim;
uniform vec3 uCameraPosition;
uniform mat4 uCartnToUnit;
#if __VERSION__ == 300
#if __VERSION__ != 100
// for webgl1 this is given as a 'define'
uniform int uMaxSteps;
#endif
@@ -166,6 +167,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
vec4 src = vec4(0.0);
vec4 dst = vec4(0.0);
bool hit = false;
float fragmentDepth;
vec3 posMin = vec3(0.0);
vec3 posMax = vec3(1.0) - vec3(1.0) / uGridDim;
@@ -258,7 +260,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#ifdef enabledFragDepth
return packDepthToRGBA(gl_FragDepthEXT);
#else
return packDepthToRGBA(gl_FragCoord.z);
return packDepthToRGBA(depth);
#endif
#elif defined(dRenderVariant_color)
#ifdef dPackedGroup
@@ -325,6 +327,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#include apply_marker_color
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
fragmentDepth = depth;
#include apply_fog
src = gl_FragColor;
@@ -393,6 +396,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#include apply_marker_color
preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
fragmentDepth = calcDepth(mvPosition.xyz);
#include apply_fog
src = gl_FragColor;
@@ -424,7 +428,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
// TODO: support float texture for higher precision values???
// TODO: support clipping exclusion texture support
void main () {
void main() {
if (gl_FrontFacing)
discard;
@@ -442,8 +446,9 @@ void main () {
vec3 step = rayDir * uStepScale;
float boundingSphereNear = distance(vBoundingSphere.xyz, uCameraPosition) - vBoundingSphere.w;
float d = max(uNear, boundingSphereNear);
gl_FragColor = raymarch(uCameraPosition + (d * rayDir), step, rayDir);
float d = max(uNear, boundingSphereNear) - mix(0.0, distance(vOrigPos, uCameraPosition), uIsOrtho);
vec3 start = mix(uCameraPosition, vOrigPos, uIsOrtho) + (d * rayDir);
gl_FragColor = raymarch(start, step, rayDir);
#if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
// discard when nothing was hit
@@ -455,7 +460,7 @@ void main () {
#if defined(dRenderMode_isosurface) && defined(enabledFragDepth)
float fragmentDepth = gl_FragDepthEXT;
#else
float fragmentDepth = calcDepth((uView * vec4(uCameraPosition + (d * rayDir), 1.0)).xyz);
float fragmentDepth = calcDepth((uModelView * vec4(start, 1.0)).xyz);
#endif
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
interior = false;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -43,7 +43,7 @@ void main() {
gl_Position = uProjection * mvPosition;
// move z position to near clip plane
gl_Position.z = gl_Position.w - 0.0001;
// move z position to near clip plane (but not too close to get precision issues)
gl_Position.z = gl_Position.w - 0.01;
}
`;

View File

@@ -4,104 +4,226 @@ precision highp int;
precision highp sampler2D;
uniform sampler2D tColor;
uniform vec2 uTexSize;
uniform vec2 uTexSizeInv;
// Basic FXAA implementation based on the code on geeks3d.com with the
// modification that the texture2DLod stuff was removed since it's
// unsupported by WebGL.
// --
// From:
// https://github.com/mitsuhiko/webgl-meincraft
// Copyright (c) 2011 by Armin Ronacher.
// Some rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * The names of the contributors may not be used to endorse or
// promote products derived from this software without specific
// prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// */
// adapted from https://github.com/kosua20/Rendu
// MIT License Copyright (c) 2017 Simon Rodriguez
#ifndef FXAA_REDUCE_MIN
#define FXAA_REDUCE_MIN (1.0/ 128.0)
#endif
#ifndef FXAA_REDUCE_MUL
#define FXAA_REDUCE_MUL (1.0 / 8.0)
#endif
#ifndef FXAA_SPAN_MAX
#define FXAA_SPAN_MAX 8.0
#endif
#define QUALITY(q) ((q) < 5 ? 1.0 : ((q) > 5 ? ((q) < 10 ? 2.0 : ((q) < 11 ? 4.0 : 8.0)) : 1.5))
vec4 fxaa(sampler2D tex, const in vec2 fragCoord, const in vec2 resolution) {
vec2 inverseVP = 1.0 / resolution;
vec2 v_rgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP;
vec2 v_rgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP;
vec2 v_rgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP;
vec2 v_rgbSE = (fragCoord + vec2(1.0, 1.0)) * inverseVP;
vec2 v_rgbM = vec2(fragCoord * inverseVP);
float rgb2luma(vec3 rgb){
return sqrt(dot(rgb, vec3(0.299, 0.587, 0.114)));
}
vec4 col = vec4(0.0);
vec3 rgbNW = texture2D(tex, v_rgbNW).xyz;
vec3 rgbNE = texture2D(tex, v_rgbNE).xyz;
vec3 rgbSW = texture2D(tex, v_rgbSW).xyz;
vec3 rgbSE = texture2D(tex, v_rgbSE).xyz;
vec4 texColor = texture2D(tex, v_rgbM);
vec3 rgbM = texColor.xyz;
vec3 luma = vec3(0.299, 0.587, 0.114);
float lumaNW = dot(rgbNW, luma);
float lumaNE = dot(rgbNE, luma);
float lumaSW = dot(rgbSW, luma);
float lumaSE = dot(rgbSE, luma);
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
float sampleLuma(vec2 uv) {
return rgb2luma(texture2D(tColor, uv).rgb);
}
vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
(0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
dir * rcpDirMin)) * inverseVP;
vec4 rgbA1 = texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5));
vec4 rgbA2 = texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5));
vec4 rgbA = 0.5 * (rgbA1 + rgbA2);
vec4 rgbB1 = texture2D(tex, fragCoord * inverseVP + dir * -0.5);
vec4 rgbB2 = texture2D(tex, fragCoord * inverseVP + dir * 0.5);
vec4 rgbB = rgbA * 0.5 + 0.25 * (rgbB1 + rgbB2);
float lumaB = dot(rgbB.rgb, luma);
if ((lumaB < lumaMin) || (lumaB > lumaMax))
col = vec4(rgbA.rgb, rgbA.a);
else
col = vec4(rgbB.rgb, rgbB.a);
return col;
float sampleLuma(vec2 uv, float uOffset, float vOffset) {
uv += uTexSizeInv * vec2(uOffset, vOffset);
return sampleLuma(uv);
}
void main(void) {
gl_FragColor = fxaa(tColor, gl_FragCoord.xy, uTexSize);
vec2 coords = gl_FragCoord.xy * uTexSizeInv;
vec2 inverseScreenSize = uTexSizeInv;
vec4 colorCenter = texture2D(tColor, coords);
// Luma at the current fragment
float lumaCenter = rgb2luma(colorCenter.rgb);
// Luma at the four direct neighbours of the current fragment.
float lumaDown = sampleLuma(coords, 0.0, -1.0);
float lumaUp = sampleLuma(coords, 0.0, 1.0);
float lumaLeft = sampleLuma(coords, -1.0, 0.0);
float lumaRight = sampleLuma(coords, 1.0, 0.0);
// Find the maximum and minimum luma around the current fragment.
float lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
float lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));
// Compute the delta.
float lumaRange = lumaMax - lumaMin;
// If the luma variation is lower that a threshold (or if we are in a really dark area),
// we are not on an edge, don't perform any AA.
if (lumaRange < max(dEdgeThresholdMin, lumaMax * dEdgeThresholdMax)) {
gl_FragColor = colorCenter;
return;
}
// Query the 4 remaining corners lumas.
float lumaDownLeft = sampleLuma(coords, -1.0, -1.0);
float lumaUpRight = sampleLuma(coords, 1.0, 1.0);
float lumaUpLeft = sampleLuma(coords, -1.0, 1.0);
float lumaDownRight = sampleLuma(coords, 1.0, -1.0);
// Combine the four edges lumas (using intermediary variables for future computations
// with the same values).
float lumaDownUp = lumaDown + lumaUp;
float lumaLeftRight = lumaLeft + lumaRight;
// Same for corners
float lumaLeftCorners = lumaDownLeft + lumaUpLeft;
float lumaDownCorners = lumaDownLeft + lumaDownRight;
float lumaRightCorners = lumaDownRight + lumaUpRight;
float lumaUpCorners = lumaUpRight + lumaUpLeft;
// Compute an estimation of the gradient along the horizontal and vertical axis.
float edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) + abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 + abs(-2.0 * lumaRight + lumaRightCorners);
float edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) + abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 + abs(-2.0 * lumaDown + lumaDownCorners);
// Is the local edge horizontal or vertical ?
bool isHorizontal = (edgeHorizontal >= edgeVertical);
// Choose the step size (one pixel) accordingly.
float stepLength = isHorizontal ? inverseScreenSize.y : inverseScreenSize.x;
// Select the two neighboring texels lumas in the opposite direction to the local edge.
float luma1 = isHorizontal ? lumaDown : lumaLeft;
float luma2 = isHorizontal ? lumaUp : lumaRight;
// Compute gradients in this direction.
float gradient1 = luma1 - lumaCenter;
float gradient2 = luma2 - lumaCenter;
// Which direction is the steepest ?
bool is1Steepest = abs(gradient1) >= abs(gradient2);
// Gradient in the corresponding direction, normalized.
float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));
// Average luma in the correct direction.
float lumaLocalAverage = 0.0;
if(is1Steepest){
// Switch the direction
stepLength = -stepLength;
lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
} else {
lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
}
// Shift UV in the correct direction by half a pixel.
vec2 currentUv = coords;
if(isHorizontal){
currentUv.y += stepLength * 0.5;
} else {
currentUv.x += stepLength * 0.5;
}
// Compute offset (for each iteration step) in the right direction.
vec2 offset = isHorizontal ? vec2(inverseScreenSize.x, 0.0) : vec2(0.0, inverseScreenSize.y);
// Compute UVs to explore on each side of the edge, orthogonally.
// The QUALITY allows us to step faster.
vec2 uv1 = currentUv - offset * QUALITY(0);
vec2 uv2 = currentUv + offset * QUALITY(0);
// Read the lumas at both current extremities of the exploration segment,
// and compute the delta wrt to the local average luma.
float lumaEnd1 = sampleLuma(uv1);
float lumaEnd2 = sampleLuma(uv2);
lumaEnd1 -= lumaLocalAverage;
lumaEnd2 -= lumaLocalAverage;
// If the luma deltas at the current extremities is larger than the local gradient,
// we have reached the side of the edge.
bool reached1 = abs(lumaEnd1) >= gradientScaled;
bool reached2 = abs(lumaEnd2) >= gradientScaled;
bool reachedBoth = reached1 && reached2;
// If the side is not reached, we continue to explore in this direction.
if(!reached1){
uv1 -= offset * QUALITY(1);
}
if(!reached2){
uv2 += offset * QUALITY(1);
}
// If both sides have not been reached, continue to explore.
if(!reachedBoth){
for(int i = 2; i < dIterations; i++){
// If needed, read luma in 1st direction, compute delta.
if(!reached1){
lumaEnd1 = sampleLuma(uv1);
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
}
// If needed, read luma in opposite direction, compute delta.
if(!reached2){
lumaEnd2 = sampleLuma(uv2);
lumaEnd2 = lumaEnd2 - lumaLocalAverage;
}
// If the luma deltas at the current extremities is larger than the local gradient,
// we have reached the side of the edge.
reached1 = abs(lumaEnd1) >= gradientScaled;
reached2 = abs(lumaEnd2) >= gradientScaled;
reachedBoth = reached1 && reached2;
// If the side is not reached, we continue to explore in this direction,
// with a variable quality.
if(!reached1){
uv1 -= offset * QUALITY(i);
}
if(!reached2){
uv2 += offset * QUALITY(i);
}
// If both sides have been reached, stop the exploration.
if(reachedBoth){
break;
}
}
}
// Compute the distances to each side edge of the edge (!).
float distance1 = isHorizontal ? (coords.x - uv1.x) : (coords.y - uv1.y);
float distance2 = isHorizontal ? (uv2.x - coords.x) : (uv2.y - coords.y);
// In which direction is the side of the edge closer ?
bool isDirection1 = distance1 < distance2;
float distanceFinal = min(distance1, distance2);
// Thickness of the edge.
float edgeThickness = (distance1 + distance2);
// Is the luma at center smaller than the local average ?
bool isLumaCenterSmaller = lumaCenter < lumaLocalAverage;
// If the luma at center is smaller than at its neighbour,
// the delta luma at each end should be positive (same variation).
bool correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller;
bool correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller;
// Only keep the result in the direction of the closer side of the edge.
bool correctVariation = isDirection1 ? correctVariation1 : correctVariation2;
// UV offset: read in the direction of the closest side of the edge.
float pixelOffset = - distanceFinal / edgeThickness + 0.5;
// If the luma variation is incorrect, do not offset.
float finalOffset = correctVariation ? pixelOffset : 0.0;
// Sub-pixel shifting
// Full weighted average of the luma over the 3x3 neighborhood.
float lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
// Ratio of the delta between the global average and the center luma,
// over the luma range in the 3x3 neighborhood.
float subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
float subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
// Compute a sub-pixel offset based on this delta.
float subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * float(dSubpixelQuality);
// Pick the biggest of the two offsets.
finalOffset = max(finalOffset, subPixelOffsetFinal);
// Compute the final UV coordinates.
vec2 finalUv = coords;
if(isHorizontal){
finalUv.y += finalOffset * stepLength;
} else {
finalUv.x += finalOffset * stepLength;
}
// Read the color at the new UV coordinates, and use it.
gl_FragColor = texture2D(tColor, finalUv);
}
`;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -31,7 +31,7 @@ uniform float uCurrentX;
uniform float uCurrentY;
uniform float uAlpha;
uniform float uResolution;
uniform float uRadiusFactor;
uniform float uRadiusFactorInv;
void main() {
vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5;
@@ -40,16 +40,16 @@ void main() {
#if defined(dCalcType_density)
float density = exp(-uAlpha * ((dist * dist) * vRadiusSqInv));
gl_FragColor.a = density / uRadiusFactor;
gl_FragColor.a = density * uRadiusFactorInv;
#elif defined(dCalcType_minDistance)
gl_FragColor.a = 1.0 - dist / uRadiusFactor;
gl_FragColor.a = 1.0 - dist * uRadiusFactorInv;
#elif defined(dCalcType_groupId)
#if defined(dGridTexType_2d)
float minDistance = 1.0 - texture2D(tMinDistanceTex, (gl_FragCoord.xy) / (uGridTexDim.xy / uGridTexScale)).a;
#elif defined(dGridTexType_3d)
float minDistance = 1.0 - texelFetch(tMinDistanceTex, ivec3(gl_FragCoord.xy, uCurrentSlice), 0).a;
#endif
if (dist / uRadiusFactor > minDistance + uResolution * 0.05)
if (dist * uRadiusFactorInv > minDistance + uResolution * 0.05)
discard;
gl_FragColor.rgb = encodeFloatRGB(vGroup);
#endif

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -21,7 +21,6 @@ varying float vRadiusSqInv;
uniform vec3 uBboxSize;
uniform vec3 uBboxMin;
uniform float uCurrentSlice;
uniform float uResolution;
void main() {
@@ -29,7 +28,7 @@ void main() {
#if defined(dCalcType_groupId)
vGroup = aGroup;
#endif
gl_PointSize = floor(((aRadius * 6.0) / uResolution) + 0.5);
gl_PointSize = ceil(((aRadius * 3.0) / uResolution) + uResolution);
vPosition = (aPosition - uBboxMin) / uResolution;
gl_Position = vec4(((aPosition - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
}

View File

@@ -1,24 +1,59 @@
export default `
precision highp float;
precision highp int;
precision highp sampler2D;
// input texture (previous level used to evaluate the new level)
uniform sampler2D tPreviousLevel;
uniform sampler2D tInputLevel;
// 1/size of the previous level texture.
// previous level used to evaluate the new level
#if __VERSION__ == 100
uniform sampler2D tPreviousLevel;
#else
precision highp isampler2D;
uniform isampler2D tPreviousLevel;
#endif
// inverted size of the previous level texture.
uniform float uSize;
uniform float uTexSize;
uniform bool uFirst;
#include common
void main(void) {
float k = 0.5 * uSize;
vec2 position = floor((gl_FragCoord.xy / uTexSize) / uSize) * uSize;
float a = texture2D(tPreviousLevel, position).r;
float b = texture2D(tPreviousLevel, position + vec2(k, 0.)).r;
float c = texture2D(tPreviousLevel, position + vec2(0., k)).r;
float d = texture2D(tPreviousLevel, position + vec2(k, k)).r;
gl_FragColor.a = a;
gl_FragColor.b = a + b;
gl_FragColor.g = gl_FragColor.b + c;
gl_FragColor.r = gl_FragColor.g + d;
#if __VERSION__ == 100
float a, b, c, d;
if (uFirst) {
a = texture2D(tInputLevel, position).r * 255.0;
b = texture2D(tInputLevel, position + vec2(k, 0.0)).r * 255.0;
c = texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0;
d = texture2D(tInputLevel, position + vec2(k, k)).r * 255.0;
} else {
a = decodeFloatRGB(texture2D(tPreviousLevel, position).rgb);
b = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
c = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
d = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
}
gl_FragColor = vec4(encodeFloatRGB(a + b + c + d), 1.0);
#else
int a, b, c, d;
if (uFirst) {
a = int(texture2D(tInputLevel, position).r * 255.0);
b = int(texture2D(tInputLevel, position + vec2(k, 0.0)).r * 255.0);
c = int(texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0);
d = int(texture2D(tInputLevel, position + vec2(k, k)).r * 255.0);
} else {
a = texture2D(tPreviousLevel, position).r;
b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r;
c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r;
d = texture2D(tPreviousLevel, position + vec2(k, k)).r;
}
gl_FragColor = ivec4(a + b + c + d);
#endif
}
`;

View File

@@ -1,18 +1,26 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
export default `
precision highp float;
precision highp sampler2D;
precision highp int;
uniform sampler2D tTexture;
#include common
#if __VERSION__ == 100
precision highp sampler2D;
uniform sampler2D tTexture;
#else
precision highp isampler2D;
uniform isampler2D tTexture;
#endif
void main(void) {
gl_FragColor = vec4(encodeFloatRGB(texture2D(tTexture, vec2(0.5)).r), 1.0);
#if __VERSION__ == 100
gl_FragColor = texture2D(tTexture, vec2(0.5));
#else
gl_FragColor = ivec4(texture2D(tTexture, vec2(0.5)).r);
#endif
}
`;

View File

@@ -10,6 +10,7 @@ precision highp int;
#include common
#include read_from_texture
#include common_frag_params
#include common_clip
#include wboit_params
uniform vec2 uImageTexDim;
@@ -88,6 +89,8 @@ varying float vInstance;
#endif
void main() {
#include clip_pixel
#if defined(dInterpolation_cubic)
vec4 imageData = biCubic(tImageTex, vUv);
#else
@@ -96,6 +99,9 @@ void main() {
imageData.a = clamp(imageData.a, 0.0, 1.0);
if (imageData.a > 0.9) imageData.a = 1.0;
float fragmentDepth = gl_FragCoord.z;
bool interior = false;
#if defined(dRenderVariant_pick)
if (imageData.a < 0.3)
discard;
@@ -121,11 +127,9 @@ void main() {
float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
#include apply_marker_color
#include apply_fog
float fragmentDepth = gl_FragCoord.z;
bool interior = false;
#include wboit_write
#endif
}

View File

@@ -16,6 +16,9 @@ precision highp int;
void main(){
#include clip_pixel
bool interior = false;
float fragmentDepth = gl_FragCoord.z;
#include assign_material_color
#if defined(dRenderVariant_pick)
@@ -28,9 +31,6 @@ void main(){
#include apply_marker_color
#include apply_fog
float fragmentDepth = gl_FragCoord.z;
bool interior = false;
#include wboit_write
#endif
}

View File

@@ -20,7 +20,6 @@ precision highp int;
uniform float uPixelRatio;
uniform float uViewportHeight;
attribute vec3 aPosition;
attribute mat4 aTransform;
attribute float aInstance;
attribute float aGroup;

Some files were not shown because too many files have changed in this diff Show More