Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3D Extrusion of Buildings #192

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions benchmarks/benches/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fn parse_tile(c: &mut Criterion) {
.into_world_tile(TileAddressingScheme::XYZ)
.unwrap(),
layers: HashSet::from(["boundary".to_owned(), "water".to_owned()]),
style: Default::default(),
};
let data = fetcher
.sync_fetch_tile(&MUNICH_COORDS)
Expand All @@ -54,6 +55,7 @@ fn tessellate_tile(c: &mut Criterion) {
.into_world_tile(TileAddressingScheme::XYZ)
.unwrap(),
layers: HashSet::from(["boundary".to_owned(), "water".to_owned()]),
style: Default::default(),
};
let data = fetcher
.sync_fetch_tile(&MUNICH_COORDS)
Expand Down
7 changes: 5 additions & 2 deletions maplibre/src/coords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,11 @@ impl WorldTileCoords {

// Divide by EXTENT to normalize tile
// Scale tiles where zoom level = self.z to 512x512
let normalize_and_scale =
Matrix4::from_nonuniform_scale(tile_scale / EXTENT, tile_scale / EXTENT, 1.0);
let normalize_and_scale = Matrix4::from_nonuniform_scale(
tile_scale / EXTENT,
tile_scale / EXTENT,
tile_scale / EXTENT,
);
translate * normalize_and_scale
}

Expand Down
1 change: 1 addition & 0 deletions maplibre/src/headless/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ impl HeadlessMap {
.iter()
.map(|layer| layer.to_string())
.collect::<HashSet<String>>(),
style: Default::default(),
},
tile_data,
),
Expand Down
3 changes: 3 additions & 0 deletions maplibre/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ pub mod transferables;

pub use geozero::mvt::tile::Layer as RawLayer;

use crate::style::Style;

/// A request for a tile at the given coordinates and in the given layers.
#[derive(Clone, Serialize, Deserialize)]
pub struct TileRequest {
pub coords: WorldTileCoords,
pub layers: HashSet<String>,
pub style: Style,
}

impl fmt::Debug for TileRequest {
Expand Down
103 changes: 103 additions & 0 deletions maplibre/src/io/tile_pipelines.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashSet;

use cgmath::InnerSpace;
use geozero::GeozeroDatasource;
use prost::Message;

Expand All @@ -9,6 +10,7 @@ use crate::{
pipeline::{DataPipeline, PipelineContext, PipelineEnd, PipelineError, Processable},
TileRequest,
},
render::ShaderVertex,
tessellation::{zero_tessellator::ZeroTessellator, IndexDataType},
};

