Skip to content

Building 3D Graphics Engine with GPU #12

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

Draft
wants to merge 8 commits into
base: master
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
473 changes: 121 additions & 352 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
[package]
name = "twoderaycaster"
name = "threedee"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
image = "0.24.6"
pixels = "0.13.0"
winit = "0.28.6"
winit_input_helper = "0.14.1"
winit = "0.28"
env_logger = "0.10"
log = "0.4"
wgpu = "0.16"
pollster = "0.3.0"
bytemuck = { version = "1.13.1", features = ["derive"] }
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# twodee raycaster
A very basic 2D raycaster built with Rust
# 3D Graphics Engine
Basic Graphics Engine built with `wgpu` in Rust

![Demo picture](https://github.com/manorajesh/twodeeraycaster/blob/master/images/demo.png)
![Demo picture](https://github.com/manorajesh/rusty-graphics/blob/wgpu/images/demo.png)

## Installation
```
git clone https://github.com/manorajesh/twodeeraycaster.git && cd twodeeraycaster
git clone https://github.com/manorajesh/rusty-graphics.git && cd rusty-graphics
cargo run
```

## Usage
Use the arrow keys to traverse the extremely entertaining room and watch the shadows

#### Important Code
The [`draw`](https://github.com/manorajesh/twodeeraycaster/blob/cdf31fba1238801ae4804fe2ce98fec9d935985d/src/raycaster.rs#L147-L207) function is responsible for casting the rays and rendering them accordingly.
The [`draw`](https://github.com/manorajesh/rusty-graphics/blob/cdf31fba1238801ae4804fe2ce98fec9d935985d/src/raycaster.rs#L147-L207) function is responsible for casting the rays and rendering them accordingly.
Binary file removed assets/map.png
Binary file not shown.
Binary file modified images/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
333 changes: 333 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
use wgpu::util::DeviceExt;
use winit::{
event::*,
event_loop::{ControlFlow, EventLoop},
window::{WindowBuilder, Window},
};
use bytemuck;

struct State {
surface: wgpu::Surface,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
size: winit::dpi::PhysicalSize<u32>,
window: Window,
render_pipeline: wgpu::RenderPipeline,
vertex_buffer: wgpu::Buffer,
num_vertices: u32,
index_buffer: wgpu::Buffer,
num_indices: u32,
}

impl State {
// Creating some of the wgpu types requires async code
async fn new(window: Window) -> Self {
let size = window.inner_size();

// The instance is a handle to our GPU
// Backends::all => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
dx12_shader_compiler: Default::default(),
});

// # Safety
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();

let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
},
).await.unwrap();

let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some.
limits: wgpu::Limits::default(),
label: None,
},
None, // Trace path
).await.unwrap();

let surface_caps = surface.get_capabilities(&adapter);
// Shader code in this tutorial assumes an sRGB surface texture. Using a different
// one will result all the colors coming out darker. If you want to support non
// sRGB surfaces, you'll need to account for that when drawing to the frame.
let surface_format = surface_caps.formats.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
};
surface.configure(&device, &config);

// Configuring render pipeline with shader code
let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_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: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
// Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
polygon_mode: wgpu::PolygonMode::Fill,
// Requires Features::DEPTH_CLIP_CONTROL
unclipped_depth: false,
// Requires Features::CONSERVATIVE_RASTERIZATION
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});

// Creating vertex buffer
let vertex_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(VERTICES),
usage: wgpu::BufferUsages::VERTEX,
}
);
let num_vertices = VERTICES.len() as u32;

// Create index buffer for better memory use
let index_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(INDICES),
usage: wgpu::BufferUsages::INDEX,
}
);
let num_indices = INDICES.len() as u32;

Self {
window,
surface,
device,
queue,
config,
size,
render_pipeline,
vertex_buffer,
num_vertices,
index_buffer,
num_indices,
}
}

pub fn window(&self) -> &Window {
&self.window
}

pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
}
}

fn input(&mut self, _event: &WindowEvent) -> bool {
false
}

fn update(&mut self) {
// todo!()
}

fn render(&mut self, color: [f64; 4]) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?;
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});

{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: color[0],
g: color[1],
b: color[2],
a: color[3],
}),
store: true,
},
})],
depth_stencil_attachment: None,
});

render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
}

// submit will accept anything that implements IntoIter
self.queue.submit(std::iter::once(encoder.finish()));
output.present();

Ok(())

}
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
position: [f32; 3],
color: [f32; 3],
}

impl Vertex {
fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
}
]
}
}
}

const VERTICES: &[Vertex] = &[
Vertex { position: [-0.0868241, 0.49240386, 0.0], color: [0.5, 0.0, 0.5] },
Vertex { position: [-0.49513406, 0.06958647, 0.0], color: [0.5, 0.0, 0.5] },
Vertex { position: [-0.21918549, -0.44939706, 0.0], color: [0.5, 0.0, 0.5] },
Vertex { position: [0.35966998, -0.3473291, 0.0], color: [0.5, 0.0, 0.5] },
Vertex { position: [0.44147372, 0.2347359, 0.0], color: [0.5, 0.0, 0.5] },
];

const INDICES: &[u16] = &[
0, 1, 4,
1, 2, 4,
2, 3, 4,
];

pub async fn run() {
env_logger::init();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

let mut state = State::new(window).await;
let mut color = [0.0, 0.0, 0.0, 1.0];

event_loop.run(move |event, _, control_flow| {
match event {
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render(color) {
Ok(_) => {}
// Reconfigure the surface if lost
Err(wgpu::SurfaceError::Lost) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e),
}
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
state.window().request_redraw();
}

Event::WindowEvent {
ref event,
window_id,
} if window_id == state.window().id() => if !state.input(event) { // UPDATED!
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
} => *control_flow = ControlFlow::Exit,

WindowEvent::Resized(physical_size) => {
state.resize(*physical_size);
}

WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
state.resize(**new_inner_size);
}

WindowEvent::CursorMoved { position, .. } => {
color = [
position.x / state.size.width as f64,
position.y / state.size.height as f64,
position.x / state.size.width as f64,
1.0,
];
}
_ => {}
}
}
_ => {}
}
});
}

Loading