Skip to content

Instantly share code, notes, and snippets.

@bczhc
Last active January 28, 2026 14:33
Show Gist options
  • Select an option

  • Save bczhc/4bcead558fc05c5572bda023c2188489 to your computer and use it in GitHub Desktop.

Select an option

Save bczhc/4bcead558fc05c5572bda023c2188489 to your computer and use it in GitHub Desktop.
volumeshader_bm native wgpu复刻版
/// Native wgpu version of https://cznull.github.io/vsbm
///
/// Co-worked with Gemini.
use bytemuck::{Pod, Zeroable};
use std::sync::Arc;
use std::time::Instant;
use std::{env, iter};
use winit::application::ApplicationHandler;
use winit::event_loop::{ActiveEventLoop, OwnedDisplayHandle};
use winit::window::{Window, WindowId};
use winit::{
event::*,
event_loop::{ControlFlow, EventLoop},
};
// --- Uniform 数据结构 (必须符合 WGSL 的 16 字节对齐) ---
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct Uniforms {
origin: [f32; 3],
padding1: f32,
right: [f32; 3],
padding2: f32,
up: [f32; 3],
padding3: f32,
forward: [f32; 3],
padding4: f32,
screen_size: [f32; 2],
len: f32,
padding5: f32,
}
struct State {
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
size: winit::dpi::PhysicalSize<u32>,
render_pipeline: wgpu::RenderPipeline,
uniform_buffer: wgpu::Buffer,
uniform_bind_group: wgpu::BindGroup,
start_time: Instant,
window: Arc<Window>,
surface_format: wgpu::TextureFormat,
}
impl State {
fn window(&self) -> &Window {
&self.window
}
fn configure_surface(&self) {
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.surface_format,
// Request compatibility with the sRGB-format texture view we‘re going to create later.
view_formats: vec![self.surface_format.add_srgb_suffix()],
alpha_mode: wgpu::CompositeAlphaMode::Auto,
width: self.size.width,
height: self.size.height,
desired_maximum_frame_latency: 2,
present_mode: wgpu::PresentMode::AutoNoVsync,
};
self.surface.configure(&self.device, &surface_config);
}
async fn new(display: OwnedDisplayHandle, window: Arc<Window>) -> Self {
let saved_window = Arc::clone(&window);
let size = window.inner_size();
let instance = wgpu::Instance::default();
let surface = instance.create_surface(window).unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor::default())
.await
.unwrap();
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = 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],
desired_maximum_frame_latency: 0,
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![surface_format.add_srgb_suffix()],
};
surface.configure(&device, &config);
// --- 核心 WGSL 着色器 ---
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../../vsbm.wgsl").into()),
});
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Uniform Buffer"),
size: std::mem::size_of::<Uniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
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 = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
label: Some("uniform_bind_group"),
});
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[&uniform_bind_group_layout],
immediate_size: 0,
});
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: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: config.format.add_srgb_suffix(),
blend: None,
write_mask: Default::default(),
})],
}),
multiview_mask: None,
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
cache: None,
});
Self {
surface,
device,
queue,
size,
render_pipeline,
uniform_buffer,
uniform_bind_group,
start_time: Instant::now(),
window: saved_window,
surface_format,
}
}
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
self.size = new_size;
// reconfigure the surface
self.configure_surface();
}
fn update(&mut self) {
let elapsed = self.start_time.elapsed().as_secs_f32();
let ang1 = 2.8 + elapsed * 0.5; // 自动旋转
let ang2: f32 = 0.4;
let len = 1.6;
let origin = [
len * ang1.cos() * ang2.cos(),
len * ang2.sin(),
len * ang1.sin() * ang2.cos(),
];
let right = [ang1.sin(), 0.0, -ang1.cos()];
let up = [
-ang2.sin() * ang1.cos(),
ang2.cos(),
-ang2.sin() * ang1.sin(),
];
let forward = [
-ang1.cos() * ang2.cos(),
-ang2.sin(),
-ang1.sin() * ang2.cos(),
];
let cx = self.size.width as f32;
let cy = self.size.height as f32;
let sx = (cx.min(cy) / cx) * (cx / cx.max(cy));
let sy = (cy.min(cx) / cy) * (cy / cx.max(cy));
// 因为使用了 1:1 的 Viewport,这里 screen_size 直接给 1.0 即可
let uniforms = Uniforms {
origin,
padding1: 0.0,
right,
padding2: 0.0,
up,
padding3: 0.0,
forward,
padding4: 0.0,
screen_size: [1.0, 1.0],
len,
padding5: 0.0,
};
self.queue
.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniforms));
}
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let surface_texture = self.surface.get_current_texture()?;
let texture_view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor {
// Without add_srgb_suffix() the image we will be working with
// might not be "gamma correct".
format: Some(self.surface_format.add_srgb_suffix()),
..Default::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: &texture_view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.07,
g: 0.06,
b: 0.08,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
multiview_mask: None,
});
// --- 核心逻辑:设置居中的 1:1 正方形视口 ---
let win_w = self.size.width as f32;
let win_h = self.size.height as f32;
let side = win_w.min(win_h); // 取短边
let x_offset = (win_w - side) / 2.0;
let y_offset = (win_h - side) / 2.0;
render_pass.set_viewport(x_offset, y_offset, side, side, 0.0, 1.0);
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
render_pass.draw(0..6, 0..1);
}
self.queue.submit(iter::once(encoder.finish()));
surface_texture.present();
Ok(())
}
}
#[derive(Default)]
struct App {
state: Option<State>,
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
// Create window object
let window = Arc::new(
event_loop
.create_window(Window::default_attributes())
.unwrap(),
);
let state = pollster::block_on(State::new(
event_loop.owned_display_handle(),
window.clone(),
));
self.state = Some(state);
window.request_redraw();
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let state = self.state.as_mut().unwrap();
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::Resized(physical_size) => state.resize(physical_size),
WindowEvent::RedrawRequested => {
state.update();
match state.render() {
Ok(_) => {}
Err(wgpu::SurfaceError::Lost) => state.resize(state.size),
Err(wgpu::SurfaceError::OutOfMemory) => event_loop.exit(),
Err(e) => eprintln!("{:?}", e),
}
state.window().request_redraw();
}
_ => {}
}
}
}
pub fn main() {
unsafe {
env::set_var("RUST_LOG", "info");
}
env_logger::init();
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App::default();
event_loop.run_app(&mut app).unwrap();
}
struct Uniforms {
origin: vec3f,
_p1: f32,
right: vec3f,
_p2: f32,
up: vec3f,
_p3: f32,
forward: vec3f,
_p4: f32,
screen_size: vec2f,
len: f32,
_p5: f32,
};
@group(0) @binding(0) var<uniform> ui: Uniforms;
struct VertexOutput {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
};
@vertex
fn vs_main(@builtin(vertex_index) idx: u32) -> VertexOutput {
var pos = array<vec2f, 6>(
vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),
vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0)
);
var out: VertexOutput;
out.pos = vec4f(pos[idx], 0.0, 1.0);
out.uv = pos[idx];
return out;
}
fn kernel(ver: vec3f) -> f32 {
var a = ver;
var b: f32; var c: f32; var d: f32;
for(var i: i32 = 0; i < 5; i++) {
b = length(a);
c = atan2(a.y, a.x) * 8.0;
d = acos(a.z / b) * 8.0;
b = pow(b, 8.0);
a = vec3f(b * sin(d) * cos(c), b * sin(d) * sin(c), b * cos(d)) + ver;
if (b > 6.0) { break; }
}
return 4.0 - dot(a, a);
}
@fragment
fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
let M_L = 0.381966;
let M_R = 0.618033;
let step_size = 0.002;
let dir = ui.forward + ui.right * uv.x * ui.screen_size.x + ui.up * uv.y * ui.screen_size.y;
let local_dir = normalize(vec3f(uv.x * ui.screen_size.x, uv.y * ui.screen_size.y, -1.0));
var v1 = kernel(ui.origin + dir * (step_size * ui.len));
var v2 = kernel(ui.origin);
var sign = 0;
var r3: f32 = 0.0;
for (var k: i32 = 2; k < 1002; k++) {
let ver = ui.origin + dir * (step_size * ui.len * f32(k));
let v = kernel(ver);
if (v > 0.0 && v1 < 0.0) {
var r1 = step_size * ui.len * f32(k - 1);
var r2 = step_size * ui.len * f32(k);
for (var l = 0; l < 8; l++) {
r3 = r1 * 0.5 + r2 * 0.5;
if (kernel(ui.origin + dir * r3) > 0.0) { r2 = r3; } else { r1 = r3; }
}
if (r3 < 2.0 * ui.len) { sign = 1; break; }
}
if (v < v1 && v1 > v2 && v1 < 0.0 && (v1 * 2.0 > v || v1 * 2.0 > v2)) {
var r1 = step_size * ui.len * f32(k - 2);
var r2 = step_size * ui.len * (f32(k) - 2.0 + 2.0 * M_L);
var r3_g = step_size * ui.len * (f32(k) - 2.0 + 2.0 * M_R);
var r4 = step_size * ui.len * f32(k);
var m2 = kernel(ui.origin + dir * r2);
var m3 = kernel(ui.origin + dir * r3_g);
for (var l = 0; l < 8; l++) {
if (m2 > m3) {
r4 = r3_g; r3_g = r2; r2 = r4 * M_L + r1 * M_R;
m3 = m2; m2 = kernel(ui.origin + dir * r2);
} else {
r1 = r2; r2 = r3_g; r3_g = r4 * M_R + r1 * M_L;
m2 = m3; m3 = kernel(ui.origin + dir * r3_g);
}
}
let target_r = select(r3_g, r2, m2 > 0.0 || m3 > 0.0);
if (kernel(ui.origin + dir * target_r) > 0.0) {
var ra = step_size * ui.len * f32(k - 2);
var rb = target_r;
for (var l = 0; l < 8; l++) {
r3 = ra * 0.5 + rb * 0.5;
if (kernel(ui.origin + dir * r3) > 0.0) { rb = r3; } else { ra = r3; }
}
if (r3 < 2.0 * ui.len && r3 > step_size * ui.len) { sign = 1; break; }
}
}
v2 = v1; v1 = v;
}
var color = vec3f(0.0);
if (sign == 1) {
let hit_pos = ui.origin + dir * r3;
let r_sq = dot(hit_pos, hit_pos);
let eps = r3 * 0.00025;
var n: vec3f;
n.x = kernel(hit_pos - ui.right * eps) - kernel(hit_pos + ui.right * eps);
n.y = kernel(hit_pos - ui.up * eps) - kernel(hit_pos + ui.up * eps);
n.z = kernel(hit_pos + ui.forward * eps) - kernel(hit_pos - ui.forward * eps);
n = normalize(n);
let reflect_v = reflect(local_dir, n);
let light_dir = vec3f(0.276, 0.920, 0.276);
var spec = pow(max(0.0, dot(reflect_v, light_dir)), 4.0);
let diff = max(0.0, dot(n, light_dir));
let shade = spec * 0.45 + diff * 0.25 + 0.3;
color = (sin(vec3f(r_sq * 10.0, r_sq * 10.0 + 2.05, r_sq * 10.0 - 2.05)) * 0.5 + 0.5) * shade;
}
// return vec4f(color, 1.0);
return vec4f(pow(color, vec3f(2.2)), 1.0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment