update
This commit is contained in:
@@ -115,6 +115,7 @@ pub struct BackgroundRenderResources {
|
|||||||
hand_image_pipeline: wgpu::RenderPipeline,
|
hand_image_pipeline: wgpu::RenderPipeline,
|
||||||
glyph_pipeline: wgpu::RenderPipeline,
|
glyph_pipeline: wgpu::RenderPipeline,
|
||||||
dot_pipeline: wgpu::RenderPipeline,
|
dot_pipeline: wgpu::RenderPipeline,
|
||||||
|
hand_membrane_pipeline: wgpu::RenderPipeline,
|
||||||
hand_dot_pipeline: wgpu::RenderPipeline,
|
hand_dot_pipeline: wgpu::RenderPipeline,
|
||||||
hand_image_bind_group: wgpu::BindGroup,
|
hand_image_bind_group: wgpu::BindGroup,
|
||||||
hand_image_texture: texture::Texture,
|
hand_image_texture: texture::Texture,
|
||||||
@@ -122,6 +123,8 @@ pub struct BackgroundRenderResources {
|
|||||||
glyph_vertex_buffer: wgpu::Buffer,
|
glyph_vertex_buffer: wgpu::Buffer,
|
||||||
glyph_instance_buffer: wgpu::Buffer,
|
glyph_instance_buffer: wgpu::Buffer,
|
||||||
glyph_instances: Vec<GlyphInstance>,
|
glyph_instances: Vec<GlyphInstance>,
|
||||||
|
hand_membrane_instance_buffer: wgpu::Buffer,
|
||||||
|
hand_membrane_instances: Vec<GlyphInstance>,
|
||||||
hand_dot_instance_buffer: wgpu::Buffer,
|
hand_dot_instance_buffer: wgpu::Buffer,
|
||||||
hand_dot_instances: Vec<GlyphInstance>,
|
hand_dot_instances: Vec<GlyphInstance>,
|
||||||
render_options: RenderOptions,
|
render_options: RenderOptions,
|
||||||
@@ -445,6 +448,8 @@ impl BackgroundRenderResources {
|
|||||||
let glyph_pipeline =
|
let glyph_pipeline =
|
||||||
create_glyph_pipeline(device, target_format, &shader, &pipeline_layout);
|
create_glyph_pipeline(device, target_format, &shader, &pipeline_layout);
|
||||||
let dot_pipeline = create_dot_pipeline(device, target_format, &shader, &pipeline_layout);
|
let dot_pipeline = create_dot_pipeline(device, target_format, &shader, &pipeline_layout);
|
||||||
|
let hand_membrane_pipeline =
|
||||||
|
create_hand_membrane_pipeline(device, target_format, &shader, &pipeline_layout);
|
||||||
let hand_dot_pipeline =
|
let hand_dot_pipeline =
|
||||||
create_hand_dot_pipeline(device, target_format, &shader, &pipeline_layout);
|
create_hand_dot_pipeline(device, target_format, &shader, &pipeline_layout);
|
||||||
|
|
||||||
@@ -474,6 +479,16 @@ impl BackgroundRenderResources {
|
|||||||
contents: bytemuck::cast_slice(&glyph_instances),
|
contents: bytemuck::cast_slice(&glyph_instances),
|
||||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||||
});
|
});
|
||||||
|
let hand_membrane_instances = build_hand_membrane_instances(
|
||||||
|
hand_image_texture.width as f32,
|
||||||
|
hand_image_texture.height as f32,
|
||||||
|
);
|
||||||
|
let hand_membrane_instance_buffer =
|
||||||
|
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Hand Fingertip Membrane Instance Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&hand_membrane_instances),
|
||||||
|
usage: wgpu::BufferUsages::VERTEX,
|
||||||
|
});
|
||||||
let hand_dot_instances = build_hand_dot_instances(
|
let hand_dot_instances = build_hand_dot_instances(
|
||||||
rows,
|
rows,
|
||||||
cols,
|
cols,
|
||||||
@@ -500,12 +515,15 @@ impl BackgroundRenderResources {
|
|||||||
hand_image_pipeline,
|
hand_image_pipeline,
|
||||||
glyph_pipeline,
|
glyph_pipeline,
|
||||||
dot_pipeline,
|
dot_pipeline,
|
||||||
|
hand_membrane_pipeline,
|
||||||
hand_dot_pipeline,
|
hand_dot_pipeline,
|
||||||
hand_image_bind_group,
|
hand_image_bind_group,
|
||||||
hand_image_texture,
|
hand_image_texture,
|
||||||
glyph_vertex_buffer,
|
glyph_vertex_buffer,
|
||||||
glyph_instance_buffer,
|
glyph_instance_buffer,
|
||||||
glyph_instances,
|
glyph_instances,
|
||||||
|
hand_membrane_instance_buffer,
|
||||||
|
hand_membrane_instances,
|
||||||
hand_dot_instance_buffer,
|
hand_dot_instance_buffer,
|
||||||
hand_dot_instances,
|
hand_dot_instances,
|
||||||
render_options,
|
render_options,
|
||||||
@@ -595,7 +613,12 @@ impl BackgroundRenderResources {
|
|||||||
fn paint_hand(&self, render_pass: &mut wgpu::RenderPass<'_>, mode: &HandGatewayMode) {
|
fn paint_hand(&self, render_pass: &mut wgpu::RenderPass<'_>, mode: &HandGatewayMode) {
|
||||||
let _range = mode.range.clone();
|
let _range = mode.range.clone();
|
||||||
|
|
||||||
// Draw the five fingertip dot matrices after hand.png, so they sit on top of the texture.
|
// First draw the translucent sensor membranes, then draw live pressure beads on their grid.
|
||||||
|
render_pass.set_pipeline(&self.hand_membrane_pipeline);
|
||||||
|
render_pass.set_vertex_buffer(0, self.glyph_vertex_buffer.slice(..));
|
||||||
|
render_pass.set_vertex_buffer(1, self.hand_membrane_instance_buffer.slice(..));
|
||||||
|
render_pass.draw(0..6, 0..self.hand_membrane_instances.len() as u32);
|
||||||
|
|
||||||
render_pass.set_pipeline(&self.hand_dot_pipeline);
|
render_pass.set_pipeline(&self.hand_dot_pipeline);
|
||||||
render_pass.set_vertex_buffer(0, self.glyph_vertex_buffer.slice(..));
|
render_pass.set_vertex_buffer(0, self.glyph_vertex_buffer.slice(..));
|
||||||
render_pass.set_vertex_buffer(1, self.hand_dot_instance_buffer.slice(..));
|
render_pass.set_vertex_buffer(1, self.hand_dot_instance_buffer.slice(..));
|
||||||
@@ -867,6 +890,39 @@ fn create_dot_pipeline(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_hand_membrane_pipeline(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
target_format: &wgpu::TextureFormat,
|
||||||
|
shader: &wgpu::ShaderModule,
|
||||||
|
layout: &wgpu::PipelineLayout,
|
||||||
|
) -> wgpu::RenderPipeline {
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Hand Fingertip Sensor Membrane Pipeline"),
|
||||||
|
layout: Some(layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: shader,
|
||||||
|
entry_point: Some("vs_hand_membrane"),
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
buffers: &[GlyphVertex::desc(), GlyphInstance::desc()],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: shader,
|
||||||
|
entry_point: Some("fs_hand_membrane"),
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: *target_format,
|
||||||
|
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview_mask: None,
|
||||||
|
cache: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn create_hand_dot_pipeline(
|
fn create_hand_dot_pipeline(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
target_format: &wgpu::TextureFormat,
|
target_format: &wgpu::TextureFormat,
|
||||||
@@ -900,6 +956,24 @@ fn create_hand_dot_pipeline(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_hand_membrane_instances(image_width: f32, image_height: f32) -> Vec<GlyphInstance> {
|
||||||
|
HAND_TIP_MATRICES
|
||||||
|
.iter()
|
||||||
|
.map(|tip| {
|
||||||
|
let uv_x = tip.center_px[0] / image_width.max(1.0);
|
||||||
|
let uv_y = tip.center_px[1] / image_height.max(1.0);
|
||||||
|
let membrane_size = [tip.size_px[0] * 1.62, tip.size_px[1] * 1.56];
|
||||||
|
|
||||||
|
GlyphInstance {
|
||||||
|
// Membrane shaders read xy as hand.png UV.
|
||||||
|
world_position: [uv_x, uv_y, 0.0, 1.0],
|
||||||
|
// Store angle and an oversized source-image pixel size for the floating sensor film.
|
||||||
|
style: [tip.angle_rad, membrane_size[0], membrane_size[1], 0.0],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn build_hand_dot_instances(
|
fn build_hand_dot_instances(
|
||||||
rows: u32,
|
rows: u32,
|
||||||
cols: u32,
|
cols: u32,
|
||||||
|
|||||||
@@ -377,6 +377,100 @@ fn hand_image_uv_to_clip(image_uv: vec2f) -> vec2f {
|
|||||||
return vec2f(screen_uv.x * 2.0 - 1.0, 1.0 - screen_uv.y * 2.0);
|
return vec2f(screen_uv.x * 2.0 - 1.0, 1.0 - screen_uv.y * 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HandMembraneVertexOutput {
|
||||||
|
@builtin(position) clip_position: vec4f,
|
||||||
|
@location(0) local: vec2f,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_2d(point: vec2f, angle: f32) -> vec2f {
|
||||||
|
let c = cos(angle);
|
||||||
|
let s = sin(angle);
|
||||||
|
return vec2f(point.x * c - point.y * s, point.x * s + point.y * c);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rounded_rect_alpha(local: vec2f, radius: f32, softness: f32) -> f32 {
|
||||||
|
let q = abs(local) - vec2f(1.0 - radius, 1.0 - radius);
|
||||||
|
let dist = length(max(q, vec2f(0.0, 0.0))) + min(max(q.x, q.y), 0.0) - radius;
|
||||||
|
return 1.0 - smoothstep(0.0, softness, dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fingertip_film_alpha(
|
||||||
|
local: vec2f,
|
||||||
|
half_width: f32,
|
||||||
|
cap_center_y: f32,
|
||||||
|
rear_edge_y: f32,
|
||||||
|
rear_bulge: f32,
|
||||||
|
softness: f32,
|
||||||
|
) -> f32 {
|
||||||
|
// Top/front of the film is a closed round fingertip cap.
|
||||||
|
let cap_dist = length(vec2f(local.x, local.y - cap_center_y));
|
||||||
|
let cap = (1.0 - smoothstep(half_width, half_width + softness, cap_dist))
|
||||||
|
* (1.0 - smoothstep(cap_center_y - softness, cap_center_y + softness, local.y));
|
||||||
|
|
||||||
|
// The rear half keeps nearly parallel sides and ends with a shallow arc;
|
||||||
|
// it deliberately does not converge back into another capsule end.
|
||||||
|
let x_norm = clamp(abs(local.x) / max(half_width, 0.001), 0.0, 1.0);
|
||||||
|
let rear_curve_y = rear_edge_y + rear_bulge * (1.0 - x_norm * x_norm);
|
||||||
|
let side = 1.0 - smoothstep(half_width, half_width + softness, abs(local.x));
|
||||||
|
let rear = 1.0 - smoothstep(rear_curve_y, rear_curve_y + softness, local.y);
|
||||||
|
let body_gate = smoothstep(cap_center_y - softness, cap_center_y + softness, local.y);
|
||||||
|
let body = side * rear * body_gate;
|
||||||
|
|
||||||
|
return max(cap, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_hand_membrane(vertex: DotVertexInput, instance: DotInstanceInput) -> HandMembraneVertexOutput {
|
||||||
|
let center_px = instance.world_position.xy * u.image.xy;
|
||||||
|
let size_px = instance.style.yz;
|
||||||
|
let angle = instance.style.x;
|
||||||
|
let local_px = vertex.local * size_px * 0.5;
|
||||||
|
let image_uv = (center_px + rotate_2d(local_px, angle)) / max(u.image.xy, vec2f(1.0, 1.0));
|
||||||
|
|
||||||
|
var out: HandMembraneVertexOutput;
|
||||||
|
out.clip_position = vec4f(hand_image_uv_to_clip(image_uv), 0.0, 1.0);
|
||||||
|
out.local = vertex.local;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_hand_membrane(in: HandMembraneVertexOutput) -> @location(0) vec4f {
|
||||||
|
// The film shape matches the fingertip: closed round front, parallel rear sides,
|
||||||
|
// and a shallow rear arc instead of a second capsule end.
|
||||||
|
let panel = fingertip_film_alpha(in.local, 0.66, -0.38, 0.72, 0.18, 0.045);
|
||||||
|
let inner = fingertip_film_alpha(in.local, 0.54, -0.36, 0.64, 0.12, 0.060);
|
||||||
|
let border = clamp(panel - inner * 0.52, 0.0, 1.0);
|
||||||
|
|
||||||
|
// Dense mesh: the live 12x7 pressure dots sit over this finer sensor lattice.
|
||||||
|
let uv = clamp(in.local * 0.5 + vec2f(0.5, 0.5), vec2f(0.0, 0.0), vec2f(1.0, 1.0));
|
||||||
|
let grid = vec2f(18.0, 34.0);
|
||||||
|
let cell = uv * grid - vec2f(0.5, 0.5);
|
||||||
|
let nearest = abs(fract(cell + vec2f(0.5, 0.5)) - vec2f(0.5, 0.5));
|
||||||
|
let grid_line = max(
|
||||||
|
1.0 - smoothstep(0.014, 0.038, nearest.x),
|
||||||
|
1.0 - smoothstep(0.014, 0.038, nearest.y),
|
||||||
|
);
|
||||||
|
let joint = 1.0 - smoothstep(0.070, 0.150, length(nearest * vec2f(1.18, 1.0)));
|
||||||
|
let center_shadow = 1.0 - smoothstep(0.10, 0.76, length(in.local * vec2f(0.92, 0.66)));
|
||||||
|
|
||||||
|
let top_light = smoothstep(-0.52, 0.16, -in.local.y) * 0.20;
|
||||||
|
let side_glow = smoothstep(0.30, 0.70, abs(in.local.x)) * 0.26;
|
||||||
|
let lift_shadow = smoothstep(0.48, 0.86, in.local.y) * (1.0 - smoothstep(0.50, 0.86, abs(in.local.x))) * 0.13;
|
||||||
|
let scan = (0.5 + 0.5 * sin((uv.y * 76.0 + uv.x * 9.0) * 6.28318)) * 0.030;
|
||||||
|
|
||||||
|
let membrane_color = vec3f(0.006, 0.28, 0.38);
|
||||||
|
let line_color = vec3f(0.12, 0.72, 0.88);
|
||||||
|
let rim_color = vec3f(0.18, 0.98, 1.0);
|
||||||
|
let color = membrane_color * (0.68 + top_light + side_glow + scan)
|
||||||
|
+ line_color * (grid_line * 0.20 + joint * 0.42)
|
||||||
|
+ rim_color * (border * 0.92 + side_glow * 0.18)
|
||||||
|
- vec3f(0.0, 0.16, 0.24) * center_shadow * 0.30
|
||||||
|
- vec3f(0.0, 0.10, 0.16) * lift_shadow;
|
||||||
|
let alpha = panel * (0.24 + grid_line * 0.10 + joint * 0.23 + border * 0.42);
|
||||||
|
|
||||||
|
return output_color(color, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_hand_dot(vertex: DotVertexInput, instance: DotInstanceInput) -> DotVertexOutput {
|
fn vs_hand_dot(vertex: DotVertexInput, instance: DotInstanceInput) -> DotVertexOutput {
|
||||||
let intensity = saturate(instance.style.x);
|
let intensity = saturate(instance.style.x);
|
||||||
@@ -387,7 +481,7 @@ fn vs_hand_dot(vertex: DotVertexInput, instance: DotInstanceInput) -> DotVertexO
|
|||||||
let center = hand_image_uv_to_clip(instance.world_position.xy);
|
let center = hand_image_uv_to_clip(instance.world_position.xy);
|
||||||
// Hand fingertip matrices are much smaller than the full Finger view.
|
// Hand fingertip matrices are much smaller than the full Finger view.
|
||||||
// Keep each bead below the local cell spacing so the 12x7 matrix remains visibly separated.
|
// Keep each bead below the local cell spacing so the 12x7 matrix remains visibly separated.
|
||||||
let pixel_size = u.glyph.x * mix(0.38, 0.56, shaped);
|
let pixel_size = u.glyph.x * mix(0.22, 0.34, shaped);
|
||||||
let ndc_offset = vertex.local * vec2f(pixel_size / u.viewport.x, pixel_size / u.viewport.y) * 2.0;
|
let ndc_offset = vertex.local * vec2f(pixel_size / u.viewport.x, pixel_size / u.viewport.y) * 2.0;
|
||||||
|
|
||||||
var out: DotVertexOutput;
|
var out: DotVertexOutput;
|
||||||
@@ -401,15 +495,15 @@ fn vs_hand_dot(vertex: DotVertexInput, instance: DotInstanceInput) -> DotVertexO
|
|||||||
fn fs_hand_dot(in: DotVertexOutput) -> @location(0) vec4f {
|
fn fs_hand_dot(in: DotVertexOutput) -> @location(0) vec4f {
|
||||||
let intensity = saturate(in.intensity);
|
let intensity = saturate(in.intensity);
|
||||||
|
|
||||||
// Use a compact round bead instead of the larger Finger-mode glow marker.
|
// Use a compact bead so the response feels like it lives on the membrane mesh.
|
||||||
let core = circle_alpha(in.local, 0.56, 0.08);
|
let core = circle_alpha(in.local, 0.48, 0.07);
|
||||||
let halo = circle_alpha(in.local, 0.78, 0.14) * 0.16;
|
let halo = circle_alpha(in.local, 0.74, 0.14) * 0.12;
|
||||||
|
|
||||||
// Keep the fingertip matrix visually close to JE-Skin's cyan model dots,
|
// Keep the fingertip matrix visually close to JE-Skin's cyan model dots,
|
||||||
// while still letting pressure brighten the bead a little.
|
// while still letting pressure brighten the bead a little.
|
||||||
let cyan = vec3f(0.34, 0.86, 1.0);
|
let cyan = vec3f(0.34, 0.86, 1.0);
|
||||||
let hot = sample_range_color(intensity);
|
let hot = sample_range_color(intensity);
|
||||||
let color = mix(cyan, hot, 0.22) * mix(0.88, 1.18, intensity);
|
let color = mix(cyan, hot, 0.22) * mix(0.82, 1.16, intensity);
|
||||||
|
|
||||||
return output_color(color, max(core, halo));
|
return output_color(color, max(core, halo));
|
||||||
}
|
}
|
||||||
@@ -435,19 +529,8 @@ fn fs_dot(in: DotVertexOutput) -> @location(0) vec4f {
|
|||||||
let intensity = saturate(in.intensity);
|
let intensity = saturate(in.intensity);
|
||||||
let base_color = sample_range_color(intensity);
|
let base_color = sample_range_color(intensity);
|
||||||
|
|
||||||
let core = circle_alpha(in.local, 0.42, 0.055);
|
let alpha = circle_alpha(in.local, 0.46, 0.045);
|
||||||
|
let color = base_color * mix(0.86, 1.06, intensity);
|
||||||
let glow = circle_alpha(in.local, 0.92, 0.20) * intensity * 0.32;
|
|
||||||
|
|
||||||
let highlight_pos = in.local - vec2f(-0.18, 0.20);
|
|
||||||
|
|
||||||
let highlight = circle_alpha(highlight_pos, 0.16, 0.08) * 0.45;
|
|
||||||
|
|
||||||
let brightness = mix(0.82, 1.22, intensity);
|
|
||||||
|
|
||||||
let color = base_color * brightness + vec3f(1.0, 1.0, 1.0) * highlight;
|
|
||||||
|
|
||||||
let alpha = max(core, glow);
|
|
||||||
|
|
||||||
return output_color(color, alpha);
|
return output_color(color, alpha);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user