Rust 语言 实现 3D 场景渲染器 基于 wgpu 绘制立方体 + 纹理映射

Rust阿木 发布于 4 天前 5 次阅读


Rust 语言实现基于 wgpu 的 3D 场景渲染器:绘制立方体与纹理映射

随着计算机图形学的发展,3D 渲染技术在游戏、影视、虚拟现实等领域得到了广泛应用。Rust 语言作为一种系统编程语言,因其高性能、安全性和并发特性,逐渐成为开发高性能图形应用的热门选择。本文将介绍如何使用 Rust 语言和 wgpu 图形库实现一个简单的 3D 场景渲染器,其中包括立方体的绘制和纹理映射。

环境准备

在开始编写代码之前,我们需要准备以下环境:

1. Rust 语言环境:安装 Rust 语言和 Cargo 包管理器。
2. wgpu 图形库:通过 Cargo 创建新项目并添加 wgpu 依赖。

sh
cargo new 3d_renderer
cd 3d_renderer
cargo add wgpu

立方体模型

我们需要定义立方体的顶点和索引数据。立方体由 8 个顶点组成,每个顶点包含位置、法线和纹理坐标信息。以下是立方体的顶点数据:

rust
[repr(C)]
[derive(Debug, Copy, Clone)]
struct Vertex {
position: [f32; 3],
normal: [f32; 3],
tex_coords: [f32; 2],
}

立方体的索引数据定义了顶点之间的连接关系,用于绘制三角形:

rust
const INDICES: &[u16] = &[
0, 1, 2, 2, 3, 0,
4, 5, 6, 6, 7, 4,
0, 4, 1, 1, 5, 2,
2, 6, 3, 3, 7, 4,
0, 2, 4, 4, 6, 7,
1, 3, 5, 5, 7, 6,
];

纹理映射

为了实现纹理映射,我们需要加载纹理图像并将其转换为 GPU 可用的格式。以下是加载纹理的示例代码:

rust
fn load_texture(device: &Device, queue: &Queue, path: &str) -> Result {
let image = image::open(path).expect("Failed to open texture image");
let image = image.to_rgba8();
let data = image.into_raw();

let texture = device.create_texture(
&TextureDescriptor {
size: wgpu::Extent3d {
width: image.width() as u32,
height: image.height() as u32,
depth: 1,
},
format: TextureFormat::Rgba8Unorm,
usage: TextureUsage::TEXTURE_BINDING | TextureUsage::COPY_DST,
..Default::default()
},
Some(MemoryUsage::Upload),
);

let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let texture_copy = wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::new(0.0, 0.0, 0.0),
};
let region = wgpu::ImageCopyRegion {
texture_size: texture.size(),
offset: wgpu::Offset3d::default(),
rows: wgpu::ImageCopyTextureRowStep {
width: 4,
height: 4,
},
};
encoder.copy_image_to_image(&texture_copy, &region, data.as_slice());

queue.submit(Some(encoder.finish()));

Ok(texture)
}

渲染流程

接下来,我们将实现渲染流程。创建一个 `RenderPass` 来绘制立方体:

rust
fn render_pass(
encoder: &mut Encoder,
pipeline: &Pipeline,
bind_group: &BindGroup,
vertex_buffer: &Buffer,
index_buffer: &Buffer,
texture: &Texture,
) {
let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
color_attachments: &[Some(RenderPassColorAttachment {
view: &frame.color attachments[0],
resolve_target: None,
load_op: LoadOp::Clear(wgpu::Color::BLACK),
store_op: StoreOp::Store,
})],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &frame.depth_stencil,
depth_load_op: LoadOp::Clear(1.0),
depth_store_op: StoreOp::Store,
stencil_load_op: LoadOp::Clear(0),
stencil_store_op: StoreOp::Store,
}),
});

render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, bind_group, &[]);
render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16);
render_pass.draw_indexed(0..INDICES.len() as u32.., 0, 0..1);
}

然后,在主循环中调用 `render_pass` 函数来绘制立方体:

rust
fn main() {
// 初始化 wgpu 图形库
let instance = Instance::new(Backends::PRIMARY);
let adapter = instance.request_adapter(&AdapterDescriptor::default()).unwrap();
let (device, queue) = adapter.request_device(&DeviceDescriptor::default(), None).unwrap();

// 创建纹理
let texture = load_texture(&device, &queue, "path/to/texture.png").unwrap();

// 创建渲染资源
let vertex_buffer = device.create_buffer(&BufferDescriptor {
label: Some("Vertex Buffer"),
size: std::mem::size_of_val(&vertices) as wgpu::BufferAddress,
usage: BufferUsage::VERTEX,
mapped_at_creation: false,
});

let index_buffer = device.create_buffer(&BufferDescriptor {
label: Some("Index Buffer"),
size: std::mem::size_of_val(&INDICES) as wgpu::BufferAddress,
usage: BufferUsage::INDEX,
mapped_at_creation: false,
});

// 创建着色器
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader Module"),
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
});

