Compare commits

..

4 Commits

20 changed files with 226 additions and 80 deletions

View File

@ -1,6 +1,7 @@
[workspace]
members = [
"editor"
"editor",
"replicant"
]
[package]
@ -23,7 +24,7 @@ slab = "^0.4"
smallmap = "^1.0"
smallvec = "^1.0"
strum = { version = "0.24", features = ["derive"] }
wgpu = "^0.13"
wgpu = "^0.12"
winit = "0.26"
[dependencies.legion]
@ -31,7 +32,7 @@ version = "^0.4"
optional = true
[dependencies.naga]
version = "0.9"
version = "0.8.5"
features = ["wgsl-in", "glsl-in", "wgsl-out", "serialize", "deserialize"]
[[bin]]

View File

@ -7,7 +7,7 @@
> A repulsive reaper of all of human consciousness -- but mostly SIGGRAPH presentations.
> H.R Giger's irreconcilable reality... if H.R. Giger was a computer programmer.
Cyborg is an experimental, GPU-driven rendering engine using Rust and wgpu.
Cyborg is a free-form rendering engine that we mash whatever we feel like into.
It's a test bed for all sorts of modern rendering technology. Our goal is to take
techniques and features from modern game engines and reimplement them on our own, for
the sake of education, performance, and reusability. We wanna *make shit work.*
@ -16,15 +16,16 @@ We also want to give artists a playground for generating all sorts of unique
3D visuals -- custom shaders, procedurally generated meshes and textures,
and a focus on *interesting* visuals.
Realism is a non-goal!
Make shrooms obsolete.
Go wild.
Modularity and reusability are important. We want to be able to unplug certain
Modularity and reusability are important. We wanna be able to unplug certain
parts of the rendering pipeline, upgrade them, fix them, document them, commit
them, tag them, etc., then stick them back in in a different place to end up
with a different resulting image.
Cyborg is licensed under the GPL-3.0. Yes, this does mean that any software using
Cyborg will be required to make its source code public. But it also means that
Cyborg is licensed under the GPL-3.0. Yes, this does mean that Bevy games using
Cyborg will be required to make their source code public. But it also means that
new visuals added to Cyborg will become available to all other developers and
artists to use. Hopefully, this will expand the collective knowledge of how different
rendering effects work, and decrease the pressure on graphics programmers to
@ -36,3 +37,10 @@ gathered from countless programmers and individual projects.
> Resistance is futile.
>
> -- The Borg
Cyborg is based on [Bevy](https://bevyengine.org/), a "refreshingly simple"
game engine written in Rust. Bevy provides a lot of useful utilities like
ECS-based data parallelization and a modular plugin system. Cyborg is integrated
into the rest of the Bevy ecosystem, and can also work as a replacement Bevy's
provided rendering engine, giving Bevy games GPU-powered, procedurally-generated
content and access to modern rendering optimizations.

View File

@ -7,16 +7,16 @@ edition = "2021"
bytemuck = "^1.0"
crossbeam-channel = "^0.5"
cyborg = { path = "../", features = ["legion"] }
egui = "0.18"
egui-winit = "0.18.0"
egui_wgpu_backend = "0.18.0"
egui = "0.17.0"
egui-winit = "0.17.0"
egui_wgpu_backend = "0.17.0"
glam = { version = "0.20", features = ["serde"] }
gltf = { version = "1.0", features = ["utils"] }
legion = "^0.4"
parking_lot = "^0.11"
pollster = "0.2"
puffin = "^0.13"
puffin_egui = "0.16.0"
puffin_egui = "0.14.0"
rfd = "^0.8"
stl = "0.2.1"

View File

@ -60,10 +60,10 @@ impl Application {
.unwrap();
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: *surface.get_supported_formats(&adapter).first().unwrap(),
format: surface.get_preferred_format(&adapter).unwrap(),
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Fifo,
present_mode: wgpu::PresentMode::Mailbox,
};
surface.configure(&device, &config);

View File

@ -204,7 +204,7 @@ impl RenderState {
let render_schedule = render_schedule.build();
// make debug draw grid
let grid_size = 100;
let grid_size = 10;
let grid_color = [0.0, 1.0, 0.0];
let mut grid_vertices = Vec::new();

View File

@ -15,10 +15,7 @@ pub struct ScriptData {
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub struct Transform {
#[serde(default)]
pub position: glam::Vec3,
#[serde(default)]
pub orientation: glam::Quat,
}

8
replicant/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "replicant"
version = "0.1.0"
edition = "2021"
[dependencies]
glam = "0.21"
termtree = "0.4"

78
replicant/src/fields.rs Normal file
View File

@ -0,0 +1,78 @@
use termtree;
use glam::{Vec3A};
use std::fmt;
pub trait Field: fmt::Debug {
fn sample(&self, pos: Vec3A) -> f32;
}
#[derive(Debug)]
pub enum FieldOp {
Add,
Sub,
Mul,
Div,
}
impl fmt::Display for FieldOp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut name = String::new();
match *self {
Add => { name = "+".to_string() },
Sub => { name = "-".to_string() },
Mul => { name = "*".to_string() },
Div => { name = "/".to_string() },
_ => { name = "?".to_string() }
}
write!(f, "{}", name)
}
}
use FieldOp::*;
#[derive(Debug)]
pub struct FieldNode {
pub field: Box<dyn Field>,
pub op: FieldOp,
pub children: Vec<FieldNode>,
}
impl FieldNode {
pub fn sample(&self, pos: Vec3A) -> f32 {
let mut result = self.field.sample(pos);
for child in self.children.iter() {
let child_result = child.sample(pos);
result = match child.op {
Add => { result + child_result },
Sub => { result - child_result },
Mul => { result * child_result },
Div => { result / child_result }
}
}
result
}
pub fn to_termtree(&self) -> termtree::Tree<String> {
// Turn the node structure into a tree
let mut node_label = String::new();
if self.children.is_empty() {
node_label = format!("{} {:?}", self.op, self.field);
} else {
node_label = format!("{} {:?} = {:?}", self.op, self.field, self.sample(Vec3A::ZERO));
}
let mut tree: termtree::Tree<String> = termtree::Tree::new(node_label);
for child in self.children.iter() {
tree.push(child.to_termtree());
}
tree
}
}
impl fmt::Display for FieldNode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_termtree())
}
}

