Skip to content

Commit

Permalink
procedural spheres completed
Browse files Browse the repository at this point in the history
  • Loading branch information
BarthPaleologue committed May 12, 2023
1 parent 7a107e6 commit bbac066
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gilgamesh"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
description = "A small 3D rendering engine built upon WGPU with the primary goal of visualizing procedural terrains."
license = "Apache-2.0"
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ extern crate gilgamesh;
use gilgamesh::{init_gilgamesh, start_gilgamesh};
use gilgamesh::mesh::Mesh;

#[test]
fn main() {
let mut app = init_gilgamesh();

let procedural_plane = Mesh::new_procedural_terrain(10.0, 64, &|x: f32, y: f32| x.sin() * y.sin(), &mut app.engine);
let sphere = Mesh::new_procedural_sphere(5.0, 32, &|x, y, z| {
f32::powi(f32::sin(60.0 * x * y * z), 2) / 2.0
}, 0.5, &mut app.engine);

app.scene.add_mesh(procedural_plane);
app.scene.add_mesh(sphere);

start_gilgamesh(app);
}
Expand Down
File renamed without changes.
8 changes: 3 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
extern crate gilgamesh;

use std::rc::Rc;
use gilgamesh::{init_gilgamesh, start_gilgamesh};
use gilgamesh::material::Material;
use gilgamesh::mesh::Mesh;

fn main() {
let mut app = init_gilgamesh();

let sphere = Mesh::new_procedural_sphere(10.0, 20, &|x, y, z| {
f32::powi(f32::sin(100.0 * x * y * z), 2) / 2.0
}, &mut app.engine);
let sphere = Mesh::new_procedural_sphere(5.0, 32, &|x, y, z| {
f32::powi(f32::sin(60.0 * x * y * z), 2) * 0.5
}, 0.5, &mut app.engine);

app.scene.add_mesh(sphere);

Expand Down
123 changes: 120 additions & 3 deletions src/material.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bytemuck::cast_slice;
use cgmath::{Matrix4, SquareMatrix};
use wgpu::{BindGroup, BindGroupLayout, Buffer, PipelineLayout, RenderPass, RenderPipeline, ShaderModule};
use wgpu::util::DeviceExt;
use wgpu::util::{DeviceExt};

use crate::engine::Engine;
use crate::mesh::Vertex;
Expand Down Expand Up @@ -105,10 +105,10 @@ impl Material {
}
}

pub fn new_terrain(max_height: f32, engine: &mut Engine) -> Material {
pub fn new_2d_terrain(max_height: f32, engine: &mut Engine) -> Material {
let shader = engine.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("terrain.wgsl").into()),
source: wgpu::ShaderSource::Wgsl(include_str!("./flat_terrain.wgsl").into()),
});

let mvp: Matrix4<f32> = Matrix4::identity();
Expand Down Expand Up @@ -221,6 +221,123 @@ impl Material {
}
}

pub fn new_sphere_terrain(sphere_radius: f32, max_height: f32, engine: &mut Engine) -> Material {
let shader = engine.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("./sphere_terrain.wgsl").into()),
});

let mvp: Matrix4<f32> = Matrix4::identity();
let mvp_ref: &[f32; 16] = mvp.as_ref();
let vertex_uniform_buffer = engine.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Uniform Buffer"),
contents: cast_slice(mvp_ref),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});

// create fragment uniform buffer. here we set eye_position = camera_position and light_position = eye_position
let fragment_uniform_buffer = engine.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Fragment Uniform Buffer"),
size: 48,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});

// store light and eye positions
let light_dir: &[f32; 3] = &[1.0, 1.0, 0.5];
let camera_position: &[f32; 3] = &[0.0, 0.0, 0.0];
engine.queue.write_buffer(&fragment_uniform_buffer, 0, cast_slice(light_dir));
engine.queue.write_buffer(&fragment_uniform_buffer, 12, cast_slice(camera_position));
engine.queue.write_buffer(&fragment_uniform_buffer, 28, cast_slice(&max_height.to_ne_bytes()));
engine.queue.write_buffer(&fragment_uniform_buffer, 32, cast_slice(&sphere_radius.to_ne_bytes()));

let uniform_bind_group_layout = engine.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}, wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("Uniform Bind Group Layout"),
});

let uniform_bind_group = engine.device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: vertex_uniform_buffer.as_entire_binding(),
}, wgpu::BindGroupEntry {
binding: 1,
resource: fragment_uniform_buffer.as_entire_binding(),
}],
label: Some("Uniform Bind Group"),
});

let pipeline_layout = engine.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[&uniform_bind_group_layout],
push_constant_ranges: &[],
});

let pipeline = engine.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: engine.config.format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent::REPLACE,
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
polygon_mode: wgpu::PolygonMode::Fill,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::LessEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
});

Material {
shader_module: shader,
vertex_uniform_buffer,
uniform_bind_group_layout,
uniform_bind_group,
pipeline_layout,
pipeline,
}
}

