Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-reference/mesh-layers/scenegraph-layer.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# ScenegraphLayer
![webgpu](https://img.shields.io/badge/webgpu-supported-blue.svg?style=flat-square)

import {ScenegraphLayerDemo} from '@site/src/doc-demos/mesh-layers';

Expand Down
1 change: 1 addition & 0 deletions docs/developer-guide/webgpu.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Layers will gradually be WebGPU enabled. Layers that support WebGPU can be teste

- [`PointCloudLayer`](https://deck.gl/examples/point-cloud-layer)
- [`ScatterplotLayer`](https://deck.gl/examples/scatterplot-layer)
- [`ScenegraphLayer`](https://deck.gl/examples/scenegraph-layer)

## Features

Expand Down
4 changes: 4 additions & 0 deletions examples/website/scenegraph/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {ScenegraphLayer} from '@deck.gl/mesh-layers';

import type {ScenegraphLayerProps} from '@deck.gl/mesh-layers';
import type {PickingInfo, MapViewState} from '@deck.gl/core';
import type {Device} from '@luma.gl/core';

// Data provided by the OpenSky Network, http://www.opensky-network.org
const DATA_URL = 'https://opensky-network.org/api/states/all';
Expand Down Expand Up @@ -110,10 +111,12 @@ export function useInterval(callback: () => unknown, delay: number) {
}

export default function App({
device,
sizeScale = 25,
onDataLoad,
mapStyle = MAP_STYLE
}: {
device?: Device;
sizeScale?: number;
onDataLoad?: (count: number) => void;
mapStyle?: string;
Expand Down Expand Up @@ -178,6 +181,7 @@ export default function App({

return (
<DeckGL
device={device}
layers={[layer]}
initialViewState={INITIAL_VIEW_STATE}
controller={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@
import type {Matrix4} from '@math.gl/core';
import type {ShaderModule} from '@luma.gl/shadertools';

const uniformBlock = `\
const uniformBlockWGSL = /* wgsl */ `\
struct ScenegraphUniforms {
sizeScale: f32,
sizeMinPixels: f32,
sizeMaxPixels: f32,
sceneModelMatrix: mat4x4<f32>,
composeModelMatrix: f32,
};

@group(0) @binding(20)
var<uniform> scenegraph: ScenegraphUniforms;
`;

const uniformBlockGLSL = /* glsl */ `\
uniform scenegraphUniforms {
float sizeScale;
float sizeMinPixels;
float sizeMaxPixels;
mat4 sceneModelMatrix;
bool composeModelMatrix;
float composeModelMatrix;
} scenegraph;
`;

Expand All @@ -20,13 +33,14 @@ export type ScenegraphProps = {
sizeMinPixels: number;
sizeMaxPixels: number;
sceneModelMatrix: Matrix4;
composeModelMatrix: boolean;
composeModelMatrix: number;
};

export const scenegraphUniforms = {
name: 'scenegraph',
vs: uniformBlock,
fs: uniformBlock,
source: uniformBlockWGSL,
vs: uniformBlockGLSL,
fs: uniformBlockGLSL,
uniformTypes: {
sizeScale: 'f32',
sizeMinPixels: 'f32',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ void main(void) {

float originalSize = project_size_to_pixel(scenegraph.sizeScale);
float clampedSize = clamp(originalSize, scenegraph.sizeMinPixels, scenegraph.sizeMaxPixels);
float sizeRatio = originalSize == 0.0 ? 0.0 : clampedSize / originalSize;

vec3 pos = (instanceModelMatrix * (scenegraph.sceneModelMatrix * vec4(positions, 1.0)).xyz) * scenegraph.sizeScale * (clampedSize / originalSize) + instanceTranslation;
if(scenegraph.composeModelMatrix) {
vec3 pos = (instanceModelMatrix * (scenegraph.sceneModelMatrix * vec4(positions, 1.0)).xyz) * scenegraph.sizeScale * sizeRatio + instanceTranslation;
if(scenegraph.composeModelMatrix > 0.5) {
DECKGL_FILTER_SIZE(pos, geometry);
// using instancePositions as world coordinates
// when using globe mode, this branch does not re-orient the model to align with the surface of the earth
Expand Down
43 changes: 33 additions & 10 deletions modules/mesh-layers/src/scenegraph-layer/scenegraph-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {Layer, project32, picking, log} from '@deck.gl/core';
import type {Device} from '@luma.gl/core';
import {Layer, project32, color, picking, log} from '@deck.gl/core';
import type {Device, RenderPipelineParameters} from '@luma.gl/core';
import {pbrMaterial} from '@luma.gl/shadertools';
import {ScenegraphNode, GroupNode, ModelNode, Model} from '@luma.gl/engine';
import {GLTFAnimator, PBREnvironment, createScenegraphsFromGLTF} from '@luma.gl/gltf';
Expand All @@ -15,6 +15,8 @@ import {MATRIX_ATTRIBUTES, shouldComposeModelMatrix} from '../utils/matrix';
import {scenegraphUniforms, ScenegraphProps} from './scenegraph-layer-uniforms';
import vs from './scenegraph-layer-vertex.glsl';
import fs from './scenegraph-layer-fragment.glsl';
import source from './scenegraph-layer.wgsl';
import {scenegraphPbrMaterial} from './scenegraph-pbr-material';

import {
UpdateParameters,
Expand Down Expand Up @@ -77,7 +79,11 @@ type _ScenegraphLayerProps<DataT> = {
*/
_imageBasedLightingEnvironment?:
| PBREnvironment
| ((context: {gl: WebGL2RenderingContext; layer: ScenegraphLayer<DataT>}) => PBREnvironment);
| ((context: {
device?: Device;
gl?: WebGL2RenderingContext;
layer: ScenegraphLayer<DataT>;
}) => PBREnvironment);

/** Anchor position accessor. */
getPosition?: Accessor<DataT, Position>;
Expand Down Expand Up @@ -182,37 +188,41 @@ export default class ScenegraphLayer<DataT = any, ExtraPropsT extends {} = {}> e
getShaders() {
const defines: {LIGHTING_PBR?: 1} = {};
let pbr;
const isWebGPU = this.context.device?.type === 'webgpu';

if (this.props._lighting === 'pbr') {
pbr = pbrMaterial;
pbr = isWebGPU ? scenegraphPbrMaterial : pbrMaterial;
defines.LIGHTING_PBR = 1;
} else if (isWebGPU) {
pbr = scenegraphPbrMaterial;
} else {
// Dummy shader module needed to handle
// pbrMaterial.pbr_baseColorSampler binding
pbr = {name: 'pbrMaterial'};
}

const modules = [project32, picking, scenegraphUniforms, pbr];
return super.getShaders({defines, vs, fs, modules});
const modules = [project32, color, picking, scenegraphUniforms, pbr];
return super.getShaders({defines, vs, fs, source, modules});
}

initializeState() {
const attributeManager = this.getAttributeManager();
const supportsTransitions = this.context.device.type !== 'webgpu';
// attributeManager is always defined for primitive layers
attributeManager!.addInstanced({
instancePositions: {
size: 3,
type: 'float64',
fp64: this.use64bitPositions(),
accessor: 'getPosition',
transition: true
transition: supportsTransitions
},
instanceColors: {
type: 'unorm8',
size: this.props.colorFormat.length,
accessor: 'getColor',
defaultValue: DEFAULT_COLOR,
transition: true
transition: supportsTransitions
},
instanceModelMatrix: MATRIX_ATTRIBUTES
});
Expand Down Expand Up @@ -330,18 +340,31 @@ export default class ScenegraphLayer<DataT = any, ExtraPropsT extends {} = {}> e
let env: PBREnvironment | undefined;
if (_imageBasedLightingEnvironment) {
if (typeof _imageBasedLightingEnvironment === 'function') {
env = _imageBasedLightingEnvironment({gl: this.context.gl, layer: this});
env = _imageBasedLightingEnvironment({
device: this.context.device,
gl: this.context.gl,
layer: this
});
} else {
env = _imageBasedLightingEnvironment;
}
}

const parameters =
this.context.device.type === 'webgpu'
? ({
depthWriteEnabled: true,
depthCompare: 'less-equal'
} satisfies RenderPipelineParameters)
: undefined;

return {
imageBasedLightingEnvironment: env,
modelOptions: {
id: this.props.id,
isInstanced: true,
bufferLayout: this.getAttributeManager()!.getBufferLayouts(),
parameters,
...this.getShaders()
},
// tangents are not supported
Expand Down Expand Up @@ -373,7 +396,7 @@ export default class ScenegraphLayer<DataT = any, ExtraPropsT extends {} = {}> e
sizeScale,
sizeMinPixels,
sizeMaxPixels,
composeModelMatrix: shouldComposeModelMatrix(viewport, coordinateSystem),
composeModelMatrix: shouldComposeModelMatrix(viewport, coordinateSystem) ? 1 : 0,
sceneModelMatrix: worldMatrix
};

Expand Down
118 changes: 118 additions & 0 deletions modules/mesh-layers/src/scenegraph-layer/scenegraph-layer.wgsl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

export default /* wgsl */ `\
struct VertexInputs {
@location(0) positions: vec3<f32>,
#ifdef HAS_NORMALS
@location(1) normals: vec3<f32>,
#endif
#ifdef HAS_UV
@location(3) texCoords: vec2<f32>,
#endif
@location(6) instancePositions: vec3<f32>,
@location(7) instancePositions64Low: vec3<f32>,
@location(8) instanceColors: vec4<f32>,
@location(9) instancePickingColors: vec3<f32>,
@location(10) instanceModelMatrixCol0: vec3<f32>,
@location(11) instanceModelMatrixCol1: vec3<f32>,
@location(12) instanceModelMatrixCol2: vec3<f32>,
@location(13) instanceTranslation: vec3<f32>,
};

struct FragmentInputs {
@builtin(position) position: vec4<f32>,
@location(0) vColor: vec4<f32>,
@location(1) vTexCoord: vec2<f32>,
@location(2) pbrPosition: vec3<f32>,
@location(3) pbrUV: vec2<f32>,
@location(4) pbrNormal: vec3<f32>,
};

@vertex
fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
var outputs: FragmentInputs;

geometry.worldPosition = inputs.instancePositions;
geometry.pickingColor = inputs.instancePickingColors;

var vertexPosition = inputs.positions;
var texCoord = vec2<f32>(0.0, 0.0);
var normal = vec3<f32>(0.0, 0.0, 1.0);

#ifdef HAS_UV
texCoord = inputs.texCoords;
#endif
#ifdef HAS_NORMALS
normal = inputs.normals;
#endif

geometry.uv = texCoord;

let instanceModelMatrix = mat3x3<f32>(
inputs.instanceModelMatrixCol0,
inputs.instanceModelMatrixCol1,
inputs.instanceModelMatrixCol2
);

let scenePosition = (scenegraph.sceneModelMatrix * vec4<f32>(vertexPosition, 1.0)).xyz;
let worldNormal = instanceModelMatrix * (scenegraph.sceneModelMatrix * vec4<f32>(normal, 0.0)).xyz;

let originalSize = project_meter_size_to_pixel(scenegraph.sizeScale);
let clampedSize = clamp(originalSize, scenegraph.sizeMinPixels, scenegraph.sizeMaxPixels);
let sizeRatio = select(0.0, clampedSize / originalSize, originalSize > 0.0);

let pos =
(instanceModelMatrix * scenePosition) * scenegraph.sizeScale * sizeRatio +
inputs.instanceTranslation;

if (scenegraph.composeModelMatrix > 0.5) {
geometry.normal = project_normal(worldNormal);
geometry.worldPosition = inputs.instancePositions + pos;
geometry.position = vec4<f32>(
project_position_vec3_f64(inputs.instancePositions + pos, inputs.instancePositions64Low),
1.0
);
} else {
let sizeAdjustedPos = project_size_vec3(pos);
geometry.position = vec4<f32>(
project_position_vec3_f64(inputs.instancePositions, inputs.instancePositions64Low) +
sizeAdjustedPos,
1.0
);
geometry.normal = project_normal(worldNormal);
}

outputs.position = project_common_position_to_clipspace(geometry.position);
outputs.vColor = inputs.instanceColors;
outputs.vTexCoord = texCoord;
outputs.pbrPosition = geometry.position.xyz;
outputs.pbrUV = texCoord;
outputs.pbrNormal = geometry.normal;
return outputs;
}

@fragment
fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
fragmentGeometry.uv = inputs.vTexCoord;

var fragColor = inputs.vColor;

#ifdef LIGHTING_PBR
fragmentInputs.pbr_vPosition = inputs.pbrPosition;
fragmentInputs.pbr_vUV = inputs.pbrUV;
fragmentInputs.pbr_vNormal = inputs.pbrNormal;
fragColor = fragColor * pbr_filterColor(vec4<f32>(0.0));
#else
#ifdef HAS_BASECOLORMAP
fragColor =
fragColor *
textureSample(pbr_baseColorSampler, pbr_baseColorSamplerSampler, inputs.vTexCoord);
#endif
#endif

fragColor.a *= color.opacity;
return deckgl_premultiplied_alpha(fragColor);
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {lighting, pbrMaterial} from '@luma.gl/shadertools';

import type {
PBRMaterialBindings,
PBRMaterialProps,
PBRMaterialUniforms,
ShaderModule
} from '@luma.gl/shadertools';

const source = (pbrMaterial.source as string)
.replace(
/fn pbr_setPositionNormalTangentUV\([\s\S]*?\n}\n/,
`fn pbr_setPositionNormalTangentUV(position: vec4f, normal: vec4f, tangent: vec4f, uv: vec2f)
{
fragmentInputs.pbr_vPosition = position.xyz;
fragmentInputs.pbr_vNormal = normal.xyz;
fragmentInputs.pbr_vTBN = mat3x3f(
vec3f(1.0, 0.0, 0.0),
vec3f(0.0, 1.0, 0.0),
vec3f(0.0, 0.0, 1.0)
);
fragmentInputs.pbr_vUV = uv;
}
`
)
.replace(/pbrProjection\.camera/g, 'project.cameraPosition');

export const scenegraphPbrMaterial = {
...pbrMaterial,
dependencies: [lighting],
source
} as const satisfies ShaderModule<PBRMaterialProps, PBRMaterialUniforms, PBRMaterialBindings>;
4 changes: 4 additions & 0 deletions website/src/examples/scenegraph-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class ScenegraphDemo extends Component {

static code = `${GITHUB_TREE}/examples/website/scenegraph`;

static hasDeviceTabs = true;

static parameters = {
sizeScale: {displayName: 'Size', type: 'range', value: 25, step: 5, min: 5, max: 500}
};
Expand Down Expand Up @@ -42,6 +44,8 @@ class ScenegraphDemo extends Component {
return (
<App
{...otherProps}
key={this.props.device?.type}
device={this.props.device}
sizeScale={params.sizeScale.value}
onDataLoad={count => this.props.onStateChange({count})}
/>
Expand Down
Loading