1
replicant/src/lib.rs Normal file
View File

@ -0,0 +1 @@
pub mod fields;

62
replicant/src/main.rs Normal file
View File

@ -0,0 +1,62 @@
use glam::{Vec3A};
use replicant::fields::{Field, FieldOp, FieldNode};
use FieldOp::*;
#[derive(Debug)]
struct Sphere {
origin: Vec3A,
radius: f32,
}
impl Field for Sphere {
fn sample(&self, pos: Vec3A) -> f32 {
self.origin.distance(pos) - self.radius
}
}
#[derive(Debug)]
struct Rect {
origin: Vec3A,
extent: Vec3A,
}
impl Field for Rect {
fn sample(&self, pos: Vec3A) -> f32 {
let q: Vec3A = pos.abs() - self.extent;
Vec3A::length(Vec3A::max(q, Vec3A::ZERO)) + f32::min(f32::max(q.x, f32::max(q.y, q.z)), 0.0)
}
}
/*
float sdPlane( vec3 p, vec3 n, float h )
{
// n must be normalized
return dot(p,n) + h;
}
*/
#[derive(Debug)]
struct Plane {
height: f32,
normal: Vec3A,
}
impl Field for Plane {
fn sample(&self, pos: Vec3A) -> f32 {
Vec3A::dot(pos, self.normal) + self.height
}
}
fn main() {
let mut float_nodes: FieldNode = FieldNode { field: Box::new(Sphere { origin: Vec3A::new(0.0, 0.0, 10.0), radius: 10.0 }), op: Add, children: vec![
FieldNode { field: Box::new(Rect { origin: Vec3A::new(0.0, 0.0, 0.0), extent: Vec3A::new(5.0, 5.0, 5.0) }), op: Add, children: Vec::new() },
FieldNode { field: Box::new(Plane { height: 0.0, normal: Vec3A::new(0.0, 0.0, 1.0) }), op: Sub, children: Vec::new() },
] };
println!("Node tree:\n{}", float_nodes);
let field = Rect { origin: Vec3A::ZERO, extent: Vec3A::new(10.0, 10.0, 10.0) };
let sample_pos = Vec3A::new(11.0, 11.0, 11.0);
println!("{:?}", field);
println!("Distance at {:?} is {}", sample_pos, field.sample(sample_pos));
}

