mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89a6102f8d | ||
|
|
163929477e | ||
|
|
c10a8369e8 | ||
|
|
8fbba52de8 | ||
|
|
ca3174b2c3 | ||
|
|
b9864fba80 | ||
|
|
f8e9bc1e7f | ||
|
|
f79f1507f7 | ||
|
|
61ab205a5d | ||
|
|
2c65260a4f | ||
|
|
0597a1ef24 | ||
|
|
8d6557e51c | ||
|
|
5cff0dff3d | ||
|
|
93206e76d7 | ||
|
|
40933a8539 | ||
|
|
989800783b | ||
|
|
d83b0d2c4d | ||
|
|
5e5d5a63dc | ||
|
|
b1755604e2 | ||
|
|
e58da9b574 | ||
|
|
f5d6498601 | ||
|
|
07f351888f | ||
|
|
4588fdd5d5 | ||
|
|
c3b32baf6a | ||
|
|
b8d60cea9b | ||
|
|
25b8956712 | ||
|
|
7015309db6 | ||
|
|
aad861db37 | ||
|
|
ae7811705d | ||
|
|
7e26dac50b | ||
|
|
75f43d038c | ||
|
|
b9ba940510 | ||
|
|
35603baaaa | ||
|
|
19dc32c491 | ||
|
|
95997e6a61 | ||
|
|
03e19a2ad7 | ||
|
|
765b133369 | ||
|
|
703e729514 | ||
|
|
b0216c4ce6 | ||
|
|
6796fc1cd4 | ||
|
|
87c504f9a8 | ||
|
|
2e770cb733 | ||
|
|
9f440f68e0 | ||
|
|
40028b27ba | ||
|
|
4676ad8738 | ||
|
|
e1c7833826 | ||
|
|
dd1bca0fee | ||
|
|
c38ab2c638 | ||
|
|
459c5aa5a7 | ||
|
|
b8bf07d393 | ||
|
|
ea87ac2094 | ||
|
|
e1b830a59d | ||
|
|
41e1ac76c0 | ||
|
|
98b118fd1e | ||
|
|
5f691913e4 | ||
|
|
26e2516097 | ||
|
|
3d2e4115ed | ||
|
|
dbce1ccb3d | ||
|
|
03aa2be978 | ||
|
|
8dfc52e1ab | ||
|
|
6058179f10 | ||
|
|
ea9e25b03c | ||
|
|
d60c3ddce3 | ||
|
|
724e79bddf | ||
|
|
2de61215c4 | ||
|
|
e783d9a9f1 | ||
|
|
e9e971d4f3 | ||
|
|
96dea14cb1 | ||
|
|
04fc157340 | ||
|
|
cfc24fa99e | ||
|
|
19c1088209 | ||
|
|
ee6c2e0841 | ||
|
|
20ee659b00 | ||
|
|
b6514a4a50 | ||
|
|
07b8bdb951 | ||
|
|
afd18cabd4 | ||
|
|
1117ce05d5 | ||
|
|
fc15e952bf | ||
|
|
249e5a3e0b | ||
|
|
4bfe3f6bde | ||
|
|
75b7e0b4d9 | ||
|
|
ee4ce2fd7a | ||
|
|
db0aa12e75 | ||
|
|
6d2578d3d0 | ||
|
|
99d61f48b4 | ||
|
|
146022dc12 | ||
|
|
92730cad01 | ||
|
|
d6b68b06da | ||
|
|
b174fbf0c6 | ||
|
|
fde1557955 | ||
|
|
24a0753881 | ||
|
|
5664e1d8be | ||
|
|
4881a41256 | ||
|
|
235e41ee03 | ||
|
|
94d293a4d3 | ||
|
|
40f1ca207f | ||
|
|
926fb38c1e | ||
|
|
5a14fcabc5 | ||
|
|
560e40773f | ||
|
|
6561732f57 | ||
|
|
b45cf206fd | ||
|
|
70e07be64d | ||
|
|
f3013f0e46 | ||
|
|
2e7041bd78 | ||
|
|
5d0447c9bb | ||
|
|
9eba0b91a8 | ||
|
|
58bc6722a9 | ||
|
|
1acfed3233 | ||
|
|
8147b3aa34 | ||
|
|
b21552ff36 | ||
|
|
c683cbe962 | ||
|
|
bd270e4428 | ||
|
|
23d942d8a5 | ||
|
|
cbcd6b99d2 | ||
|
|
ee5c098a9f | ||
|
|
070a15d679 | ||
|
|
befa5174f8 | ||
|
|
d6c4366f40 | ||
|
|
181cfefa63 | ||
|
|
0e7c885961 | ||
|
|
d58e90d93f | ||
|
|
cd872b47e6 | ||
|
|
2683c5b318 | ||
|
|
c71f60a164 | ||
|
|
881cbc1947 | ||
|
|
f3e7febbd1 | ||
|
|
e68ad13031 | ||
|
|
7fbbe1e63a | ||
|
|
a5ca72af3c | ||
|
|
1ce6641eb3 | ||
|
|
5dc413ab8c | ||
|
|
50b615e86c | ||
|
|
5b4c6743e7 |
@@ -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
25493
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
77
package.json
77
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.13",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
} }
|
||||
}
|
||||
} });
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)}`;
|
||||
|
||||
@@ -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).`
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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.');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -4,165 +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;
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -213,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
|
||||
}
|
||||
@@ -249,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++) {
|
||||
@@ -258,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;
|
||||
@@ -274,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++) {
|
||||
@@ -319,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);
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -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');
|
||||
|
||||
@@ -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' });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,6 +522,13 @@ namespace Canvas3D {
|
||||
materialId: r.materialId,
|
||||
})));
|
||||
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 +613,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 +674,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,13 +688,16 @@ namespace Canvas3D {
|
||||
get notifyDidDraw() { return notifyDidDraw; },
|
||||
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
|
||||
didDraw,
|
||||
commited,
|
||||
reprCount,
|
||||
resized,
|
||||
setProps: (properties, doNotRequestDraw = false) => {
|
||||
const props: PartialCanvas3DProps = typeof properties === 'function'
|
||||
let props: PartialCanvas3DProps = typeof properties === 'function'
|
||||
? produce(getProps(), properties)
|
||||
: properties;
|
||||
|
||||
props = PD.normalizeParams(Canvas3DParams, props, 'children');
|
||||
|
||||
const cameraState: Partial<Camera.Snapshot> = Object.create(null);
|
||||
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
|
||||
cameraState.mode = props.camera.mode;
|
||||
@@ -688,7 +772,6 @@ namespace Canvas3D {
|
||||
|
||||
scene.clear();
|
||||
helper.debug.clear();
|
||||
input.dispose();
|
||||
controls.dispose();
|
||||
renderer.dispose();
|
||||
interactionHelper.dispose();
|
||||
|
||||
@@ -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, { visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false, noClip: false }, materialId);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
130
src/mol-canvas3d/passes/fxaa.ts
Normal file
130
src/mol-canvas3d/passes/fxaa.ts
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) || props.postprocessing.antialiasing.name === 'on';
|
||||
|
||||
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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,28 +189,26 @@ 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 {
|
||||
const width = colorTexture.getWidth();
|
||||
const height = colorTexture.getHeight();
|
||||
|
||||
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),
|
||||
uTexSize: ValueCell.create(Vec2.create(width, height)),
|
||||
tDepth: ValueCell.create(depthTexture),
|
||||
tOutlines: ValueCell.create(outlinesTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
|
||||
|
||||
dOrthographic: ValueCell.create(0),
|
||||
uNear: ValueCell.create(1),
|
||||
@@ -63,48 +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(64, {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(20, { 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.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
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: 32, step: 1 }, { description: 'Number of edge exploration steps.' }),
|
||||
subpixelQuality: PD.Numeric(1.00, { min: 0.00, max: 1.00, step: 0.01 }, { description: 'Choose the amount of sub-pixel aliasing removal.' }),
|
||||
}),
|
||||
antialiasing: PD.MappedStatic('smaa', {
|
||||
fxaa: PD.Group(FxaaParams),
|
||||
smaa: PD.Group(SmaaParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Fast Approximate Anti-Aliasing (FXAA)' }),
|
||||
}, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }),
|
||||
};
|
||||
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
|
||||
|
||||
@@ -115,38 +266,179 @@ 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
|
||||
|
||||
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
|
||||
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
|
||||
|
||||
constructor(private webgl: WebGLContext, 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, 'uint8', 'linear');
|
||||
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();
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthTexture.define(width, height);
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthBlurProxyTexture.define(width, height);
|
||||
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) {
|
||||
this.target.setSize(width, height);
|
||||
this.tmpTarget.setSize(width, height);
|
||||
this.outlinesTarget.setSize(width, height);
|
||||
this.ssaoDepthTexture.define(width, height);
|
||||
this.ssaoDepthBlurProxyTexture.define(width, height);
|
||||
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.fxaa.values.uTexSizeInv, Vec2.set(this.fxaa.values.uTexSizeInv.ref.value, 1 / width, 1 / 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, width, height));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -154,86 +446,25 @@ 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') {
|
||||
this.ssaoFramebuffer.bind();
|
||||
this.ssaoRenderable.render();
|
||||
|
||||
if (props.antialiasing.name === 'on') {
|
||||
this.tmpTarget.bind();
|
||||
} else if (toDrawingBuffer) {
|
||||
this.webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.target.bind();
|
||||
}
|
||||
this.ssaoBlurFirstPassFramebuffer.bind();
|
||||
this.ssaoBlurFirstPassRenderable.render();
|
||||
|
||||
this.updateState(camera);
|
||||
this.renderable.render();
|
||||
}
|
||||
|
||||
private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) {
|
||||
if (props.antialiasing.name === 'off') return;
|
||||
|
||||
const { values } = this.fxaa;
|
||||
|
||||
let needsUpdate = false;
|
||||
|
||||
const input = (props.occlusion.name === 'on' || props.outline.name === 'on')
|
||||
? this.tmpTarget.texture : this.drawPass.colorTarget.texture;
|
||||
if (values.tColor.ref.value !== input) {
|
||||
ValueCell.update(this.fxaa.values.tColor, input);
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
const { edgeThresholdMin, edgeThresholdMax, iterations, subpixelQuality } = props.antialiasing.params;
|
||||
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.fxaa.update();
|
||||
this.ssaoBlurSecondPassFramebuffer.bind();
|
||||
this.ssaoBlurSecondPassRenderable.render();
|
||||
}
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
@@ -242,62 +473,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.name === 'off') {
|
||||
this._renderPostprocessing(camera, toDrawingBuffer, props);
|
||||
}
|
||||
readonly target: RenderTarget
|
||||
private readonly fxaa: FxaaPass
|
||||
private readonly smaa: SmaaPass
|
||||
|
||||
if (props.antialiasing.name === 'on') {
|
||||
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'),
|
||||
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.75),
|
||||
};
|
||||
|
||||
const schema = { ...FxaaSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', FxaaShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
255
src/mol-canvas3d/passes/smaa.ts
Normal file
255
src/mol-canvas3d/passes/smaa.ts
Normal file
File diff suppressed because one or more lines are too long
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,7 @@ export namespace BaseGeometry {
|
||||
colorOnly: false,
|
||||
opaque,
|
||||
writeDepth: opaque,
|
||||
noClip: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
102
src/mol-geo/geometry/cylinders/cylinders-builder.ts
Normal file
102
src/mol-geo/geometry/cylinders/cylinders-builder.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
278
src/mol-geo/geometry/cylinders/cylinders.ts
Normal file
278
src/mol-geo/geometry/cylinders/cylinders.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,6 +82,7 @@ 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':
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -102,7 +104,8 @@ export namespace TextureMesh {
|
||||
|
||||
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount / 3, 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,
|
||||
@@ -111,9 +114,9 @@ export namespace TextureMesh {
|
||||
|
||||
// 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 +129,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,11 +142,12 @@ 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);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
|
||||
|
||||
if (values.drawCount.ref.value > values.aGroup.ref.value.length) {
|
||||
// console.log('updating vertex ids in aGroup to handle larger drawCount')
|
||||
@@ -150,8 +156,9 @@ export namespace TextureMesh {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ function createPoints() {
|
||||
pickable: true,
|
||||
colorOnly: false,
|
||||
opaque: true,
|
||||
writeDepth: true
|
||||
writeDepth: true,
|
||||
noClip: false,
|
||||
};
|
||||
|
||||
return createRenderObject('points', values, state, -1);
|
||||
|
||||
223
src/mol-gl/compute/grid3d.ts
Normal file
223
src/mol-gl/compute/grid3d.ts
Normal 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);
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* 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 { createComputeRenderable } 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';
|
||||
@@ -24,39 +24,48 @@ const HistopyramidReductionSchema = {
|
||||
tPreviousLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uSize: UniformSpec('f'),
|
||||
uTexSize: UniformSpec('f'),
|
||||
uFirst: UniformSpec('b'),
|
||||
};
|
||||
|
||||
let HistopyramidReductionRenderable: ComputeRenderable<Values<typeof HistopyramidReductionSchema>>;
|
||||
const HistogramPyramidName = 'histogram-pyramid';
|
||||
|
||||
function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
|
||||
if (HistopyramidReductionRenderable) {
|
||||
ValueCell.update(HistopyramidReductionRenderable.values.tPreviousLevel, initialTexture);
|
||||
HistopyramidReductionRenderable.update();
|
||||
return HistopyramidReductionRenderable;
|
||||
if (ctx.namedComputeRenderables[HistogramPyramidName]) {
|
||||
const v = ctx.namedComputeRenderables[HistogramPyramidName].values;
|
||||
|
||||
ValueCell.update(v.tPreviousLevel, initialTexture);
|
||||
|
||||
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, initialTexture);
|
||||
}
|
||||
return ctx.namedComputeRenderables[HistogramPyramidName];
|
||||
}
|
||||
|
||||
function createHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
|
||||
const values: Values<typeof HistopyramidReductionSchema> = {
|
||||
...QuadValues,
|
||||
tPreviousLevel: ValueCell.create(initialTexture),
|
||||
uSize: ValueCell.create(0),
|
||||
uTexSize: ValueCell.create(0),
|
||||
uFirst: ValueCell.create(true),
|
||||
};
|
||||
|
||||
const schema = { ...HistopyramidReductionSchema };
|
||||
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
|
||||
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 = getTexture(`level${level}`, ctx, 'image-float32', 'rgba', 'float', 'nearest');
|
||||
const framebuffer = getFramebuffer(`level${level}`, ctx);
|
||||
texture.attachFramebuffer(framebuffer, 0);
|
||||
textureFramebuffer = { texture, framebuffer };
|
||||
textureFramebuffer.texture.define(size, size);
|
||||
@@ -70,12 +79,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,24 +109,27 @@ 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)
|
||||
// console.log('levels', levels, 'maxSize', maxSize, 'input', w);
|
||||
|
||||
const pyramidTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
pyramidTexture.define(maxSize, maxSize);
|
||||
const pyramidTex = getTexture('pyramid', ctx, 'image-float32', 'rgba', 'float', 'nearest');
|
||||
pyramidTex.define(maxSize, maxSize);
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
pyramidTexture.attachFramebuffer(framebuffer, 0);
|
||||
const framebuffer = getFramebuffer('pyramid', ctx);
|
||||
pyramidTex.attachFramebuffer(framebuffer, 0);
|
||||
gl.viewport(0, 0, maxSize, maxSize);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
const levelTexturesFramebuffers: TextureFramebuffer[] = [];
|
||||
@@ -116,46 +144,42 @@ 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);
|
||||
|
||||
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);
|
||||
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', finalCount, 'scale', scale);
|
||||
|
||||
|
||||
return {
|
||||
pyramidTex: pyramidTexture,
|
||||
count: finalCount,
|
||||
height,
|
||||
levels,
|
||||
scale
|
||||
};
|
||||
return { pyramidTex, count, height, levels, scale };
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createComputeRenderable, ComputeRenderable } from '../../renderable';
|
||||
import { createComputeRenderable } from '../../renderable';
|
||||
import { WebGLContext } from '../../webgl/context';
|
||||
import { createComputeRenderItem } from '../../webgl/render-item';
|
||||
import { Values, TextureSpec } from '../../renderable/schema';
|
||||
@@ -21,33 +21,32 @@ const HistopyramidSumSchema = {
|
||||
tTexture: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
};
|
||||
|
||||
let HistopyramidSumRenderable: ComputeRenderable<Values<typeof HistopyramidSumSchema>>;
|
||||
const HistopyramidSumName = 'histopyramid-sum';
|
||||
|
||||
function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
if (HistopyramidSumRenderable) {
|
||||
ValueCell.update(HistopyramidSumRenderable.values.tTexture, texture);
|
||||
HistopyramidSumRenderable.update();
|
||||
return HistopyramidSumRenderable;
|
||||
if (ctx.namedComputeRenderables[HistopyramidSumName]) {
|
||||
const v = ctx.namedComputeRenderables[HistopyramidSumName].values;
|
||||
|
||||
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: 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);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
function setRenderingDefaults(ctx: WebGLContext) {
|
||||
@@ -68,8 +67,16 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
|
||||
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] = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
ctx.namedTextures[HistopyramidSumName].define(1, 1);
|
||||
}
|
||||
const sumTexture = ctx.namedTextures[HistopyramidSumName];
|
||||
sumTexture.attachFramebuffer(framebuffer, 0);
|
||||
|
||||
setRenderingDefaults(ctx);
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -30,18 +30,36 @@ const ActiveVoxelsSchema = {
|
||||
uScale: UniformSpec('v2'),
|
||||
};
|
||||
|
||||
const ActiveVoxelsName = 'active-voxels';
|
||||
|
||||
function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
|
||||
if (ctx.namedComputeRenderables[ActiveVoxelsName]) {
|
||||
const v = ctx.namedComputeRenderables[ActiveVoxelsName].values;
|
||||
|
||||
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: Values<typeof ActiveVoxelsSchema> = {
|
||||
...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 +75,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 +86,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 +104,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();
|
||||
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -38,18 +38,44 @@ const IsosurfaceSchema = {
|
||||
uScale: UniformSpec('v2'),
|
||||
};
|
||||
|
||||
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, height: number) {
|
||||
if (ctx.namedComputeRenderables[IsosurfaceName]) {
|
||||
const v = ctx.namedComputeRenderables[IsosurfaceName].values;
|
||||
|
||||
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);
|
||||
|
||||
ctx.namedComputeRenderables[IsosurfaceName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
|
||||
}
|
||||
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, height: number) {
|
||||
// console.log('uSize', Math.pow(2, levels))
|
||||
const values: Values<typeof IsosurfaceSchema> = {
|
||||
...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,7 +83,6 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
uGridDim: ValueCell.create(gridDim),
|
||||
uGridTexDim: ValueCell.create(gridTexDim),
|
||||
uGridTransform: ValueCell.create(transform),
|
||||
|
||||
uScale: ValueCell.create(scale),
|
||||
};
|
||||
|
||||
@@ -82,31 +107,25 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
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;
|
||||
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)
|
||||
|
||||
const framebuffer = resources.framebuffer();
|
||||
|
||||
let needsClear = false;
|
||||
if (!ctx.namedFramebuffers[IsosurfaceName]) {
|
||||
ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
|
||||
}
|
||||
const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
|
||||
|
||||
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());
|
||||
} else {
|
||||
needsClear = true;
|
||||
}
|
||||
vertexGroupTexture.define(width, height);
|
||||
|
||||
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;
|
||||
}
|
||||
normalTexture.define(width, height);
|
||||
|
||||
// const infoTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
|
||||
// infoTex.define(pyramidTex.width, pyramidTex.height)
|
||||
@@ -147,11 +166,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
]);
|
||||
|
||||
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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -21,6 +21,7 @@ export type RenderableState = {
|
||||
colorOnly: boolean
|
||||
opaque: boolean
|
||||
writeDepth: boolean
|
||||
noClip: boolean
|
||||
}
|
||||
|
||||
export interface Renderable<T extends RenderableValues> {
|
||||
|
||||
40
src/mol-gl/renderable/cylinders.ts
Normal file
40
src/mol-gl/renderable/cylinders.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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,
|
||||
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);
|
||||
}
|
||||
@@ -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[]'),
|
||||
|
||||
@@ -22,6 +22,8 @@ export const TextureMeshSchema = {
|
||||
dFlatShaded: DefineSpec('boolean'),
|
||||
dDoubleSided: DefineSpec('boolean'),
|
||||
dFlipSided: DefineSpec('boolean'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('boolean'),
|
||||
dGeoTexture: DefineSpec('boolean'),
|
||||
};
|
||||
export type TextureMeshSchema = typeof TextureMeshSchema
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -94,10 +96,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 +119,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 +145,7 @@ type Clip = {
|
||||
objects: {
|
||||
count: number
|
||||
type: number[]
|
||||
invert: boolean[]
|
||||
position: number[]
|
||||
rotation: number[]
|
||||
scale: number[]
|
||||
@@ -150,8 +154,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 +164,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 +236,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),
|
||||
@@ -261,13 +268,20 @@ namespace Renderer {
|
||||
}
|
||||
|
||||
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 +465,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 +474,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 +513,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 +529,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 +568,8 @@ namespace Renderer {
|
||||
renderBlended,
|
||||
renderBlendedOpaque,
|
||||
renderBlendedTransparent,
|
||||
renderBlendedVolume,
|
||||
renderBlendedVolumeOpaque,
|
||||
renderBlendedVolumeTransparent,
|
||||
renderWboitOpaque,
|
||||
renderWboitTransparent,
|
||||
|
||||
@@ -598,9 +644,7 @@ namespace Renderer {
|
||||
}
|
||||
},
|
||||
|
||||
get props() {
|
||||
return p;
|
||||
},
|
||||
props: p,
|
||||
get stats(): RendererStats {
|
||||
return {
|
||||
programCount: ctx.stats.resourceCounts.program,
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -51,10 +51,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 +85,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,
|
||||
@@ -117,6 +121,8 @@ export function ShaderCode(name: string, vert: string, frag: string, extensions:
|
||||
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions };
|
||||
}
|
||||
|
||||
// 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 +131,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' });
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,7 +12,7 @@ export default `
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_vertex) || defined(dColorType_vertexInstance)
|
||||
#if __VERSION__ != 300
|
||||
#if __VERSION__ == 100
|
||||
attribute float aVertex;
|
||||
#else
|
||||
#define aVertex float(gl_VertexID)
|
||||
@@ -25,7 +25,7 @@ export default `
|
||||
uniform sampler2D tOverpaint;
|
||||
#endif
|
||||
#elif defined(dRenderVariant_pick)
|
||||
#if __VERSION__ != 300
|
||||
#if __VERSION__ == 100
|
||||
varying vec4 vColor;
|
||||
#else
|
||||
flat out vec4 vColor;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -26,9 +26,10 @@ 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)); }
|
||||
|
||||
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 +50,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 +91,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) {
|
||||
|
||||
46
src/mol-gl/shader/chunks/float-to-rgba.glsl.ts
Normal file
46
src/mol-gl/shader/chunks/float-to-rgba.glsl.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
`;
|
||||
@@ -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 Disney’s 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);
|
||||
}
|
||||
`;
|
||||
91
src/mol-gl/shader/chunks/rgba-to-float.glsl.ts
Normal file
91
src/mol-gl/shader/chunks/rgba-to-float.glsl.ts
Normal 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);
|
||||
}
|
||||
`;
|
||||
@@ -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) {
|
||||
|
||||
12
src/mol-gl/shader/copy.frag.ts
Normal file
12
src/mol-gl/shader/copy.frag.ts
Normal 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);
|
||||
}
|
||||
`;
|
||||
139
src/mol-gl/shader/cylinders.frag.ts
Normal file
139
src/mol-gl/shader/cylinders.frag.ts
Normal 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
|
||||
}
|
||||
`;
|
||||
74
src/mol-gl/shader/cylinders.vert.ts
Normal file
74
src/mol-gl/shader/cylinders.vert.ts
Normal 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
|
||||
}
|
||||
`;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -5,17 +5,28 @@ precision highp sampler2D;
|
||||
// input texture (previous level used to evaluate the new level)
|
||||
uniform sampler2D tPreviousLevel;
|
||||
|
||||
// 1/size of the previous level texture.
|
||||
// inverted size of the previous level texture.
|
||||
uniform float uSize;
|
||||
uniform float uTexSize;
|
||||
uniform bool uFirst;
|
||||
|
||||
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;
|
||||
float a, b, c, d;
|
||||
|
||||
if (uFirst) {
|
||||
a = texture2D(tPreviousLevel, position).r * 255.0;
|
||||
b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r * 255.0;
|
||||
c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r * 255.0;
|
||||
d = texture2D(tPreviousLevel, 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.a = a;
|
||||
gl_FragColor.b = a + b;
|
||||
gl_FragColor.g = gl_FragColor.b + c;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tTriCount;
|
||||
@@ -10,8 +11,9 @@ uniform vec3 uGridDim;
|
||||
uniform vec3 uGridTexDim;
|
||||
uniform vec2 uScale;
|
||||
|
||||
// cube corners
|
||||
const vec3 c0 = vec3(0., 0., 0.);
|
||||
#include common
|
||||
|
||||
// cube corners (excluding origin)
|
||||
const vec3 c1 = vec3(1., 0., 0.);
|
||||
const vec3 c2 = vec3(1., 1., 0.);
|
||||
const vec3 c3 = vec3(0., 1., 0.);
|
||||
@@ -22,27 +24,23 @@ const vec3 c7 = vec3(0., 1., 1.);
|
||||
|
||||
vec3 index3dFrom2d(vec2 coord) {
|
||||
vec2 gridTexPos = coord * uGridTexDim.xy;
|
||||
vec2 columnRow = floor(gridTexPos / uGridDim.xy);
|
||||
vec2 columnRow = ivec2Div(gridTexPos, uGridDim.xy);
|
||||
vec2 posXY = gridTexPos - columnRow * uGridDim.xy;
|
||||
float posZ = columnRow.y * floor(uGridTexDim.x / uGridDim.x) + columnRow.x;
|
||||
vec3 posXYZ = vec3(posXY, posZ) / uGridDim;
|
||||
return posXYZ;
|
||||
float posZ = columnRow.y * intDiv(uGridTexDim.x, uGridDim.x) + columnRow.x;
|
||||
return vec3(posXY, posZ);
|
||||
}
|
||||
|
||||
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)); }
|
||||
|
||||
vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
|
||||
float zSlice = floor(pos.z * gridDim.z + 0.5); // round to nearest z-slice
|
||||
float column = intMod(zSlice * gridDim.x, texDim.x) / gridDim.x;
|
||||
float row = floor(intDiv(zSlice * gridDim.x, texDim.x));
|
||||
float column = intDiv(intMod(zSlice * gridDim.x, texDim.x), gridDim.x);
|
||||
float row = intDiv(zSlice * gridDim.x, texDim.x);
|
||||
vec2 coord = (vec2(column * gridDim.x, row * gridDim.y) + (pos.xy * gridDim.xy)) / (texDim / uScale);
|
||||
// return texture2D(tex, coord + 0.5 / texDim);
|
||||
return texture2D(tex, coord);
|
||||
}
|
||||
|
||||
vec4 voxel(vec3 pos) {
|
||||
return texture3dFrom2dNearest(tVolumeData, pos, uGridDim, uGridTexDim.xy);
|
||||
pos = min(max(vec3(0.0), pos), uGridDim - vec3(1.0));
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
@@ -51,27 +49,23 @@ void main(void) {
|
||||
|
||||
// get MC case as the sum of corners that are below the given iso level
|
||||
float c = step(voxel(posXYZ).a, uIsoValue)
|
||||
+ 2. * step(voxel(posXYZ + c1 / uGridDim).a, uIsoValue)
|
||||
+ 4. * step(voxel(posXYZ + c2 / uGridDim).a, uIsoValue)
|
||||
+ 8. * step(voxel(posXYZ + c3 / uGridDim).a, uIsoValue)
|
||||
+ 16. * step(voxel(posXYZ + c4 / uGridDim).a, uIsoValue)
|
||||
+ 32. * step(voxel(posXYZ + c5 / uGridDim).a, uIsoValue)
|
||||
+ 64. * step(voxel(posXYZ + c6 / uGridDim).a, uIsoValue)
|
||||
+ 128. * step(voxel(posXYZ + c7 / uGridDim).a, uIsoValue);
|
||||
+ 2. * step(voxel(posXYZ + c1).a, uIsoValue)
|
||||
+ 4. * step(voxel(posXYZ + c2).a, uIsoValue)
|
||||
+ 8. * step(voxel(posXYZ + c3).a, uIsoValue)
|
||||
+ 16. * step(voxel(posXYZ + c4).a, uIsoValue)
|
||||
+ 32. * step(voxel(posXYZ + c5).a, uIsoValue)
|
||||
+ 64. * step(voxel(posXYZ + c6).a, uIsoValue)
|
||||
+ 128. * step(voxel(posXYZ + c7).a, uIsoValue);
|
||||
c *= step(c, 254.);
|
||||
|
||||
// handle out of bounds positions
|
||||
posXYZ += 1.0;
|
||||
posXYZ.xy += 1.0; // pixel padding (usually ok even if the texture has no padding)
|
||||
if (posXYZ.x >= uGridDim.x || posXYZ.y >= uGridDim.y || posXYZ.z >= uGridDim.z)
|
||||
c = 0.0;
|
||||
|
||||
// get total triangles to generate for calculated MC case from triCount texture
|
||||
float totalTrianglesToGenerate = texture2D(tTriCount, vec2(intMod(c, 16.), floor(c / 16.)) / 16.).a;
|
||||
gl_FragColor = vec4(vec3(floor(totalTrianglesToGenerate * 255.0 + 0.5) * 3.0), c);
|
||||
|
||||
// gl_FragColor = vec4(255.0, 0.0, 0.0, voxel(posXYZ + c4 / uGridDim).a * 255.0);
|
||||
// gl_FragColor = vec4(255.0, 0.0, 0.0, voxel(posXYZ).a * 255.0);
|
||||
|
||||
// vec2 uv = vCoordinate;
|
||||
// uv = gl_FragCoord.xy / uGridTexDim.xy;
|
||||
|
||||
// if (uv.y < 0.91) discard;
|
||||
// gl_FragColor = vec4(vCoordinate * 255.0, 0.0, 255.0);
|
||||
// gl_FragColor = vec4(250.0, 0.0, 0.0, 255.0);
|
||||
gl_FragColor = vec4(vec3(totalTrianglesToGenerate * 3.0), c / 255.0);
|
||||
}
|
||||
`;
|
||||
@@ -1,5 +1,6 @@
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tActiveVoxelsPyramid;
|
||||
@@ -19,12 +20,9 @@ uniform mat4 uGridTransform;
|
||||
// scale to volume data coord
|
||||
uniform vec2 uScale;
|
||||
|
||||
// varying vec2 vCoordinate;
|
||||
|
||||
#include common
|
||||
|
||||
// cube corners
|
||||
const vec3 c0 = vec3(0., 0., 0.);
|
||||
// cube corners (excluding origin)
|
||||
const vec3 c1 = vec3(1., 0., 0.);
|
||||
const vec3 c2 = vec3(1., 1., 0.);
|
||||
const vec3 c3 = vec3(0., 1., 0.);
|
||||
@@ -33,28 +31,30 @@ const vec3 c5 = vec3(1., 0., 1.);
|
||||
const vec3 c6 = vec3(1., 1., 1.);
|
||||
const vec3 c7 = vec3(0., 1., 1.);
|
||||
|
||||
const float EPS = 0.00001;
|
||||
|
||||
vec3 index3dFrom2d(vec2 coord) {
|
||||
vec2 gridTexPos = coord * uGridTexDim.xy;
|
||||
vec2 columnRow = floor(gridTexPos / uGridDim.xy);
|
||||
vec2 columnRow = ivec2Div(gridTexPos, uGridDim.xy);
|
||||
vec2 posXY = gridTexPos - columnRow * uGridDim.xy;
|
||||
float posZ = columnRow.y * floor(uGridTexDim.x / uGridDim.x) + columnRow.x;
|
||||
vec3 posXYZ = vec3(posXY, posZ);
|
||||
return posXYZ;
|
||||
float posZ = columnRow.y * intDiv(uGridTexDim.x, uGridDim.x) + columnRow.x;
|
||||
return vec3(posXY, posZ);
|
||||
}
|
||||
|
||||
vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
|
||||
float zSlice = floor(pos.z * gridDim.z + 0.5); // round to nearest z-slice
|
||||
float column = intMod(zSlice * gridDim.x, texDim.x) / gridDim.x;
|
||||
float row = floor(intDiv(zSlice * gridDim.x, texDim.x));
|
||||
float column = intDiv(intMod(zSlice * gridDim.x, texDim.x), gridDim.x);
|
||||
float row = intDiv(zSlice * gridDim.x, texDim.x);
|
||||
vec2 coord = (vec2(column * gridDim.x, row * gridDim.y) + (pos.xy * gridDim.xy)) / (texDim / uScale);
|
||||
return texture2D(tex, coord + 0.5 / (texDim / uScale));
|
||||
// return texture2D(tex, coord);
|
||||
}
|
||||
|
||||
vec4 voxel(vec3 pos) {
|
||||
return texture3dFrom2dNearest(tVolumeData, pos, uGridDim, uGridTexDim.xy);
|
||||
pos = min(max(vec3(0.0), pos), uGridDim - vec3(1.0));
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
}
|
||||
|
||||
vec4 voxel2(vec3 pos) {
|
||||
pos = min(max(vec3(0.0), pos), uGridDim - vec3(vec2(2.0), 1.0));
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
@@ -78,7 +78,7 @@ void main(void) {
|
||||
vec4 vI4 = vec4(vI);
|
||||
|
||||
// traverse the different levels of the pyramid
|
||||
for(int i = 1; i < 12; i++) {
|
||||
for(int i = 1; i < 14; i++) {
|
||||
if(float(i) >= uLevels) break;
|
||||
|
||||
offset -= diff;
|
||||
@@ -103,7 +103,7 @@ void main(void) {
|
||||
vec2 coord2d = position / uScale;
|
||||
vec3 coord3d = floor(index3dFrom2d(coord2d) + 0.5);
|
||||
|
||||
float edgeIndex = floor(texture2D(tActiveVoxelsBase, position).a + 0.5);
|
||||
float edgeIndex = floor(texture2D(tActiveVoxelsBase, position).a * 255.0 + 0.5);
|
||||
|
||||
// current vertex for the up to 15 MC cases
|
||||
float currentVertex = vI - dot(m, starts);
|
||||
@@ -149,13 +149,13 @@ void main(void) {
|
||||
// vec3 b0 = coord3d;
|
||||
// vec3 b1 = coord3d;
|
||||
// if (mcIndex == 0.0) {
|
||||
// b0 += c0; b1 += c1;
|
||||
// b1 += c1;
|
||||
// } else if (mcIndex == 1.0) {
|
||||
// b0 += c1; b1 += c2;
|
||||
// } else if (mcIndex == 2.0) {
|
||||
// b0 += c2; b1 += c3;
|
||||
// } else if (mcIndex == 3.0) {
|
||||
// b0 += c3; b1 += c0;
|
||||
// b0 += c3;
|
||||
// } else if (mcIndex == 4.0) {
|
||||
// b0 += c4; b1 += c5;
|
||||
// } else if (mcIndex == 5.0) {
|
||||
@@ -165,7 +165,7 @@ void main(void) {
|
||||
// } else if (mcIndex == 7.0) {
|
||||
// b0 += c7; b1 += c4;
|
||||
// } else if (mcIndex == 8.0) {
|
||||
// b0 += c0; b1 += c4;
|
||||
// b1 += c4;
|
||||
// } else if (mcIndex == 9.0) {
|
||||
// b0 += c1; b1 += c5;
|
||||
// } else if (mcIndex == 10.0) {
|
||||
@@ -176,27 +176,34 @@ void main(void) {
|
||||
// b0 = floor(b0 + 0.5);
|
||||
// b1 = floor(b1 + 0.5);
|
||||
|
||||
vec4 d0 = voxel(b0 / uGridDim);
|
||||
vec4 d1 = voxel(b1 / uGridDim);
|
||||
vec4 d0 = voxel(b0);
|
||||
vec4 d1 = voxel(b1);
|
||||
|
||||
float v0 = d0.a;
|
||||
float v1 = d1.a;
|
||||
|
||||
float t = (uIsoValue - v0) / (v0 - v1);
|
||||
// t = -0.5;
|
||||
gl_FragData[0].xyz = (uGridTransform * vec4(b0 + t * (b0 - b1), 1.0)).xyz;
|
||||
gl_FragData[0].w = decodeFloatRGB(d0.rgb); // group id
|
||||
|
||||
// group id
|
||||
#if __VERSION__ == 100
|
||||
// webgl1 does not support 'flat' interpolation (i.e. no interpolation)
|
||||
// so we ensure a constant group id per triangle
|
||||
gl_FragData[0].w = decodeFloatRGB(voxel(coord3d).rgb);
|
||||
#else
|
||||
gl_FragData[0].w = t < 0.5 ? decodeFloatRGB(d0.rgb) : decodeFloatRGB(d1.rgb);
|
||||
#endif
|
||||
|
||||
// normals from gradients
|
||||
vec3 n0 = -normalize(vec3(
|
||||
voxel((b0 - c1) / uGridDim).a - voxel((b0 + c1) / uGridDim).a,
|
||||
voxel((b0 - c3) / uGridDim).a - voxel((b0 + c3) / uGridDim).a,
|
||||
voxel((b0 - c4) / uGridDim).a - voxel((b0 + c4) / uGridDim).a
|
||||
voxel2(b0 - c1).a - voxel2(b0 + c1).a,
|
||||
voxel2(b0 - c3).a - voxel2(b0 + c3).a,
|
||||
voxel2(b0 - c4).a - voxel2(b0 + c4).a
|
||||
));
|
||||
vec3 n1 = -normalize(vec3(
|
||||
voxel((b1 - c1) / uGridDim).a - voxel((b1 + c1) / uGridDim).a,
|
||||
voxel((b1 - c3) / uGridDim).a - voxel((b1 + c3) / uGridDim).a,
|
||||
voxel((b1 - c4) / uGridDim).a - voxel((b1 + c4) / uGridDim).a
|
||||
voxel2(b1 - c1).a - voxel2(b1 + c1).a,
|
||||
voxel2(b1 - c3).a - voxel2(b1 + c3).a,
|
||||
voxel2(b1 - c4).a - voxel2(b1 + c4).a
|
||||
));
|
||||
gl_FragData[1].xyz = -vec3(
|
||||
n0.x + t * (n0.x - n1.x),
|
||||
|
||||
@@ -35,6 +35,7 @@ void main() {
|
||||
interior = !frontFacing;
|
||||
#endif
|
||||
|
||||
float fragmentDepth = gl_FragCoord.z;
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
@@ -60,8 +61,6 @@ void main() {
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
|
||||
float fragmentDepth = gl_FragCoord.z;
|
||||
#include wboit_write
|
||||
#endif
|
||||
}
|
||||
|
||||
65
src/mol-gl/shader/outlines.frag.ts
Normal file
65
src/mol-gl/shader/outlines.frag.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
*/
|
||||
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tDepth;
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
uniform float uNear;
|
||||
uniform float uFar;
|
||||
|
||||
uniform float uMaxPossibleViewZDiff;
|
||||
|
||||
#include common
|
||||
|
||||
float getViewZ(const in float depth) {
|
||||
#if dOrthographic == 1
|
||||
return orthographicDepthToViewZ(depth, uNear, uFar);
|
||||
#else
|
||||
return perspectiveDepthToViewZ(depth, uNear, uFar);
|
||||
#endif
|
||||
}
|
||||
|
||||
float getDepth(const in vec2 coords) {
|
||||
return unpackRGBAToDepth(texture2D(tDepth, coords));
|
||||
}
|
||||
|
||||
bool isBackground(const in float depth) {
|
||||
return depth == 1.0;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
float backgroundViewZ = uFar + 3.0 * uMaxPossibleViewZDiff;
|
||||
|
||||
vec2 coords = gl_FragCoord.xy / uTexSize;
|
||||
vec2 invTexSize = 1.0 / uTexSize;
|
||||
|
||||
float selfDepth = getDepth(coords);
|
||||
float selfViewZ = isBackground(selfDepth) ? backgroundViewZ : getViewZ(getDepth(coords));
|
||||
|
||||
float outline = 1.0;
|
||||
float bestDepth = 1.0;
|
||||
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
vec2 sampleCoords = coords + vec2(float(x), float(y)) * invTexSize;
|
||||
float sampleDepth = getDepth(sampleCoords);
|
||||
float sampleViewZ = isBackground(sampleDepth) ? backgroundViewZ : getViewZ(sampleDepth);
|
||||
|
||||
if (abs(selfViewZ - sampleViewZ) > uMaxPossibleViewZDiff && selfDepth > sampleDepth && sampleDepth <= bestDepth) {
|
||||
outline = 0.0;
|
||||
bestDepth = sampleDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(outline, packUnitIntervalToRG(bestDepth), 0.0);
|
||||
}
|
||||
`;
|
||||
@@ -23,6 +23,9 @@ const float radius = 0.5;
|
||||
|
||||
void main(){
|
||||
#include clip_pixel
|
||||
|
||||
float fragmentDepth = gl_FragCoord.z;
|
||||
bool interior = false;
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
@@ -42,9 +45,6 @@ void main(){
|
||||
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
|
||||
float fragmentDepth = gl_FragCoord.z;
|
||||
bool interior = false;
|
||||
#include wboit_write
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
/**
|
||||
* 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 `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tSsaoDepth;
|
||||
uniform sampler2D tColor;
|
||||
uniform sampler2D tPackedDepth;
|
||||
uniform sampler2D tDepth;
|
||||
uniform sampler2D tOutlines;
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
uniform float uNear;
|
||||
@@ -12,6 +21,7 @@ uniform float uFar;
|
||||
uniform float uFogNear;
|
||||
uniform float uFogFar;
|
||||
uniform vec3 uFogColor;
|
||||
uniform bool uTransparentBackground;
|
||||
|
||||
uniform float uOcclusionBias;
|
||||
uniform float uOcclusionRadius;
|
||||
@@ -19,29 +29,12 @@ uniform float uOcclusionRadius;
|
||||
uniform float uOutlineScale;
|
||||
uniform float uOutlineThreshold;
|
||||
|
||||
const float noiseAmount = 0.0002;
|
||||
const vec4 occlusionColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
uniform float uMaxPossibleViewZDiff;
|
||||
|
||||
const vec3 occlusionColor = vec3(0.0);
|
||||
|
||||
#include common
|
||||
|
||||
float noise(const in vec2 coords) {
|
||||
float a = 12.9898;
|
||||
float b = 78.233;
|
||||
float c = 43758.5453;
|
||||
float dt = dot(coords, vec2(a,b));
|
||||
float sn = mod(dt, 3.14159);
|
||||
|
||||
return fract(sin(sn) * c);
|
||||
}
|
||||
|
||||
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 getViewZ(const in float depth) {
|
||||
#if dOrthographic == 1
|
||||
return orthographicDepthToViewZ(depth, uNear, uFar);
|
||||
@@ -51,68 +44,88 @@ float getViewZ(const in float depth) {
|
||||
}
|
||||
|
||||
float getDepth(const in vec2 coords) {
|
||||
return unpackRGBAToDepth(texture2D(tPackedDepth, coords));
|
||||
return unpackRGBAToDepth(texture2D(tDepth, coords));
|
||||
}
|
||||
|
||||
float calcSSAO(const in vec2 coords, const in float depth) {
|
||||
float occlusionFactor = 0.0;
|
||||
bool isBackground(const in float depth) {
|
||||
return depth == 1.0;
|
||||
}
|
||||
|
||||
for (int i = -dOcclusionKernelSize; i <= dOcclusionKernelSize; i++) {
|
||||
for (int j = -dOcclusionKernelSize; j <= dOcclusionKernelSize; j++) {
|
||||
vec2 coordsDelta = coords + uOcclusionRadius / float(dOcclusionKernelSize) * vec2(float(i) / uTexSize.x, float(j) / uTexSize.y);
|
||||
coordsDelta += noiseAmount * (noise(coordsDelta) - 0.5) / uTexSize;
|
||||
coordsDelta = clamp(coordsDelta, 0.5 / uTexSize, 1.0 - 1.0 / uTexSize);
|
||||
if (getDepth(coordsDelta) < depth) occlusionFactor += 1.0;
|
||||
float getOutline(const in vec2 coords, out float closestTexel) {
|
||||
float backgroundViewZ = uFar + 3.0 * uMaxPossibleViewZDiff;
|
||||
vec2 invTexSize = 1.0 / uTexSize;
|
||||
|
||||
float selfDepth = getDepth(coords);
|
||||
float selfViewZ = isBackground(selfDepth) ? backgroundViewZ : getViewZ(selfDepth);
|
||||
|
||||
float outline = 1.0;
|
||||
closestTexel = 1.0;
|
||||
for (int y = -dOutlineScale; y <= dOutlineScale; y++) {
|
||||
for (int x = -dOutlineScale; x <= dOutlineScale; x++) {
|
||||
if (x * x + y * y > dOutlineScale * dOutlineScale) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vec2 sampleCoords = coords + vec2(float(x), float(y)) * invTexSize;
|
||||
|
||||
vec4 sampleOutlineCombined = texture2D(tOutlines, sampleCoords);
|
||||
float sampleOutline = sampleOutlineCombined.r;
|
||||
float sampleOutlineDepth = unpackRGToUnitInterval(sampleOutlineCombined.gb);
|
||||
|
||||
if (sampleOutline == 0.0 && sampleOutlineDepth < closestTexel && abs(selfViewZ - sampleOutlineDepth) > uMaxPossibleViewZDiff) {
|
||||
outline = 0.0;
|
||||
closestTexel = sampleOutlineDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return occlusionFactor / float((2 * dOcclusionKernelSize + 1) * (2 * dOcclusionKernelSize + 1));
|
||||
return outline;
|
||||
}
|
||||
|
||||
vec2 calcEdgeDepth(const in vec2 coords) {
|
||||
vec2 invTexSize = 1.0 / uTexSize;
|
||||
float halfScaleFloor = floor(uOutlineScale * 0.5);
|
||||
float halfScaleCeil = ceil(uOutlineScale * 0.5);
|
||||
|
||||
vec2 bottomLeftUV = coords - invTexSize * halfScaleFloor;
|
||||
vec2 topRightUV = coords + invTexSize * halfScaleCeil;
|
||||
vec2 bottomRightUV = coords + vec2(invTexSize.x * halfScaleCeil, -invTexSize.y * halfScaleFloor);
|
||||
vec2 topLeftUV = coords + vec2(-invTexSize.x * halfScaleFloor, invTexSize.y * halfScaleCeil);
|
||||
|
||||
float depth0 = getDepth(bottomLeftUV);
|
||||
float depth1 = getDepth(topRightUV);
|
||||
float depth2 = getDepth(bottomRightUV);
|
||||
float depth3 = getDepth(topLeftUV);
|
||||
|
||||
float depthFiniteDifference0 = depth1 - depth0;
|
||||
float depthFiniteDifference1 = depth3 - depth2;
|
||||
|
||||
return vec2(
|
||||
sqrt(pow(depthFiniteDifference0, 2.0) + pow(depthFiniteDifference1, 2.0)) * 100.0,
|
||||
min(depth0, min(depth1, min(depth2, depth3)))
|
||||
);
|
||||
float getSsao(vec2 coords) {
|
||||
float rawSsao = unpackRGToUnitInterval(texture2D(tSsaoDepth, coords).xy);
|
||||
if (rawSsao > 0.999) {
|
||||
return 1.0;
|
||||
} else if (rawSsao > 0.001) {
|
||||
return rawSsao;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
vec2 coords = gl_FragCoord.xy / uTexSize;
|
||||
vec4 color = texture2D(tColor, coords);
|
||||
|
||||
#ifdef dOutlineEnable
|
||||
vec2 edgeDepth = calcEdgeDepth(coords);
|
||||
float edgeFlag = step(edgeDepth.x, uOutlineThreshold);
|
||||
color.rgb *= edgeFlag;
|
||||
float viewDist;
|
||||
float fogFactor;
|
||||
|
||||
float viewDist = abs(getViewZ(edgeDepth.y));
|
||||
float fogFactor = smoothstep(uFogNear, uFogFar, viewDist) * (1.0 - edgeFlag);
|
||||
color.rgb = mix(color.rgb, uFogColor, fogFactor);
|
||||
#endif
|
||||
|
||||
// occlusion needs to be handled after outline to darken them properly
|
||||
#ifdef dOcclusionEnable
|
||||
float depth = getDepth(coords);
|
||||
if (depth <= 0.99) {
|
||||
float occlusionFactor = calcSSAO(coords, depth);
|
||||
color = mix(color, occlusionColor, uOcclusionBias * occlusionFactor);
|
||||
if (!isBackground(depth)) {
|
||||
viewDist = abs(getViewZ(depth));
|
||||
fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
|
||||
float occlusionFactor = getSsao(coords);
|
||||
if (!uTransparentBackground) {
|
||||
color.rgb = mix(mix(occlusionColor, uFogColor, fogFactor), color.rgb, occlusionFactor);
|
||||
} else {
|
||||
color.rgb = mix(occlusionColor * (1.0 - fogFactor), color.rgb, occlusionFactor);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// outline needs to be handled after occlusion to keep them clean
|
||||
#ifdef dOutlineEnable
|
||||
float closestTexel;
|
||||
float outline = getOutline(coords, closestTexel);
|
||||
|
||||
if (outline == 0.0) {
|
||||
color.rgb *= outline;
|
||||
viewDist = abs(getViewZ(closestTexel));
|
||||
fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
|
||||
if (!uTransparentBackground) {
|
||||
color.rgb = mix(color.rgb, uFogColor, fogFactor);
|
||||
} else {
|
||||
color.a = 1.0 - fogFactor;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
66
src/mol-gl/shader/smaa/blend.frag.ts
Normal file
66
src/mol-gl/shader/smaa/blend.frag.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Slightly adapted from https://github.com/mrdoob/three.js
|
||||
* MIT License Copyright (c) 2010-2020 three.js authors
|
||||
*
|
||||
* WebGL port of Subpixel Morphological Antialiasing (SMAA) v2.8
|
||||
* Preset: SMAA 1x Medium (with color edge detection)
|
||||
* https://github.com/iryoku/smaa/releases/tag/v2.8
|
||||
*/
|
||||
|
||||
export const blend_frag = `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tWeights;
|
||||
uniform sampler2D tColor;
|
||||
uniform vec2 uTexSizeInv;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec4 vOffset[2];
|
||||
|
||||
vec4 SMAANeighborhoodBlendingPS(vec2 texCoord, vec4 offset[2], sampler2D colorTex, sampler2D blendTex) {
|
||||
// Fetch the blending weights for current pixel:
|
||||
vec4 a;
|
||||
a.xz = texture2D(blendTex, texCoord).xz;
|
||||
a.y = texture2D(blendTex, offset[1].zw).g;
|
||||
a.w = texture2D(blendTex, offset[1].xy).a;
|
||||
|
||||
// Is there any blending weight with a value greater than 0.0?
|
||||
if (dot(a, vec4(1.0, 1.0, 1.0, 1.0)) < 1e-5) {
|
||||
return texture2D(colorTex, texCoord, 0.0);
|
||||
} else {
|
||||
// Up to 4 lines can be crossing a pixel (one through each edge). We
|
||||
// favor blending by choosing the line with the maximum weight for each
|
||||
// direction:
|
||||
vec2 offset;
|
||||
offset.x = a.a > a.b ? a.a : -a.b; // left vs. right
|
||||
offset.y = a.g > a.r ? -a.g : a.r; // top vs. bottom // WebGL port note: Changed signs
|
||||
|
||||
// Then we go in the direction that has the maximum weight:
|
||||
if (abs(offset.x) > abs(offset.y)) { // horizontal vs. vertical
|
||||
offset.y = 0.0;
|
||||
} else {
|
||||
offset.x = 0.0;
|
||||
}
|
||||
|
||||
// Fetch the opposite color and lerp by hand:
|
||||
vec4 C = texture2D(colorTex, texCoord, 0.0);
|
||||
texCoord += sign(offset) * uTexSizeInv;
|
||||
vec4 Cop = texture2D(colorTex, texCoord, 0.0);
|
||||
float s = abs(offset.x) > abs(offset.y) ? abs(offset.x) : abs(offset.y);
|
||||
|
||||
// WebGL port note: Added gamma correction
|
||||
C.xyz = pow(C.xyz, vec3(2.2));
|
||||
Cop.xyz = pow(Cop.xyz, vec3(2.2));
|
||||
vec4 mixed = mix(C, Cop, s);
|
||||
mixed.xyz = pow(mixed.xyz, vec3(1.0 / 2.2));
|
||||
|
||||
return mixed;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_FragColor = SMAANeighborhoodBlendingPS(vUv, vOffset, tColor, tWeights);
|
||||
}
|
||||
`;
|
||||
35
src/mol-gl/shader/smaa/blend.vert.ts
Normal file
35
src/mol-gl/shader/smaa/blend.vert.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Slightly adapted from https://github.com/mrdoob/three.js
|
||||
* MIT License Copyright (c) 2010-2020 three.js authors
|
||||
*
|
||||
* WebGL port of Subpixel Morphological Antialiasing (SMAA) v2.8
|
||||
* Preset: SMAA 1x Medium (with color edge detection)
|
||||
* https://github.com/iryoku/smaa/releases/tag/v2.8
|
||||
*/
|
||||
|
||||
export const blend_vert = `
|
||||
precision highp float;
|
||||
|
||||
attribute vec2 aPosition;
|
||||
uniform vec2 uQuadScale;
|
||||
|
||||
uniform vec2 uTexSizeInv;
|
||||
uniform vec4 uViewport;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec4 vOffset[2];
|
||||
|
||||
void SMAANeighborhoodBlendingVS(vec2 texCoord) {
|
||||
vOffset[0] = texCoord.xyxy + uTexSizeInv.xyxy * vec4(-1.0, 0.0, 0.0, 1.0); // WebGL port note: Changed sign in W component
|
||||
vOffset[1] = texCoord.xyxy + uTexSizeInv.xyxy * vec4(1.0, 0.0, 0.0, -1.0); // WebGL port note: Changed sign in W component
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 scale = uViewport.zw * uTexSizeInv;
|
||||
vec2 shift = uViewport.xy * uTexSizeInv;
|
||||
vUv = (aPosition + 1.0) * 0.5 * scale + shift;
|
||||
SMAANeighborhoodBlendingVS(vUv);
|
||||
vec2 position = aPosition * uQuadScale - vec2(1.0, 1.0) + uQuadScale;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
76
src/mol-gl/shader/smaa/edges.frag.ts
Normal file
76
src/mol-gl/shader/smaa/edges.frag.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Slightly adapted from https://github.com/mrdoob/three.js
|
||||
* MIT License Copyright (c) 2010-2020 three.js authors
|
||||
*
|
||||
* WebGL port of Subpixel Morphological Antialiasing (SMAA) v2.8
|
||||
* Preset: SMAA 1x Medium (with color edge detection)
|
||||
* https://github.com/iryoku/smaa/releases/tag/v2.8
|
||||
*/
|
||||
|
||||
export const edges_frag = `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tColor;
|
||||
uniform vec2 uTexSizeInv;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec4 vOffset[3];
|
||||
|
||||
vec4 SMAAColorEdgeDetectionPS(vec2 texcoord, vec4 offset[3], sampler2D colorTex) {
|
||||
vec2 threshold = vec2(dEdgeThreshold, dEdgeThreshold);
|
||||
|
||||
// Calculate color deltas:
|
||||
vec4 delta;
|
||||
vec3 C = texture2D(colorTex, texcoord).rgb;
|
||||
|
||||
vec3 Cleft = texture2D(colorTex, offset[0].xy).rgb;
|
||||
vec3 t = abs(C - Cleft);
|
||||
delta.x = max(max(t.r, t.g), t.b);
|
||||
|
||||
vec3 Ctop = texture2D(colorTex, offset[0].zw).rgb;
|
||||
t = abs(C - Ctop);
|
||||
delta.y = max(max(t.r, t.g), t.b);
|
||||
|
||||
// We do the usual threshold:
|
||||
vec2 edges = step(threshold, delta.xy);
|
||||
|
||||
// Then discard if there is no edge:
|
||||
if (dot(edges, vec2(1.0, 1.0 )) == 0.0)
|
||||
discard;
|
||||
|
||||
// Calculate right and bottom deltas:
|
||||
vec3 Cright = texture2D(colorTex, offset[1].xy).rgb;
|
||||
t = abs( C - Cright );
|
||||
delta.z = max(max(t.r, t.g), t.b);
|
||||
|
||||
vec3 Cbottom = texture2D(colorTex, offset[1].zw).rgb;
|
||||
t = abs(C - Cbottom);
|
||||
delta.w = max(max(t.r, t.g), t.b);
|
||||
|
||||
// Calculate the maximum delta in the direct neighborhood:
|
||||
float maxDelta = max(max(max(delta.x, delta.y), delta.z), delta.w );
|
||||
|
||||
// Calculate left-left and top-top deltas:
|
||||
vec3 Cleftleft = texture2D(colorTex, offset[2].xy).rgb;
|
||||
t = abs( C - Cleftleft );
|
||||
delta.z = max(max(t.r, t.g), t.b);
|
||||
|
||||
vec3 Ctoptop = texture2D(colorTex, offset[2].zw).rgb;
|
||||
t = abs(C - Ctoptop);
|
||||
delta.w = max(max(t.r, t.g), t.b);
|
||||
|
||||
// Calculate the final maximum delta:
|
||||
maxDelta = max(max(maxDelta, delta.z), delta.w);
|
||||
|
||||
// Local contrast adaptation in action:
|
||||
edges.xy *= step(0.5 * maxDelta, delta.xy);
|
||||
|
||||
return vec4(edges, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_FragColor = SMAAColorEdgeDetectionPS(vUv, vOffset, tColor);
|
||||
}
|
||||
`;
|
||||
36
src/mol-gl/shader/smaa/edges.vert.ts
Normal file
36
src/mol-gl/shader/smaa/edges.vert.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Slightly adapted from https://github.com/mrdoob/three.js
|
||||
* MIT License Copyright (c) 2010-2020 three.js authors
|
||||
*
|
||||
* WebGL port of Subpixel Morphological Antialiasing (SMAA) v2.8
|
||||
* Preset: SMAA 1x Medium (with color edge detection)
|
||||
* https://github.com/iryoku/smaa/releases/tag/v2.8
|
||||
*/
|
||||
|
||||
export const edges_vert = `
|
||||
precision highp float;
|
||||
|
||||
attribute vec2 aPosition;
|
||||
uniform vec2 uQuadScale;
|
||||
|
||||
uniform vec2 uTexSizeInv;
|
||||
uniform vec4 uViewport;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec4 vOffset[3];
|
||||
|
||||
void SMAAEdgeDetectionVS(vec2 texCoord) {
|
||||
vOffset[0] = texCoord.xyxy + uTexSizeInv.xyxy * vec4(-1.0, 0.0, 0.0, 1.0); // WebGL port note: Changed sign in W component
|
||||
vOffset[1] = texCoord.xyxy + uTexSizeInv.xyxy * vec4(1.0, 0.0, 0.0, -1.0); // WebGL port note: Changed sign in W component
|
||||
vOffset[2] = texCoord.xyxy + uTexSizeInv.xyxy * vec4(-2.0, 0.0, 0.0, 2.0); // WebGL port note: Changed sign in W component
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 scale = uViewport.zw * uTexSizeInv;
|
||||
vec2 shift = uViewport.xy * uTexSizeInv;
|
||||
vUv = (aPosition + 1.0) * 0.5 * scale + shift;
|
||||
SMAAEdgeDetectionVS(vUv);
|
||||
vec2 position = aPosition * uQuadScale - vec2(1.0, 1.0) + uQuadScale;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
216
src/mol-gl/shader/smaa/weights.frag.ts
Normal file
216
src/mol-gl/shader/smaa/weights.frag.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* Slightly adapted from https://github.com/mrdoob/three.js
|
||||
* MIT License Copyright (c) 2010-2020 three.js authors
|
||||
*
|
||||
* WebGL port of Subpixel Morphological Antialiasing (SMAA) v2.8
|
||||
* Preset: SMAA 1x Medium (with color edge detection)
|
||||
* https://github.com/iryoku/smaa/releases/tag/v2.8
|
||||
*/
|
||||
|
||||
export const weights_frag = `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
#define SMAASampleLevelZeroOffset(tex, coord, offset) texture2D(tex, coord + float(offset) * uTexSizeInv, 0.0)
|
||||
|
||||
#define SMAA_AREATEX_MAX_DISTANCE 16
|
||||
#define SMAA_AREATEX_PIXEL_SIZE (1.0 / vec2(160.0, 560.0))
|
||||
#define SMAA_AREATEX_SUBTEX_SIZE (1.0 / 7.0)
|
||||
|
||||
uniform sampler2D tEdges;
|
||||
uniform sampler2D tArea;
|
||||
uniform sampler2D tSearch;
|
||||
uniform vec2 uTexSizeInv;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec4 vOffset[3];
|
||||
varying vec2 vPixCoord;
|
||||
|
||||
#if __VERSION__ == 100
|
||||
vec2 round(vec2 x) {
|
||||
return sign(x) * floor(abs(x) + 0.5);
|
||||
}
|
||||
#endif
|
||||
|
||||
float SMAASearchLength(sampler2D searchTex, vec2 e, float bias, float scale) {
|
||||
// Not required if searchTex accesses are set to point:
|
||||
// float2 SEARCH_TEX_PIXEL_SIZE = 1.0 / float2(66.0, 33.0);
|
||||
// e = float2(bias, 0.0) + 0.5 * SEARCH_TEX_PIXEL_SIZE +
|
||||
// e * float2(scale, 1.0) * float2(64.0, 32.0) * SEARCH_TEX_PIXEL_SIZE;
|
||||
e.r = bias + e.r * scale;
|
||||
return 255.0 * texture2D(searchTex, e, 0.0).r;
|
||||
}
|
||||
|
||||
float SMAASearchXLeft(sampler2D edgesTex, sampler2D searchTex, vec2 texCoord, float end) {
|
||||
/**
|
||||
* @PSEUDO_GATHER4
|
||||
* This texCoord has been offset by (-0.25, -0.125) in the vertex shader to
|
||||
* sample between edge, thus fetching four edges in a row.
|
||||
* Sampling with different offsets in each direction allows to disambiguate
|
||||
* which edges are active from the four fetched ones.
|
||||
*/
|
||||
vec2 e = vec2(0.0, 1.0);
|
||||
|
||||
for (int i = 0; i < dMaxSearchSteps; i++) { // WebGL port note: Changed while to for
|
||||
e = texture2D( edgesTex, texCoord, 0.0).rg;
|
||||
texCoord -= vec2(2.0, 0.0) * uTexSizeInv;
|
||||
if (!(texCoord.x > end && e.g > 0.8281 && e.r == 0.0)) break;
|
||||
}
|
||||
|
||||
// We correct the previous (-0.25, -0.125) offset we applied:
|
||||
texCoord.x += 0.25 * uTexSizeInv.x;
|
||||
|
||||
// The searches are bias by 1, so adjust the coords accordingly:
|
||||
texCoord.x += uTexSizeInv.x;
|
||||
|
||||
// Disambiguate the length added by the last step:
|
||||
texCoord.x += 2.0 * uTexSizeInv.x; // Undo last step
|
||||
texCoord.x -= uTexSizeInv.x * SMAASearchLength(searchTex, e, 0.0, 0.5);
|
||||
|
||||
return texCoord.x;
|
||||
}
|
||||
|
||||
float SMAASearchXRight(sampler2D edgesTex, sampler2D searchTex, vec2 texCoord, float end) {
|
||||
vec2 e = vec2( 0.0, 1.0 );
|
||||
|
||||
for (int i = 0; i < dMaxSearchSteps; i++) { // WebGL port note: Changed while to for
|
||||
e = texture2D(edgesTex, texCoord, 0.0).rg;
|
||||
texCoord += vec2(2.0, 0.0) * uTexSizeInv;
|
||||
if (!(texCoord.x < end && e.g > 0.8281 && e.r == 0.0)) break;
|
||||
}
|
||||
|
||||
texCoord.x -= 0.25 * uTexSizeInv.x;
|
||||
texCoord.x -= uTexSizeInv.x;
|
||||
texCoord.x -= 2.0 * uTexSizeInv.x;
|
||||
texCoord.x += uTexSizeInv.x * SMAASearchLength( searchTex, e, 0.5, 0.5 );
|
||||
|
||||
return texCoord.x;
|
||||
}
|
||||
|
||||
float SMAASearchYUp(sampler2D edgesTex, sampler2D searchTex, vec2 texCoord, float end) {
|
||||
vec2 e = vec2( 1.0, 0.0 );
|
||||
|
||||
for (int i = 0; i < dMaxSearchSteps; i++) { // WebGL port note: Changed while to for
|
||||
e = texture2D(edgesTex, texCoord, 0.0).rg;
|
||||
texCoord += vec2(0.0, 2.0) * uTexSizeInv; // WebGL port note: Changed sign
|
||||
if (!(texCoord.y > end && e.r > 0.8281 && e.g == 0.0)) break;
|
||||
}
|
||||
|
||||
texCoord.y -= 0.25 * uTexSizeInv.y; // WebGL port note: Changed sign
|
||||
texCoord.y -= uTexSizeInv.y; // WebGL port note: Changed sign
|
||||
texCoord.y -= 2.0 * uTexSizeInv.y; // WebGL port note: Changed sign
|
||||
texCoord.y += uTexSizeInv.y * SMAASearchLength(searchTex, e.gr, 0.0, 0.5); // WebGL port note: Changed sign
|
||||
|
||||
return texCoord.y;
|
||||
}
|
||||
|
||||
float SMAASearchYDown(sampler2D edgesTex, sampler2D searchTex, vec2 texCoord, float end) {
|
||||
vec2 e = vec2( 1.0, 0.0 );
|
||||
|
||||
for (int i = 0; i < dMaxSearchSteps; i++) { // WebGL port note: Changed while to for
|
||||
e = texture2D(edgesTex, texCoord, 0.0).rg;
|
||||
texCoord -= vec2( 0.0, 2.0 ) * uTexSizeInv; // WebGL port note: Changed sign
|
||||
if (!(texCoord.y < end && e.r > 0.8281 && e.g == 0.0)) break;
|
||||
}
|
||||
|
||||
texCoord.y += 0.25 * uTexSizeInv.y; // WebGL port note: Changed sign
|
||||
texCoord.y += uTexSizeInv.y; // WebGL port note: Changed sign
|
||||
texCoord.y += 2.0 * uTexSizeInv.y; // WebGL port note: Changed sign
|
||||
texCoord.y -= uTexSizeInv.y * SMAASearchLength(searchTex, e.gr, 0.5, 0.5); // WebGL port note: Changed sign
|
||||
|
||||
return texCoord.y;
|
||||
}
|
||||
|
||||
vec2 SMAAArea(sampler2D areaTex, vec2 dist, float e1, float e2, float offset) {
|
||||
// Rounding prevents precision errors of bilinear filtering:
|
||||
vec2 texCoord = float(SMAA_AREATEX_MAX_DISTANCE) * round(4.0 * vec2(e1, e2)) + dist;
|
||||
|
||||
// We do a scale and bias for mapping to texel space:
|
||||
texCoord = SMAA_AREATEX_PIXEL_SIZE * texCoord + (0.5 * SMAA_AREATEX_PIXEL_SIZE);
|
||||
|
||||
// Move to proper place, according to the subpixel offset:
|
||||
texCoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset;
|
||||
|
||||
return texture2D(areaTex, texCoord, 0.0).rg;
|
||||
}
|
||||
|
||||
vec4 SMAABlendingWeightCalculationPS(vec2 texCoord, vec2 pixCoord, vec4 offset[3], sampler2D edgesTex, sampler2D areaTex, sampler2D searchTex, ivec4 subsampleIndices) {
|
||||
vec4 weights = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
vec2 e = texture2D(edgesTex, texCoord).rg;
|
||||
|
||||
if (e.g > 0.0) { // Edge at north
|
||||
vec2 d;
|
||||
|
||||
// Find the distance to the left:
|
||||
vec2 coords;
|
||||
coords.x = SMAASearchXLeft(edgesTex, searchTex, offset[0].xy, offset[2].x );
|
||||
coords.y = offset[1].y; // offset[1].y = texCoord.y - 0.25 * uTexSizeInv.y (@CROSSING_OFFSET)
|
||||
d.x = coords.x;
|
||||
|
||||
// Now fetch the left crossing edges, two at a time using bilinear
|
||||
// filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to
|
||||
// discern what value each edge has:
|
||||
float e1 = texture2D(edgesTex, coords, 0.0).r;
|
||||
|
||||
// Find the distance to the right:
|
||||
coords.x = SMAASearchXRight(edgesTex, searchTex, offset[0].zw, offset[2].y);
|
||||
d.y = coords.x;
|
||||
|
||||
// We want the distances to be in pixel units (doing this here allow to
|
||||
// better interleave arithmetic and memory accesses):
|
||||
d = d / uTexSizeInv.x - pixCoord.x;
|
||||
|
||||
// SMAAArea below needs a sqrt, as the areas texture is compressed
|
||||
// quadratically:
|
||||
vec2 sqrt_d = sqrt(abs(d));
|
||||
|
||||
// Fetch the right crossing edges:
|
||||
coords.y -= 1.0 * uTexSizeInv.y; // WebGL port note: Added
|
||||
float e2 = SMAASampleLevelZeroOffset(edgesTex, coords, ivec2(1, 0)).r;
|
||||
|
||||
// Ok, we know how this pattern looks like, now it is time for getting
|
||||
// the actual area:
|
||||
weights.rg = SMAAArea(areaTex, sqrt_d, e1, e2, float(subsampleIndices.y));
|
||||
}
|
||||
|
||||
if (e.r > 0.0) { // Edge at west
|
||||
vec2 d;
|
||||
|
||||
// Find the distance to the top:
|
||||
vec2 coords;
|
||||
|
||||
coords.y = SMAASearchYUp(edgesTex, searchTex, offset[1].xy, offset[2].z );
|
||||
coords.x = offset[0].x; // offset[1].x = texCoord.x - 0.25 * uTexSizeInv.x;
|
||||
d.x = coords.y;
|
||||
|
||||
// Fetch the top crossing edges:
|
||||
float e1 = texture2D(edgesTex, coords, 0.0).g;
|
||||
|
||||
// Find the distance to the bottom:
|
||||
coords.y = SMAASearchYDown(edgesTex, searchTex, offset[1].zw, offset[2].w);
|
||||
d.y = coords.y;
|
||||
|
||||
// We want the distances to be in pixel units:
|
||||
d = d / uTexSizeInv.y - pixCoord.y;
|
||||
|
||||
// SMAAArea below needs a sqrt, as the areas texture is compressed
|
||||
// quadratically:
|
||||
vec2 sqrt_d = sqrt(abs(d));
|
||||
|
||||
// Fetch the bottom crossing edges:
|
||||
coords.y -= 1.0 * uTexSizeInv.y; // WebGL port note: Added
|
||||
float e2 = SMAASampleLevelZeroOffset(edgesTex, coords, ivec2(0, 1)).g;
|
||||
|
||||
// Get the area for this direction:
|
||||
weights.ba = SMAAArea(areaTex, sqrt_d, e1, e2, float(subsampleIndices.x));
|
||||
}
|
||||
|
||||
return weights;
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_FragColor = SMAABlendingWeightCalculationPS(vUv, vPixCoord, vOffset, tEdges, tArea, tSearch, ivec4(0.0));
|
||||
}
|
||||
`;
|
||||
42
src/mol-gl/shader/smaa/weights.vert.ts
Normal file
42
src/mol-gl/shader/smaa/weights.vert.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Slightly adapted from https://github.com/mrdoob/three.js
|
||||
* MIT License Copyright (c) 2010-2020 three.js authors
|
||||
*
|
||||
* WebGL port of Subpixel Morphological Antialiasing (SMAA) v2.8
|
||||
* Preset: SMAA 1x Medium (with color edge detection)
|
||||
* https://github.com/iryoku/smaa/releases/tag/v2.8
|
||||
*/
|
||||
|
||||
export const weights_vert = `
|
||||
precision highp float;
|
||||
|
||||
attribute vec2 aPosition;
|
||||
uniform vec2 uQuadScale;
|
||||
|
||||
uniform vec2 uTexSizeInv;
|
||||
uniform vec4 uViewport;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec4 vOffset[3];
|
||||
varying vec2 vPixCoord;
|
||||
|
||||
void SMAABlendingWeightCalculationVS(vec2 texCoord) {
|
||||
vPixCoord = texCoord / uTexSizeInv;
|
||||
|
||||
// We will use these offsets for the searches later on (see @PSEUDO_GATHER4):
|
||||
vOffset[0] = texCoord.xyxy + uTexSizeInv.xyxy * vec4(-0.25, 0.125, 1.25, 0.125); // WebGL port note: Changed sign in Y and W components
|
||||
vOffset[1] = texCoord.xyxy + uTexSizeInv.xyxy * vec4(-0.125, 0.25, -0.125, -1.25); // WebGL port note: Changed sign in Y and W components
|
||||
|
||||
// And these for the searches, they indicate the ends of the loops:
|
||||
vOffset[2] = vec4(vOffset[0].xz, vOffset[1].yw) + vec4(-2.0, 2.0, -2.0, 2.0) * uTexSizeInv.xxyy * float(dMaxSearchSteps);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 scale = uViewport.zw * uTexSizeInv;
|
||||
vec2 shift = uViewport.xy * uTexSizeInv;
|
||||
vUv = (aPosition + 1.0) * 0.5 * scale + shift;
|
||||
SMAABlendingWeightCalculationVS(vUv);
|
||||
vec2 position = aPosition * uQuadScale - vec2(1.0, 1.0) + uQuadScale;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
@@ -15,9 +15,6 @@ precision highp int;
|
||||
#include common_clip
|
||||
#include wboit_params
|
||||
|
||||
uniform float uClipNear;
|
||||
uniform float uIsOrtho;
|
||||
|
||||
varying float vRadius;
|
||||
varying float vRadiusSq;
|
||||
varying vec3 vPoint;
|
||||
@@ -26,10 +23,6 @@ varying vec3 vPointViewPosition;
|
||||
vec3 cameraPos;
|
||||
vec3 cameraNormal;
|
||||
|
||||
float calcClip(const in vec3 cameraPos) {
|
||||
return dot(vec4(cameraPos, 1.0), vec4(0.0, 0.0, 1.0, uClipNear - 0.5));
|
||||
}
|
||||
|
||||
bool Impostor(out vec3 cameraPos, out vec3 cameraNormal){
|
||||
vec3 cameraSpherePos = -vPointViewPosition;
|
||||
cameraSpherePos.z += vRadius;
|
||||
@@ -75,19 +68,17 @@ void main(void){
|
||||
discard;
|
||||
#endif
|
||||
|
||||
// FIXME not compatible with custom clipping plane
|
||||
// Set the depth based on the new cameraPos.
|
||||
gl_FragDepthEXT = calcDepth(cameraPos);
|
||||
vec3 vViewPosition = cameraPos;
|
||||
gl_FragDepthEXT = calcDepth(vViewPosition);
|
||||
if (!flag && gl_FragDepthEXT >= 0.0) {
|
||||
gl_FragDepthEXT = 0.0 + (0.0000001 / vRadius);
|
||||
}
|
||||
|
||||
// bugfix (mac only?)
|
||||
if (gl_FragDepthEXT < 0.0)
|
||||
discard;
|
||||
if (gl_FragDepthEXT > 1.0)
|
||||
discard;
|
||||
if (gl_FragDepthEXT < 0.0) discard;
|
||||
if (gl_FragDepthEXT > 1.0) discard;
|
||||
|
||||
float fragmentDepth = gl_FragDepthEXT;
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
@@ -100,15 +91,12 @@ void main(void){
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
vec3 normal = -cameraNormal;
|
||||
vec3 vViewPosition = cameraPos;
|
||||
#include apply_light_color
|
||||
#endif
|
||||
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
|
||||
float fragmentDepth = gl_FragDepthEXT;
|
||||
#include wboit_write
|
||||
#endif
|
||||
}
|
||||
|
||||
84
src/mol-gl/shader/ssao-blur.frag.ts
Normal file
84
src/mol-gl/shader/ssao-blur.frag.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
*/
|
||||
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tSsaoDepth;
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
uniform float uKernel[dOcclusionKernelSize];
|
||||
|
||||
uniform float uBlurDirectionX;
|
||||
uniform float uBlurDirectionY;
|
||||
|
||||
uniform float uMaxPossibleViewZDiff;
|
||||
|
||||
uniform float uNear;
|
||||
uniform float uFar;
|
||||
|
||||
#include common
|
||||
|
||||
float getViewZ(const in float depth) {
|
||||
#if dOrthographic == 1
|
||||
return orthographicDepthToViewZ(depth, uNear, uFar);
|
||||
#else
|
||||
return perspectiveDepthToViewZ(depth, uNear, uFar);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isBackground(const in float depth) {
|
||||
return depth == 1.0;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
vec2 coords = gl_FragCoord.xy / uTexSize;
|
||||
|
||||
vec2 packedDepth = texture2D(tSsaoDepth, coords).zw;
|
||||
|
||||
float selfDepth = unpackRGToUnitInterval(packedDepth);
|
||||
// if background and if second pass
|
||||
if (isBackground(selfDepth) && uBlurDirectionY != 0.0) {
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
|
||||
return;
|
||||
}
|
||||
|
||||
float selfViewZ = getViewZ(selfDepth);
|
||||
|
||||
vec2 offset = vec2(uBlurDirectionX, uBlurDirectionY) / uTexSize;
|
||||
|
||||
float sum = 0.0;
|
||||
float kernelSum = 0.0;
|
||||
// only if kernelSize is odd
|
||||
for (int i = -dOcclusionKernelSize / 2; i <= dOcclusionKernelSize / 2; i++) {
|
||||
vec2 sampleCoords = coords + float(i) * offset;
|
||||
|
||||
vec4 sampleSsaoDepth = texture2D(tSsaoDepth, sampleCoords);
|
||||
|
||||
float sampleDepth = unpackRGToUnitInterval(sampleSsaoDepth.zw);
|
||||
if (isBackground(sampleDepth)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (abs(float(i)) > 1.0) { // abs is not defined for int in webgl1
|
||||
float sampleViewZ = getViewZ(sampleDepth);
|
||||
if (abs(selfViewZ - sampleViewZ) > uMaxPossibleViewZDiff) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
float kernel = uKernel[int(abs(float(i)))]; // abs is not defined for int in webgl1
|
||||
float sampleValue = unpackRGToUnitInterval(sampleSsaoDepth.xy);
|
||||
|
||||
sum += kernel * sampleValue;
|
||||
kernelSum += kernel;
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(sum / kernelSum), packedDepth);
|
||||
}
|
||||
`;
|
||||
110
src/mol-gl/shader/ssao.frag.ts
Normal file
110
src/mol-gl/shader/ssao.frag.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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 `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
|
||||
#include common
|
||||
|
||||
uniform sampler2D tDepth;
|
||||
|
||||
uniform vec3 uSamples[dNSamples];
|
||||
|
||||
uniform mat4 uProjection;
|
||||
uniform mat4 uInvProjection;
|
||||
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
uniform float uRadius;
|
||||
uniform float uBias;
|
||||
|
||||
float smootherstep(float edge0, float edge1, float x) {
|
||||
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
||||
return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
|
||||
}
|
||||
|
||||
float noise(const in vec2 coords) {
|
||||
float a = 12.9898;
|
||||
float b = 78.233;
|
||||
float c = 43758.5453;
|
||||
float dt = dot(coords, vec2(a,b));
|
||||
float sn = mod(dt, PI);
|
||||
return abs(fract(sin(sn) * c)); // is abs necessary?
|
||||
}
|
||||
|
||||
vec2 getNoiseVec2(const in vec2 coords) {
|
||||
return vec2(noise(coords), noise(coords + vec2(PI, 2.71828)));
|
||||
}
|
||||
|
||||
bool isBackground(const in float depth) {
|
||||
return depth == 1.0;
|
||||
}
|
||||
|
||||
float getDepth(const in vec2 coords) {
|
||||
return unpackRGBAToDepth(texture2D(tDepth, coords));
|
||||
}
|
||||
|
||||
vec3 normalFromDepth(const in float depth, const in float depth1, const in float depth2, vec2 offset1, vec2 offset2) {
|
||||
vec3 p1 = vec3(offset1, depth1 - depth);
|
||||
vec3 p2 = vec3(offset2, depth2 - depth);
|
||||
|
||||
vec3 normal = cross(p1, p2);
|
||||
normal.z = -normal.z;
|
||||
|
||||
return normalize(normal);
|
||||
}
|
||||
|
||||
// StarCraft II Ambient Occlusion by [Filion and McNaughton 2008]
|
||||
void main(void) {
|
||||
vec2 invTexSize = 1.0 / uTexSize;
|
||||
vec2 selfCoords = gl_FragCoord.xy * invTexSize;
|
||||
|
||||
float selfDepth = getDepth(selfCoords);
|
||||
vec2 selfPackedDepth = packUnitIntervalToRG(selfDepth);
|
||||
|
||||
if (isBackground(selfDepth)) {
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(0.0), selfPackedDepth);
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 offset1 = vec2(0.0, invTexSize.y);
|
||||
vec2 offset2 = vec2(invTexSize.x, 0.0);
|
||||
|
||||
float selfDepth1 = getDepth(selfCoords + offset1);
|
||||
float selfDepth2 = getDepth(selfCoords + offset2);
|
||||
|
||||
vec3 selfViewNormal = normalFromDepth(selfDepth, selfDepth1, selfDepth2, offset1, offset2);
|
||||
vec3 selfViewPos = screenSpaceToViewSpace(vec3(selfCoords, selfDepth), uInvProjection);
|
||||
|
||||
vec3 randomVec = normalize(vec3(getNoiseVec2(selfCoords) * 2.0 - 1.0, 0.0));
|
||||
|
||||
vec3 tangent = normalize(randomVec - selfViewNormal * dot(randomVec, selfViewNormal));
|
||||
vec3 bitangent = cross(selfViewNormal, tangent);
|
||||
mat3 TBN = mat3(tangent, bitangent, selfViewNormal);
|
||||
|
||||
float occlusion = 0.0;
|
||||
for(int i = 0; i < dNSamples; i++){
|
||||
vec3 sampleViewPos = TBN * uSamples[i];
|
||||
sampleViewPos = selfViewPos + sampleViewPos * uRadius;
|
||||
|
||||
vec4 offset = vec4(sampleViewPos, 1.0);
|
||||
offset = uProjection * offset;
|
||||
offset.xyz = (offset.xyz / offset.w) * 0.5 + 0.5;
|
||||
|
||||
float sampleViewZ = screenSpaceToViewSpace(vec3(offset.xy, getDepth(offset.xy)), uInvProjection).z;
|
||||
|
||||
occlusion += step(sampleViewPos.z + 0.025, sampleViewZ) * smootherstep(0.0, 1.0, uRadius / abs(selfViewPos.z - sampleViewZ));
|
||||
}
|
||||
occlusion = 1.0 - (uBias * occlusion / float(dNSamples));
|
||||
|
||||
vec2 packedOcclusion = packUnitIntervalToRG(occlusion);
|
||||
|
||||
gl_FragColor = vec4(packedOcclusion, selfPackedDepth);
|
||||
}
|
||||
`;
|
||||
@@ -32,6 +32,9 @@ void main2(){
|
||||
|
||||
void main(){
|
||||
#include clip_pixel
|
||||
|
||||
float fragmentDepth = gl_FragCoord.z;
|
||||
bool interior = false;
|
||||
#include assign_material_color
|
||||
|
||||
if (vTexCoord.x > 1.0) {
|
||||
@@ -66,9 +69,6 @@ void main(){
|
||||
#elif defined(dRenderVariant_color)
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
|
||||
float fragmentDepth = gl_FragCoord.z;
|
||||
bool interior = false;
|
||||
#include wboit_write
|
||||
#endif
|
||||
}
|
||||
|
||||
53
src/mol-gl/shader/util/grid3d-template.frag.ts
Normal file
53
src/mol-gl/shader/util/grid3d-template.frag.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @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 bool uLittleEndian;
|
||||
uniform float uWidth;
|
||||
|
||||
#ifdef CUMULATIVE
|
||||
uniform sampler2D tCumulativeSum;
|
||||
#endif
|
||||
|
||||
{UNIFORMS}
|
||||
|
||||
{UTILS}
|
||||
|
||||
#include float_to_rgba
|
||||
#ifdef CUMULATIVE
|
||||
#include rgba_to_float
|
||||
#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);
|
||||
|
||||
{MAIN}
|
||||
|
||||
#ifdef CUMULATIVE
|
||||
float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth)), uLittleEndian);
|
||||
#endif
|
||||
gl_FragColor = floatToRgba({RETURN}, uLittleEndian);
|
||||
}
|
||||
`;
|
||||
@@ -1,11 +1,11 @@
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
|
||||
import { getErrorDescription, getGLContext } from './context';
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
import { getErrorDescription } from './context';
|
||||
import { getProgram } from './program';
|
||||
import { getShader } from './shader';
|
||||
|
||||
@@ -111,6 +111,27 @@ export function getTextureFloatLinear(gl: GLRenderingContext): COMPAT_texture_fl
|
||||
return gl.getExtension('OES_texture_float_linear');
|
||||
}
|
||||
|
||||
export interface COMPAT_texture_half_float {
|
||||
readonly HALF_FLOAT: number
|
||||
}
|
||||
|
||||
export function getTextureHalfFloat(gl: GLRenderingContext): COMPAT_texture_half_float | null {
|
||||
if (isWebGL2(gl)) {
|
||||
return { HALF_FLOAT: gl.HALF_FLOAT };
|
||||
} else {
|
||||
const ext = gl.getExtension('OES_texture_half_float');
|
||||
if (ext === null) return null;
|
||||
return { HALF_FLOAT: ext.HALF_FLOAT_OES };
|
||||
}
|
||||
}
|
||||
|
||||
export interface COMPAT_texture_half_float_linear {
|
||||
}
|
||||
|
||||
export function getTextureHalfFloatLinear(gl: GLRenderingContext): COMPAT_texture_half_float_linear | null {
|
||||
return gl.getExtension('OES_texture_half_float_linear');
|
||||
}
|
||||
|
||||
export interface COMPAT_blend_minmax {
|
||||
readonly MIN: number
|
||||
readonly MAX: number
|
||||
@@ -146,13 +167,35 @@ export function getColorBufferFloat(gl: GLRenderingContext): COMPAT_color_buffer
|
||||
const ext = gl.getExtension('WEBGL_color_buffer_float');
|
||||
if (ext === null) {
|
||||
// test as support may not be advertised by browsers
|
||||
return testColorBufferFloat() ? { RGBA32F: 0x8814 } : null;
|
||||
gl.getExtension('OES_texture_float');
|
||||
return testColorBuffer(gl, gl.FLOAT) ? { RGBA32F: 0x8814 } : null;
|
||||
}
|
||||
gl.getExtension('EXT_float_blend');
|
||||
return { RGBA32F: ext.RGBA32F_EXT };
|
||||
}
|
||||
}
|
||||
|
||||
export interface COMPAT_color_buffer_half_float {
|
||||
readonly RGBA16F: number;
|
||||
}
|
||||
|
||||
export function getColorBufferHalfFloat(gl: GLRenderingContext): COMPAT_color_buffer_half_float | null {
|
||||
if (isWebGL2(gl)) {
|
||||
if (gl.getExtension('EXT_color_buffer_half_float') === null) return null;
|
||||
gl.getExtension('EXT_float_blend');
|
||||
return { RGBA16F: gl.RGBA16F };
|
||||
} else {
|
||||
const ext = gl.getExtension('EXT_color_buffer_half_float');
|
||||
if (ext === null) {
|
||||
// test as support may not be advertised by browsers
|
||||
gl.getExtension('OES_texture_half_float');
|
||||
return testColorBuffer(gl, 0x8D61) ? { RGBA16F: 0x881A } : null;
|
||||
}
|
||||
gl.getExtension('EXT_float_blend');
|
||||
return { RGBA16F: ext.RGBA16F_EXT };
|
||||
}
|
||||
}
|
||||
|
||||
export interface COMPAT_draw_buffers {
|
||||
drawBuffers(buffers: number[]): void;
|
||||
readonly COLOR_ATTACHMENT0: number;
|
||||
@@ -283,7 +326,7 @@ const TextureTestVertShader = `
|
||||
attribute vec4 aPosition;
|
||||
|
||||
void main() {
|
||||
gl_Position = aPosition;
|
||||
gl_Position = aPosition;
|
||||
}`;
|
||||
|
||||
const TextureTestFragShader = `
|
||||
@@ -292,28 +335,15 @@ uniform vec4 uColor;
|
||||
uniform sampler2D uTexture;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(uTexture, vec2(0.5, 0.5)) * uColor;
|
||||
gl_FragColor = texture2D(uTexture, vec2(0.5, 0.5)) * uColor;
|
||||
}`;
|
||||
|
||||
const TextureTestTexCoords = new Float32Array([
|
||||
-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0
|
||||
]);
|
||||
|
||||
export function testColorBufferFloat() {
|
||||
// adapted from https://stackoverflow.com/questions/28827511/
|
||||
|
||||
// Get A WebGL context
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 16;
|
||||
canvas.height = 16;
|
||||
canvas.style.width = `${16}px`;
|
||||
canvas.style.height = `${16}px`;
|
||||
const gl = getGLContext(canvas);
|
||||
if (gl === null) throw new Error('Unable to get WebGL context');
|
||||
|
||||
const type = gl.FLOAT;
|
||||
gl.getExtension('OES_texture_float');
|
||||
|
||||
// adapted from https://stackoverflow.com/questions/28827511/
|
||||
export function testColorBuffer(gl: GLRenderingContext, type: number) {
|
||||
// setup shaders
|
||||
const vertShader = getShader(gl, { type: 'vert', source: TextureTestVertShader });
|
||||
const fragShader = getShader(gl, { type: 'frag', source: TextureTestFragShader });
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user