// 创建管线
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("Pipeline Layout"),
bind_group_layouts: &[&vertex_bind_group_layout, &texture_bind_group_layout],
});

let pipeline = device.create_pipeline(&PipelineDescriptor {
label: Some("Pipeline"),
layout: &pipeline_layout,
vertex: VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[VertexBufferLayout {
array_stride: std::mem::size_of_val(&vertices[0]) as wgpu::BufferAddress,
attributes: &[
VertexAttribute {
format: VertexFormat::Float3,
offset: 0,
shader_location: 0,
},
VertexAttribute {
format: VertexFormat::Float3,
offset: std::mem::size_of_val(&vertices[0].position) as wgpu::BufferAddress,
shader_location: 1,
},
VertexAttribute {
format: VertexFormat::Float2,
offset: std::mem::size_of_val(&vertices[0].tex_coords) as wgpu::BufferAddress,
shader_location: 2,
},
],
}],
},
fragment: Some(FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(ColorTargetState {
format: TextureFormat::Bgra8UnormSrgb,
blend: Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::Zero,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
strip_index_format: Some(wgpu::IndexFormat::Uint16),
..Default::default()
},
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: CompareFunction::Less,
..Default::default()
}),
multisample: MultisampleState {
count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
},
});

// 创建绑定组
let vertex_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferType::Vertex,
has_dynamic_offset: false,
min_binding_size: std::mem::size_of_val(&vertices[0]) as wgpu::BufferAddress,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferType::Index,
has_dynamic_offset: false,
min_binding_size: std::mem::size_of_val(&INDICES[0]) as wgpu::BufferAddress,
},
count: None,
},
BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
},
count: None,
},
BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: BindingType::Sampler,
count: None,
},
],
label: Some("Vertex Bind Group Layout"),
});

let texture_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
},
count: None,
}],
label: Some("Texture Bind Group Layout"),
});

let vertex_bind_group = device.create_bind_group(&BindGroupDescriptor {
label: Some("Vertex Bind Group"),
layout: &vertex_bind_group_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BufferBinding {
buffer: &vertex_buffer,
offset: 0,
size: None,
},
},
BindGroupEntry {
binding: 1,
resource: BufferBinding {
buffer: &index_buffer,
offset: 0,
size: None,
},
},
],
});

let texture_bind_group = device.create_bind_group(&BindGroupDescriptor {
label: Some("Texture Bind Group"),
layout: &texture_bind_group_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: TextureBinding {
view: &texture.create_view(&TextureViewDescriptor::default()),
sampler: texture.create_sampler(&SamplerDescriptor::default()),
},
}],
});

// 创建帧缓冲区
let size = Size2D {
width: 800,
height: 600,
};
let texture_descriptor = TextureDescriptor {
size: Extent3d {
width: size.width,
height: size.height,
depth: 1,
},
format: TextureFormat::Bgra8UnormSrgb,
usage: TextureUsage::OUTPUT_ATTACHMENT,
..Default::default()
};
let texture = device.create_texture(&texture_descriptor);
let texture_view = texture.create_view(&TextureViewDescriptor::default());
let render_texture = device.create_texture(&texture_descriptor);
let render_texture_view = render_texture.create_view(&TextureViewDescriptor::default());
let render_pass_color_attachment = RenderPassColorAttachment {
view: &render_texture_view,
resolve_target: None,
load_op: LoadOp::Clear(wgpu::Color::BLACK),
store_op: StoreOp::Store,
};
let render_pass_depth_stencil_attachment = RenderPassDepthStencilAttachment {
view: &frame.depth_stencil,
depth_load_op: LoadOp::Clear(1.0),
depth_store_op: StoreOp::Store,
stencil_load_op: LoadOp::Clear(0),
stencil_store_op: StoreOp::Store,
};

// 主循环
let mut running = true;
while running {
// 处理输入事件
// ...

// 创建渲染命令
let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor::default());
render_pass(&mut encoder, &pipeline, &vertex_bind_group, &index_buffer, &texture, &render_pass_color_attachment, &render_pass_depth_stencil_attachment);

// 提交渲染命令
queue.submit(Some(encoder.finish()));
}
}

总结

本文介绍了如何使用 Rust 语言和 wgpu 图形库实现一个简单的 3D 场景渲染器,其中包括立方体的绘制和纹理映射。通过以上代码,我们可以绘制一个带有纹理的立方体,并了解 Rust 语言在图形渲染领域的应用。这只是一个简单的示例,实际开发中还需要考虑更多因素,如光照、阴影、动画等。希望本文能对您有所帮助。