pub fn bind<'a, 'b>(&'a self, render_pass: &'b mut RenderPass<'a>) -> () {
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
Expand Down
29 changes: 20 additions & 9 deletions src/procedural.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ use crate::material::Material;
use crate::mesh::Mesh;

impl Mesh {
/// Creates a new procedural 2D terrain.
/// It is made of a subdivided plane, with a given `size` and number of subdivisions (`nb_subdivisions`)
/// The `height_fn` takes x and z as parameters and is used to set the y coordinate of each vertex.
/// The `max_height` parameter is used to scale the y coordinate of each vertex in the range [0, 1]
/// `engine` is a mutable reference to the Gilgamesh engine.
/// It returns a Mesh that can be moved with its transform and with a default terrain material.
pub fn new_procedural_terrain(size: f32, nb_subdivisions: u32, height_fn: &dyn Fn(f32, f32) -> f32, max_height: f32, engine: &mut Engine) -> Mesh {
let mut positions = vec!([0.0, 0.0, 0.0]; (nb_subdivisions * nb_subdivisions) as usize);
let mut indices = vec!(0; (6 * (nb_subdivisions - 1) * (nb_subdivisions - 1)) as usize);
Expand All @@ -30,24 +36,26 @@ impl Mesh {
}

let mut mesh = Mesh::from_vertex_data(indices, positions, None, engine);
mesh.material = Rc::from(Material::new_terrain(max_height, engine));
mesh.material = Rc::from(Material::new_2d_terrain(max_height, engine));

mesh
}

pub fn new_procedural_plane(size: f32, nb_subdivisions: u32, engine: &mut Engine) -> Mesh {
Mesh::new_procedural_terrain(size, nb_subdivisions, &|_, _| 0.0, 1.0, engine)
}

pub fn new_procedural_sphere(diameter: f32, nb_subdivisions: u32, height_fn: &dyn Fn(f32, f32, f32) -> f32, engine: &mut Engine) -> Mesh {
/// Creates a new procedural 3D sphere.
/// It is made of a subdivided icosahedron, with a given `diameter` and number of subdivisions (`nb_subdivisions`)
/// The `height_fn` takes x, y and z as parameters and is used to set the height of each vertex above the surface of the sphere.
/// The `max_height` parameter is used to scale the height of each vertex in the range [0, 1]
/// `engine` is a mutable reference to the Gilgamesh engine.
pub fn new_procedural_sphere(diameter: f32, nb_subdivisions: u32, height_fn: &dyn Fn(f32, f32, f32) -> f32, max_height: f32, engine: &mut Engine) -> Mesh {
let sphere = IcoSphere::new(nb_subdivisions as usize, |_| ());
let vertices_raw = sphere.raw_points();
let mut vertices: Vec<[f32;3]> = Vec::with_capacity(vertices_raw.len());
let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(vertices_raw.len());

for vertex in vertices_raw {
let unit_vertex = [vertex[0], vertex[1], vertex[2]];

let height = height_fn(vertex[0], vertex[1], vertex[2]) as f32;
let original_vertex = [vertex[0] * diameter / 4.0, vertex[1] * diameter / 4.0, vertex[2] * diameter / 4.0];
let original_vertex = [vertex[0] * diameter / 2.0, vertex[1] * diameter / 2.0, vertex[2] * diameter / 2.0];

let moved_vertex = [original_vertex[0] + unit_vertex[0] * height, original_vertex[1] + unit_vertex[1] * height, original_vertex[2] + unit_vertex[2] * height];

Expand All @@ -56,6 +64,9 @@ impl Mesh {

let indices = sphere.get_all_indices();

Mesh::from_vertex_data(indices, vertices, None, engine)
let mut mesh = Mesh::from_vertex_data(indices, vertices, None, engine);
mesh.material = Rc::from(Material::new_sphere_terrain(diameter / 2.0, max_height, engine));

mesh
}
}
55 changes: 55 additions & 0 deletions src/sphere_terrain.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// vertex shader

struct Uniforms {
MVP: mat4x4<f32>
}
@binding(0) @group(0) var<uniform> uniforms: Uniforms;

struct VertexInput {
@location(0) pos: vec4<f32>,
@location(1) color: vec4<f32>,
@location(2) normal: vec4<f32>
};

struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) vPosition: vec3<f32>,
@location(1) vColor: vec4<f32>,
@location(2) vNormal: vec3<f32>
};

@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var output: VertexOutput;
output.position = uniforms.MVP * in.pos;
output.vPosition = in.pos.xyz;
output.vColor = in.color;
output.vNormal = in.normal.xyz;
return output;
}

struct FragUniforms {
light_dir: vec3<f32>,
camera_position: vec3<f32>,
max_height: f32,
sphere_radius: f32
};
@binding(1) @group(0) var<uniform> frag_uniforms : FragUniforms;

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let height01 = (length(in.vPosition) - frag_uniforms.sphere_radius) / frag_uniforms.max_height;
let grass_color = vec3(0.0, 0.5, 0.0);
let snow_color = vec3(1.0, 1.0, 1.0);

let flat_color = mix(grass_color, snow_color, smoothstep(0.5, 0.6, height01));

let slope = 1.0 - pow(dot(normalize(in.vNormal), normalize(in.vPosition)), 32.0);
let slope_color = vec3(0.2, 0.1, 0.1);

let ndl: f32 = max(dot(in.vNormal, normalize(frag_uniforms.light_dir)), 0.01);

let color: vec3<f32> = mix(flat_color, slope_color, smoothstep(0.8, 0.9, slope));

return vec4(ndl * color, 1.0);
}

0 comments on commit bbac066

Please sign in to comment.