Expand Down Expand Up @@ -97,6 +99,106 @@ impl Processable for TessellateLayer {
e
);
} else {
let layer_style = tile_request
.style
.layers
.iter()
.find(|layer_style| {
layer.name == *layer_style.source_layer.as_ref().unwrap_or(&"".to_string())
})
.unwrap();

// Extrude all the buildings on the z axis if osm_3d_extrusion is enabled on the layer
if layer_style.extrusion {
// We create a list of all the outer/contour edges. Meaning that these
// edges are not inside the 2d mesh, and a "wall" should be instantiated for them.
// In order to do that, we create a `HashSet` of every edge that appears only
// once in the entire layer.
let mut contour_edges: HashSet<(u32, u32)> =
HashSet::with_capacity(tessellator.buffer.indices.len());
for i in 0..tessellator.buffer.indices.len() {
let a = tessellator.buffer.indices[i];
let b = tessellator.buffer.indices
[if (i + 1) % 3 == 0 { i - 2 } else { i + 1 }];

// If the contour edge already exist, it is an inner edge and not a contour edge so we remove it
if contour_edges.contains(&(b, a)) {
contour_edges.remove(&(b, a));
} else {
contour_edges.insert((a, b));
}
}

// For each "wall" of the buildings, we create 2 triangles in the clockwise
// direction so that their normals are facing outward.
let mut extruded_vertices = vec![];
let mut side_faces_indices = vec![];
for mut edge in contour_edges {
let edge_vector = [
tessellator.buffer.vertices[edge.1 as usize].position[0]
- tessellator.buffer.vertices[edge.0 as usize].position[0],
tessellator.buffer.vertices[edge.1 as usize].position[1]
- tessellator.buffer.vertices[edge.0 as usize].position[1],
0.0,
];
let normal_vector =
cgmath::Vector3::from([-edge_vector[1], edge_vector[0], 0.0])
.normalize()
.into();
let a_position = tessellator.buffer.vertices[edge.0 as usize].position;
let b_position = tessellator.buffer.vertices[edge.1 as usize].position;
extruded_vertices.push(ShaderVertex::new(
[a_position[0], a_position[1], 0.0],
normal_vector,
));
let a = (extruded_vertices.len() + tessellator.buffer.vertices.len() - 1)
as u32;
extruded_vertices.push(ShaderVertex::new(
[b_position[0], b_position[1], 0.0],
normal_vector,
));
let b = (extruded_vertices.len() + tessellator.buffer.vertices.len() - 1)
as u32;
extruded_vertices.push(ShaderVertex::new(
[a_position[0], a_position[1], 40.0],
normal_vector,
));
let a_extruded = (extruded_vertices.len()
+ tessellator.buffer.vertices.len()
- 1) as u32;
extruded_vertices.push(ShaderVertex::new(
[b_position[0], b_position[1], 40.0],
normal_vector,
));
let b_extruded = (extruded_vertices.len()
+ tessellator.buffer.vertices.len()
- 1) as u32;
side_faces_indices.push(a);
side_faces_indices.push(b_extruded);
side_faces_indices.push(a_extruded);
side_faces_indices.push(b);
side_faces_indices.push(b_extruded);
side_faces_indices.push(a);
}

// We move the vertices to the top, because the bottom will not be visible anyway.
for i in 0..tessellator.buffer.vertices.len() {
tessellator.buffer.vertices[i] = ShaderVertex::new(
[
tessellator.buffer.vertices[i].position[0],
tessellator.buffer.vertices[i].position[1],
40.0,
],
tessellator.buffer.vertices[i].normal,
);
}

// We insert the new walls to the buffer.
tessellator.buffer.vertices.extend(extruded_vertices.iter());
tessellator.buffer.indices.extend(side_faces_indices.iter());
}

// We send the tessellated layer to the pipeline.
context.processor_mut().layer_tesselation_finished(
coords,
tessellator.buffer.into(),
Expand Down Expand Up @@ -169,6 +271,7 @@ mod tests {
TileRequest {
coords: (0, 0, ZoomLevel::default()).into(),
layers: Default::default(),
style: Default::default(),
},
Box::new([0]), // TODO: Add proper tile byte array
),
Expand Down
35 changes: 35 additions & 0 deletions maplibre/src/render/shaders/light.fragment.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
struct ShaderCamera {
view_proj: mat4x4<f32>,
view_position: vec4<f32>,
};

struct ShaderLight {
direction: vec4<f32>,
color: vec4<f32>,
}

struct ShaderGlobals {
camera: ShaderCamera,
light: ShaderLight,
};

@group(0) @binding(0) var<uniform> globals: ShaderGlobals;

struct Output {
@location(0) out_color: vec4<f32>,
};

@fragment
fn main(@builtin(position) position: vec4<f32>, @location(0) v_color: vec4<f32>, @location(1) normal: vec3<f32>) -> Output {

// We don't need (or want) much ambient light, so 0.1 is fine
let ambient_strength = 0.1;
let ambient_color = globals.light.color.xyz * ambient_strength;

let diffuse_strength = max(dot(normal, globals.light.direction.xyz), 0.0);
let diffuse_color = globals.light.color.xyz * diffuse_strength;

let result = (ambient_color + diffuse_color) * v_color.xyz;

return Output(vec4<f32>(result, v_color.a));
}
42 changes: 33 additions & 9 deletions maplibre/src/render/shaders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ impl Shader for TileShader {
// position
wgpu::VertexAttribute {
offset: 0,
format: wgpu::VertexFormat::Float32x2,
format: wgpu::VertexFormat::Float32x3,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we should upgrade from 2D to 3D for everything.
This will increase the required bandwidth to the GPU.

But I'm not sure how much the impact is performance wise.

shader_location: 0,
},
// normal
wgpu::VertexAttribute {
offset: wgpu::VertexFormat::Float32x2.size(),
format: wgpu::VertexFormat::Float32x2,
offset: wgpu::VertexFormat::Float32x3.size(),
format: wgpu::VertexFormat::Float32x3,
shader_location: 1,
},
],
Expand Down Expand Up @@ -181,7 +181,7 @@ impl Shader for TileShader {

fn describe_fragment(&self) -> FragmentState {
FragmentState {
source: include_str!("basic.fragment.wgsl"),
source: include_str!("light.fragment.wgsl"),
entry_point: "main",
targets: vec![Some(wgpu::ColorTargetState {
format: self.format,
Expand Down Expand Up @@ -217,36 +217,60 @@ impl Default for ShaderCamera {
}
}

#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct ShaderLight {
direction: Vec4f32,
color: Vec4f32,
}

impl ShaderLight {
pub fn new(direction: Vec4f32, color: Vec4f32) -> Self {
Self { direction, color }
}
}

impl Default for ShaderLight {
fn default() -> Self {
Self {
direction: [-0.3, -0.4, 1.0, 0.0], // Sun orientation
color: [1.0, 1.0, 1.0, 1.0], // Sun color and intensity
}
}
}

#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct ShaderGlobals {
camera: ShaderCamera,
light: ShaderLight,
}

impl ShaderGlobals {
pub fn new(camera_uniform: ShaderCamera) -> Self {
pub fn new(camera_uniform: ShaderCamera, light_uniform: ShaderLight) -> Self {
Self {
camera: camera_uniform,
light: light_uniform,
}
}
}

#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct ShaderVertex {
pub position: Vec2f32,
pub normal: Vec2f32,
pub position: Vec3f32,
pub normal: Vec3f32,
}

impl ShaderVertex {
pub fn new(position: Vec2f32, normal: Vec2f32) -> Self {
pub fn new(position: Vec3f32, normal: Vec3f32) -> Self {
Self { position, normal }
}
}

impl Default for ShaderVertex {
fn default() -> Self {
ShaderVertex::new([0.0, 0.0], [0.0, 0.0])
ShaderVertex::new([0.0, 0.0, 0.0], [0.0, 0.0, 0.0])
}
}

Expand Down
18 changes: 12 additions & 6 deletions maplibre/src/render/shaders/tile.vertex.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@ struct ShaderCamera {
view_position: vec4<f32>,
};

struct ShaderLight {
direction: vec4<f32>,
color: vec4<f32>,
}

struct ShaderGlobals {
camera: ShaderCamera,
light: ShaderLight,
};

@group(0) @binding(0) var<uniform> globals: ShaderGlobals;

struct VertexOutput {
@location(0) v_color: vec4<f32>,
@builtin(position) position: vec4<f32>,
@location(0) v_color: vec4<f32>,
@location(1) normal: vec3<f32>,
};

@vertex
fn main(
@location(0) position: vec2<f32>,
@location(1) normal: vec2<f32>,
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(4) translate1: vec4<f32>,
@location(5) translate2: vec4<f32>,
@location(6) translate3: vec4<f32>,
Expand All @@ -27,17 +34,16 @@ fn main(
@location(10) z_index: f32,
@builtin(instance_index) instance_idx: u32 // instance_index is used when we have multiple instances of the same "object"
) -> VertexOutput {
let z = 0.0;
let width = 3.0 * zoom_factor;

// The following code moves all "invisible" vertices to (0, 0, 0)
//if (color.w == 0.0) {
// return VertexOutput(color, vec4<f32>(0.0, 0.0, 0.0, 1.0));
//}

var position = mat4x4<f32>(translate1, translate2, translate3, translate4) * vec4<f32>(position + normal * width, z, 1.0);
var position = mat4x4<f32>(translate1, translate2, translate3, translate4) * vec4<f32>(position + normal * width, 1.0);
// FIXME: how to fix z-fighting?
position.z = z_index;

return VertexOutput(color, position);
return VertexOutput(position, color, normal);
}
Loading