diff --git a/docs/developer-guide/webgpu.md b/docs/developer-guide/webgpu.md index 688ff42665b..14342d395f2 100644 --- a/docs/developer-guide/webgpu.md +++ b/docs/developer-guide/webgpu.md @@ -41,8 +41,8 @@ The table below covers the public layer exports from the layer packages. It is d | `@deck.gl/layers` | `LineLayer` | ✅ | ✅ | | `@deck.gl/layers` | `PointCloudLayer` | ✅ | ✅ | | `@deck.gl/layers` | `ScatterplotLayer` | ✅ | ✅ | -| `@deck.gl/layers` | `ColumnLayer` | ✅ | ❌ | -| `@deck.gl/layers` | `GridCellLayer` | ✅ | ❌ | +| `@deck.gl/layers` | `ColumnLayer` | ✅ | ✅ | +| `@deck.gl/layers` | `GridCellLayer` | ✅ | ✅ | | `@deck.gl/layers` | `PathLayer` | ✅ | ✅ | | `@deck.gl/layers` | `PolygonLayer` | ✅ | ❌ | | `@deck.gl/layers` | `GeoJsonLayer` | ✅ | ❌ | diff --git a/modules/layers/src/column-layer/column-layer-uniforms.ts b/modules/layers/src/column-layer/column-layer-uniforms.ts index 683a41d18c8..00d3f2a71be 100644 --- a/modules/layers/src/column-layer/column-layer-uniforms.ts +++ b/modules/layers/src/column-layer/column-layer-uniforms.ts @@ -4,6 +4,27 @@ import type {ShaderModule} from '@luma.gl/shadertools'; +const uniformBlockWGSL = /* wgsl */ `\ +struct ColumnUniforms { + radius: f32, + angle: f32, + offset: vec2, + extruded: f32, + stroked: f32, + isStroke: f32, + coverage: f32, + elevationScale: f32, + edgeDistance: f32, + widthScale: f32, + widthMinPixels: f32, + widthMaxPixels: f32, + radiusUnits: i32, + widthUnits: i32, +}; + +@group(0) @binding(3) var column: ColumnUniforms; +`; + const uniformBlock = `\ uniform columnUniforms { float radius; @@ -42,6 +63,7 @@ export type ColumnProps = { export const columnUniforms = { name: 'column', + source: uniformBlockWGSL, vs: uniformBlock, fs: uniformBlock, uniformTypes: { diff --git a/modules/layers/src/column-layer/column-layer.ts b/modules/layers/src/column-layer/column-layer.ts index d529d553e87..15d091dac19 100644 --- a/modules/layers/src/column-layer/column-layer.ts +++ b/modules/layers/src/column-layer/column-layer.ts @@ -23,6 +23,7 @@ import {Model} from '@luma.gl/engine'; import ColumnGeometry from './column-geometry'; import {columnUniforms, ColumnProps} from './column-layer-uniforms'; +import {getColumnLayerWGSL as source} from './column-layer.wgsl'; import vs from './column-layer-vertex.glsl'; import fs from './column-layer-fragment.glsl'; @@ -234,6 +235,7 @@ export default class ColumnLayer exten defines.FLAT_SHADING = 1; } return super.getShaders({ + source: source(flatShading), vs, fs, defines, diff --git a/modules/layers/src/column-layer/column-layer.wgsl.ts b/modules/layers/src/column-layer/column-layer.wgsl.ts new file mode 100644 index 00000000000..270892a046c --- /dev/null +++ b/modules/layers/src/column-layer/column-layer.wgsl.ts @@ -0,0 +1,216 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +const sharedSource = /* wgsl */ `\ +struct Attributes { + @location(0) positions: vec3, + @location(1) normals: vec3, + @location(2) instancePositions: vec3, + @location(3) instancePositions64Low: vec3, + @location(4) instanceElevations: f32, + @location(5) instanceFillColors: vec4, + @location(6) instanceLineColors: vec4, + @location(7) instanceStrokeWidths: f32, + @location(8) instancePickingColors: vec3 +}; + +fn getRotationMatrix(angle: f32) -> mat2x2 { + let s = sin(angle); + let c = cos(angle); + return mat2x2( + vec2(c, s), + vec2(-s, c) + ); +} + +fn getOffset( + positions: vec3, + strokeOffsetRatio: f32, + dotRadius: f32, + rotationMatrix: mat2x2 +) -> vec3 { + var offset = (rotationMatrix * positions.xy * strokeOffsetRatio + column.offset) * dotRadius; + if (column.radiusUnits == UNIT_METERS) { + offset = project_size_vec2(offset); + } else if (column.radiusUnits == UNIT_PIXELS) { + offset = project_pixel_size_vec2(offset); + } + return vec3(offset, 0.0); +} +`; + +const smoothSource = /* wgsl */ `\ +${sharedSource} + +struct Varyings { + @builtin(position) position: vec4, + @location(0) color: vec4 +}; + +@vertex +fn vertexMain(attributes: Attributes) -> Varyings { + var varyings: Varyings; + + geometry.worldPosition = attributes.instancePositions; + geometry.pickingColor = attributes.instancePickingColors; + + let isStroke = column.isStroke > 0.5; + let baseColor = select(attributes.instanceFillColors, attributes.instanceLineColors, isStroke); + let rotationMatrix = getRotationMatrix(column.angle); + + var elevation = 0.0; + var strokeOffsetRatio = 1.0; + + if (column.extruded > 0.5) { + elevation = + attributes.instanceElevations * (attributes.positions.z + 1.0) / 2.0 * column.elevationScale; + } else if (column.stroked > 0.5) { + let widthPixels = clamp( + project_unit_size_to_pixel(attributes.instanceStrokeWidths * column.widthScale, column.widthUnits), + column.widthMinPixels, + column.widthMaxPixels + ) / 2.0; + let halfOffset = + project_pixel_size_float(widthPixels) / + project_size_float(column.edgeDistance * column.coverage * column.radius); + if (isStroke) { + strokeOffsetRatio -= sign(attributes.positions.z) * halfOffset; + } else { + strokeOffsetRatio -= halfOffset; + } + } + + let shouldRender = select(0.0, 1.0, baseColor.a > 0.0 && attributes.instanceElevations >= 0.0); + let dotRadius = column.radius * column.coverage * shouldRender; + let centroidPosition = + vec3( + attributes.instancePositions.xy, + attributes.instancePositions.z + elevation + ); + let offset = getOffset(attributes.positions, strokeOffsetRatio, dotRadius, rotationMatrix); + let projected = project_position_to_clipspace_and_commonspace( + centroidPosition, + attributes.instancePositions64Low, + offset + ); + + geometry.position = projected.commonPosition; + geometry.normal = project_normal(vec3(rotationMatrix * attributes.normals.xy, attributes.normals.z)); + + let lightColor = lighting_getLightColor2( + baseColor.rgb, + project.cameraPosition, + geometry.position.xyz, + geometry.normal + ); + + varyings.position = projected.clipPosition; + varyings.color = vec4( + select(baseColor.rgb, lightColor, column.extruded > 0.5 && !isStroke), + baseColor.a * color.opacity + ); + + return varyings; +} + +@fragment +fn fragmentMain(varyings: Varyings) -> @location(0) vec4 { + geometry.uv = vec2(0.0); + return deckgl_premultiplied_alpha(varyings.color); +} +`; + +const flatSource = /* wgsl */ `\ +${sharedSource} + +struct Varyings { + @builtin(position) position: vec4, + @location(0) color: vec4, + @location(1) cameraPosition: vec3, + @location(2) positionCommonspace: vec4 +}; + +@vertex +fn vertexMain(attributes: Attributes) -> Varyings { + var varyings: Varyings; + + geometry.worldPosition = attributes.instancePositions; + geometry.pickingColor = attributes.instancePickingColors; + + let isStroke = column.isStroke > 0.5; + let baseColor = select(attributes.instanceFillColors, attributes.instanceLineColors, isStroke); + let rotationMatrix = getRotationMatrix(column.angle); + + var elevation = 0.0; + var strokeOffsetRatio = 1.0; + + if (column.extruded > 0.5) { + elevation = + attributes.instanceElevations * (attributes.positions.z + 1.0) / 2.0 * column.elevationScale; + } else if (column.stroked > 0.5) { + let widthPixels = clamp( + project_unit_size_to_pixel(attributes.instanceStrokeWidths * column.widthScale, column.widthUnits), + column.widthMinPixels, + column.widthMaxPixels + ) / 2.0; + let halfOffset = + project_pixel_size_float(widthPixels) / + project_size_float(column.edgeDistance * column.coverage * column.radius); + if (isStroke) { + strokeOffsetRatio -= sign(attributes.positions.z) * halfOffset; + } else { + strokeOffsetRatio -= halfOffset; + } + } + + let shouldRender = select(0.0, 1.0, baseColor.a > 0.0 && attributes.instanceElevations >= 0.0); + let dotRadius = column.radius * column.coverage * shouldRender; + let centroidPosition = + vec3( + attributes.instancePositions.xy, + attributes.instancePositions.z + elevation + ); + let offset = getOffset(attributes.positions, strokeOffsetRatio, dotRadius, rotationMatrix); + let projected = project_position_to_clipspace_and_commonspace( + centroidPosition, + attributes.instancePositions64Low, + offset + ); + + geometry.position = projected.commonPosition; + geometry.normal = project_normal(vec3(rotationMatrix * attributes.normals.xy, attributes.normals.z)); + + varyings.position = projected.clipPosition; + varyings.color = vec4(baseColor.rgb, baseColor.a * color.opacity); + varyings.cameraPosition = project.cameraPosition; + varyings.positionCommonspace = projected.commonPosition; + + return varyings; +} + +@fragment +fn fragmentMain(varyings: Varyings) -> @location(0) vec4 { + geometry.uv = vec2(0.0); + + var fragColor = varyings.color; + if (column.extruded > 0.5 && column.isStroke < 0.5) { + let normal = normalize(cross(dpdx(varyings.positionCommonspace.xyz), dpdy(varyings.positionCommonspace.xyz))); + fragColor = vec4( + lighting_getLightColor2( + varyings.color.rgb, + varyings.cameraPosition, + varyings.positionCommonspace.xyz, + normal + ), + varyings.color.a + ); + } + + return deckgl_premultiplied_alpha(fragColor); +} +`; + +export function getColumnLayerWGSL(flatShading: boolean): string { + return flatShading ? flatSource : smoothSource; +}