tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 660Markdown 302TOML 157RON 312382 Definiciones

⎇ c5a232cf24788ab75a850ffe81eca5a7af50e2f8 llimphi-voxel/examples/shine_demo.rs Rust · 187 líneas · 6.3 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo — 27 crates nuevos (anim, lottie, svg, mesh, image, layer, test, wire-view, widgets rag-sidebar/router/lazy-list/rive-button) y sync de los 95 existentes 93253cc22 · Sergio ·

1//! Prueba del **brillo especular + glaseado** por voxel: una fila de esferas, cada
2//! una de un mineral con su factor de brillo (`Mineral::brillo`) codificado en el
3//! alpha del voxel. El ray-march devuelve un reflejo dependiente de la vista — algo
4//! que la luz plana de Minecraft no hace: el carbón queda mate, el diamante destella.
5//!
6//! `cargo run -p llimphi-voxel --example shine_demo --release` → `/tmp/shine.png`
7
8use std::fs::File;
9use std::io::BufWriter;
10
11use llimphi_3d::glam::Vec3;
12use llimphi_3d::{Atmosphere, Camera3d, Scene3d, VoxelGrid, VoxelRenderer};
13use llimphi_hal::{wgpu, Hal};
14use llimphi_raster::peniko::Color;
15use llimphi_raster::{vello, Renderer};
16use llimphi_voxel::Mineral;
17
18const W: u32 = 1280;
19const H: u32 = 520;
20const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
21
22fn main() {
23    // Gradiente de brillo: carbón mate → diamante espejado.
24    let fila = [
25        Mineral::Carbon,
26        Mineral::Sal,
27        Mineral::Hierro,
28        Mineral::Cobre,
29        Mineral::Oro,
30        Mineral::Cuarzo,
31        Mineral::Amatista,
32        Mineral::Diamante,
33    ];
34    let r: i32 = 5;
35    let gap: i32 = 4;
36    let step = 2 * r + gap;
37    let margin = r + 3;
38    let dim = [
39        (fila.len() as i32 * step + 2 * margin) as u32,
40        (2 * r + 14) as u32,
41        (2 * r + 10) as u32,
42    ];
43    let ground_h: i32 = 3;
44    let mut grid = VoxelGrid::new(dim);
45
46    // Suelo mate gris.
47    for x in 0..dim[0] {
48        for z in 0..dim[2] {
49            for y in 0..ground_h as u32 {
50                grid.set(x, y, z, [96, 96, 100]);
51            }
52        }
53    }
54
55    // Esferas.
56    let cz = dim[2] as i32 / 2;
57    let cy = ground_h + r;
58    for (i, min) in fila.iter().enumerate() {
59        let cx = margin + r + i as i32 * step;
60        let col = min.color();
61        let alpha = VoxelGrid::shine_to_alpha(min.brillo());
62        for dx in -r..=r {
63            for dy in -r..=r {
64                for dz in -r..=r {
65                    if dx * dx + dy * dy + dz * dz <= r * r {
66                        grid.set_rgba((cx + dx) as u32, (cy + dy) as u32, (cz + dz) as u32, col, alpha);
67                    }
68                }
69            }
70        }
71        eprintln!("{:>10}: brillo {:.2} → alpha {}", min.label(), min.brillo(), alpha);
72    }
73
74    let hal = pollster::block_on(Hal::new(None)).expect("hal");
75    let mut renderer = Renderer::new(&hal).expect("renderer");
76    let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
77    vr.sun_dir = [0.28, 0.72, 0.62]; // sol alto-frontal → el reflejo cae hacia la cámara
78    vr.atmosphere = Atmosphere {
79        sky_zenith: [70, 96, 150],
80        sky_horizon: [178, 196, 214],
81        fog_density: 0.0,
82        god_rays: 0.0,
83    };
84
85    let camera = Camera3d::orbit(
86        Vec3::new(0.0, r as f32 * 0.4, 0.0),
87        8_f32.to_radians(),
88        18_f32.to_radians(),
89        dim[0] as f32 * 0.72,
90    );
91
92    let mut scene = Scene3d::new();
93    let pixels = render(&hal, &mut renderer, &mut scene, &mut vr, &camera);
94    write_png(&pixels, "/tmp/shine.png");
95    eprintln!("escrito /tmp/shine.png — brillo especular por mineral");
96}
97
98fn render(
99    hal: &Hal,
100    renderer: &mut Renderer,
101    scene: &mut Scene3d,
102    vr: &mut VoxelRenderer,
103    camera: &Camera3d,
104) -> Vec<u8> {
105    let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
106        label: Some("inter"),
107        size: wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
108        mip_level_count: 1,
109        sample_count: 1,
110        dimension: wgpu::TextureDimension::D2,
111        format: FMT,
112        usage: wgpu::TextureUsages::STORAGE_BINDING
113            | wgpu::TextureUsages::TEXTURE_BINDING
114            | wgpu::TextureUsages::RENDER_ATTACHMENT
115            | wgpu::TextureUsages::COPY_SRC,
116        view_formats: &[],
117    });
118    let view = inter.create_view(&wgpu::TextureViewDescriptor::default());
119    renderer
120        .render_to_view(hal, &vello::Scene::new(), &view, W, H, Color::from_rgba8(0, 0, 0, 255))
121        .expect("base");
122    let mut enc = hal
123        .device
124        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("shine") });
125    scene.render(&hal.device, &hal.queue, &mut enc, &view, (W, H), camera, Some(vr), &[]);
126    hal.queue.submit(std::iter::once(enc.finish()));
127    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
128    readback(hal, &inter)
129}
130
131fn readback(hal: &Hal, target: &wgpu::Texture) -> Vec<u8> {
132    let unpadded = (W * 4) as usize;
133    let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
134    let padded = unpadded.div_ceil(align) * align;
135    let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
136        label: Some("readback"),
137        size: (padded * H as usize) as u64,
138        usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
139        mapped_at_creation: false,
140    });
141    let mut enc = hal
142        .device
143        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
144    enc.copy_texture_to_buffer(
145        wgpu::TexelCopyTextureInfo {
146            texture: target,
147            mip_level: 0,
148            origin: wgpu::Origin3d::ZERO,
149            aspect: wgpu::TextureAspect::All,
150        },
151        wgpu::TexelCopyBufferInfo {
152            buffer: &buf,
153            layout: wgpu::TexelCopyBufferLayout {
154                offset: 0,
155                bytes_per_row: Some(padded as u32),
156                rows_per_image: Some(H),
157            },
158        },
159        wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
160    );
161    hal.queue.submit(std::iter::once(enc.finish()));
162    let slice = buf.slice(..);
163    let (tx, rx) = std::sync::mpsc::channel();
164    slice.map_async(wgpu::MapMode::Read, move |r| {
165        let _ = tx.send(r);
166    });
167    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
168    rx.recv().unwrap().unwrap();
169    let data = slice.get_mapped_range();
170    let mut pixels = Vec::with_capacity((W * H * 4) as usize);
171    for row in 0..H as usize {
172        let s = row * padded;
173        pixels.extend_from_slice(&data[s..s + unpadded]);
174    }
175    drop(data);
176    buf.unmap();
177    pixels
178}
179
180fn write_png(pixels: &[u8], path: &str) {
181    let file = File::create(path).expect("png");
182    let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
183    enc.set_color(png::ColorType::Rgba);
184    enc.set_depth(png::BitDepth::Eight);
185    let mut wtr = enc.write_header().unwrap();
186    wtr.write_image_data(pixels).unwrap();
187}