View File

@ -1,30 +1,25 @@
struct CameraUniform {
eye: vec4<f32>,
vp: mat4x4<f32>,
eye: vec4<f32>;
vp: mat4x4<f32>;
};
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tan_frame: u32,
[[location(0)]] position: vec3<f32>;
[[location(1)]] tan_frame: u32;
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) position: vec3<f32>,
@location(1) color: vec3<f32>,
[[builtin(position)]] clip_position: vec4<f32>;
[[location(0)]] position: vec3<f32>;
};
@group(0) @binding(0)
[[group(0), binding(0)]]
var<uniform> camera: CameraUniform;
fn random(seed: u32, salt: f32) -> f32 {
return abs(sin((f32(seed & u32(0x11111)) * 0.7071 + salt) * 78.233));
}
@vertex
[[stage(vertex)]]
fn vs_main(
@builtin(instance_index) mesh_idx: u32,
@builtin(vertex_index) vertex_idx: u32,
[[builtin(instance_index)]] mesh_idx: u32,
[[builtin(vertex_index)]] vertex_idx: u32,
vertex: VertexInput,
) -> VertexOutput {
let world_pos = vertex.position;
@ -32,15 +27,12 @@ fn vs_main(
var out: VertexOutput;
out.clip_position = camera.vp * vec4<f32>(world_pos, 1.0);
out.position = world_pos;
out.color.r = random(vertex_idx, f32(1.0) + world_pos.x);
out.color.g = random(vertex_idx, f32(2.0) + world_pos.y);
out.color.b = random(vertex_idx, f32(3.0) + world_pos.z);
return out;
}
@fragment
[[stage(fragment)]]
fn fs_main(
frag: VertexOutput,
) -> @location(0) vec4<f32> {
return vec4<f32>(frag.color, 1.0);
) -> [[location(0)]] vec4<f32> {
return vec4<f32>(sin(frag.position) * vec3<f32>(0.5) + vec3<f32>(0.5), 1.0);
}

View File

@ -2,33 +2,32 @@
#include skin.wgsl
struct SkinningUniform {
transform: mat4x4<f32>,
src_offset: u32,
dst_offset: u32,
count: u32,
transform: mat4x4<f32>;
src_offset: u32;
dst_offset: u32;
count: u32;
};
@group(0) @binding(0)
[[group(0), binding(0)]]
var<storage,read> skinning_ubo: SkinningUniform;
@group(0) @binding(1)
[[group(0), binding(1)]]
var<storage,write> dst_vertices: SkinnedVertexArray;
@group(0) @binding(2)
[[group(0), binding(2)]]
var<storage,read> src_vertices: SkinnedVertexArray;
@compute
@workgroup_size(64)
[[stage(compute), workgroup_size(64)]]
fn cs_main(
@builtin(global_invocation_id) global_invocation_id: vec3<u32>,
[[builtin(global_invocation_id)]] global_invocation_id: vec3<u32>,
) {
let vertex_index = global_invocation_id.x;
if (vertex_index >= skinning_ubo.count) {
return;
}
let src_index = skinning_ubo.src_offset + vertex_index;
let dst_index = skinning_ubo.dst_offset + vertex_index;
let src_index = skinning_ubo.src_offset + vertex_index;
let dst_index = skinning_ubo.dst_offset + vertex_index;
let ptf = src_vertices.data[src_index].ptf;
let position = ptf.xyz;

View File

@ -1,6 +1,6 @@
struct TangentFrame {
normal: vec3<f32>,
tangent: vec3<f32>
normal: vec3<f32>;
tangent: vec3<f32>;
};
// https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/?like_comment=12

View File

@ -1,7 +1,7 @@
struct SkinnedVertex {
ptf: vec4<f32>
ptf: vec4<f32>;
};
struct SkinnedVertexArray {
data: array<SkinnedVertex>
data: array<SkinnedVertex>;
};

View File

@ -268,14 +268,14 @@ impl Renderer {
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
color_attachments: &[wgpu::RenderPassColorAttachment {
view: target_views.output,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: true,
},
})],
}],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: target_views.depth,
depth_ops: Some(wgpu::Operations {

View File

@ -24,7 +24,7 @@ impl DebugPass {
target_info: ViewportInfo,
) -> Self {
// TODO hook into ShaderStore system
let shader = device.create_shader_module(wgpu::include_wgsl!("debug_shader.wgsl"));
let shader = device.create_shader_module(&wgpu::include_wgsl!("debug_shader.wgsl"));
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("DebugPass Pipeline Layout"),
@ -43,11 +43,11 @@ impl DebugPass {
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
targets: &[wgpu::ColorTargetState {
format: target_info.output_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::LineList,
@ -130,7 +130,7 @@ impl RenderPass for DebugPass {
self.device
.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
label: Some("DebugPass Render Bundle"),
color_formats: &[Some(self.target_info.output_format)],
color_formats: &[self.target_info.output_format],
depth_stencil: Some(wgpu::RenderBundleDepthStencil {
format: self.target_info.depth_format,
depth_read_only: false, // TODO optimize?

View File

@ -1,26 +1,26 @@
struct CameraUniform {
eye: vec4<f32>,
vp: mat4x4<f32>,
eye: vec4<f32>;
vp: mat4x4<f32>;
};
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) color: vec3<f32>
[[location(0)]] position: vec3<f32>;
[[location(1)]] color: vec3<f32>;
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) position: vec3<f32>,
@location(1) color: vec3<f32>
[[builtin(position)]] clip_position: vec4<f32>;
[[location(0)]] position: vec3<f32>;
[[location(1)]] color: vec3<f32>;
};
@group(0) @binding(0)
[[group(0), binding(0)]]
var<uniform> camera: CameraUniform;
@vertex
[[stage(vertex)]]
fn vs_main(
@builtin(instance_index) mesh_idx: u32,
@builtin(vertex_index) vertex_idx: u32,
[[builtin(instance_index)]] mesh_idx: u32,
[[builtin(vertex_index)]] vertex_idx: u32,
vertex: VertexInput,
) -> VertexOutput {
let world_pos = vertex.position;
@ -32,9 +32,9 @@ fn vs_main(
return out;
}
@fragment
[[stage(fragment)]]
fn fs_main(
frag: VertexOutput,
) -> @location(0) vec4<f32> {
) -> [[location(0)]] vec4<f32> {
return vec4<f32>(frag.color, 1.0);
}

View File

@ -142,11 +142,11 @@ impl MeshPass {
let shader = shader_info.store.get(&shader_info.forward).unwrap();
let targets = &[Some(wgpu::ColorTargetState {
let targets = &[wgpu::ColorTargetState {
format: target_info.output_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})];
}];
let mut pipeline_desc = wgpu::RenderPipelineDescriptor {
label: Some("Opaque MeshPass Pipeline"),
@ -449,7 +449,7 @@ impl RenderPass for MeshPass {
mesh.vertex_count / 64 + 1
};
cmds.dispatch_workgroups(workgroup_num as u32, 1, 1);
cmds.dispatch(workgroup_num as u32, 1, 1);
}
}
}
@ -465,7 +465,7 @@ impl RenderPass for MeshPass {
self.device
.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
label: Some("Opaque Pass Render Bundle"),
color_formats: &[Some(self.target_info.output_format)],
color_formats: &[self.target_info.output_format],
depth_stencil: Some(wgpu::RenderBundleDepthStencil {
format: self.target_info.depth_format,
depth_read_only: false, // TODO optimize?

View File

@ -83,7 +83,7 @@ impl ShaderStore {
fn load_wgsl(&self, wgsl_source: String) -> Result<wgpu::ShaderModule, ShaderError> {
let shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
.create_shader_module(&wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(wgsl_source)),
});

View File

@ -58,10 +58,10 @@ impl WinitViewport {
.unwrap();
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: *surface.get_supported_formats(&adapter).first().unwrap(),
format: surface.get_preferred_format(&adapter).unwrap(),
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Fifo,
present_mode: wgpu::PresentMode::Mailbox,
};
surface.configure(&device, &config);