feat(renderer): 添加遮罩支持,优化绘制流程和背景渐变

This commit is contained in:
2026-07-09 00:11:48 +08:00
parent 547085c819
commit 514fca6000
3 changed files with 470 additions and 32 deletions

View File

@@ -21,6 +21,8 @@ pub mod egl {
pub const EGL_OPENGL_ES2_BIT: c_int = 0x0004; pub const EGL_OPENGL_ES2_BIT: c_int = 0x0004;
pub const EGL_NONE: c_int = 0x3038; pub const EGL_NONE: c_int = 0x3038;
pub const EGL_CONTEXT_CLIENT_VERSION: c_int = 0x3098; pub const EGL_CONTEXT_CLIENT_VERSION: c_int = 0x3098;
pub const EGL_DEPTH_SIZE: c_int = 0x3025;
pub const EGL_STENCIL_SIZE: c_int = 0x3026;
pub type EGLDisplay = *mut c_void; pub type EGLDisplay = *mut c_void;
pub type EGLConfig = *mut c_void; pub type EGLConfig = *mut c_void;
@@ -94,13 +96,14 @@ pub mod x11 {
// ============ GLES2 ============ // ============ GLES2 ============
pub mod gl { pub mod gl {
use std::os::raw::{c_int, c_uint}; use std::os::raw::{c_int, c_uint, c_uchar};
pub type GLenum = c_uint; pub type GLenum = c_uint;
pub type GLuint = c_uint; pub type GLuint = c_uint;
pub type GLint = c_int; pub type GLint = c_int;
pub type GLsizei = c_int; pub type GLsizei = c_int;
pub type GLbitfield = c_uint; pub type GLbitfield = c_uint;
pub type GLboolean = c_uchar;
pub const GL_FLOAT: GLenum = 0x1406; pub const GL_FLOAT: GLenum = 0x1406;
pub const GL_ARRAY_BUFFER: GLenum = 0x8892; pub const GL_ARRAY_BUFFER: GLenum = 0x8892;
@@ -121,6 +124,7 @@ pub mod gl {
pub const GL_COMPILE_STATUS: GLenum = 0x8B81; pub const GL_COMPILE_STATUS: GLenum = 0x8B81;
pub const GL_LINK_STATUS: GLenum = 0x8B82; pub const GL_LINK_STATUS: GLenum = 0x8B82;
pub const GL_TRIANGLES: GLenum = 0x0004; pub const GL_TRIANGLES: GLenum = 0x0004;
pub const GL_TRIANGLE_STRIP: GLenum = 0x0005;
pub const GL_BLEND: GLenum = 0x0BE2; pub const GL_BLEND: GLenum = 0x0BE2;
pub const GL_SRC_ALPHA: GLenum = 0x0302; pub const GL_SRC_ALPHA: GLenum = 0x0302;
pub const GL_ONE_MINUS_SRC_ALPHA: GLenum = 0x0303; pub const GL_ONE_MINUS_SRC_ALPHA: GLenum = 0x0303;
@@ -131,6 +135,20 @@ pub mod gl {
pub const GL_STATIC_DRAW: GLenum = 0x88E4; pub const GL_STATIC_DRAW: GLenum = 0x88E4;
pub const GL_FALSE: GLenum = 0; pub const GL_FALSE: GLenum = 0;
pub const GL_NO_ERROR: GLenum = 0; pub const GL_NO_ERROR: GLenum = 0;
// 遮罩stencil相关常量
pub const GL_DEPTH_TEST: GLenum = 0x0B71;
pub const GL_STENCIL_TEST: GLenum = 0x0B90;
pub const GL_STENCIL_BUFFER_BIT: GLbitfield = 0x0400;
pub const GL_KEEP: GLenum = 0x1E00;
pub const GL_REPLACE: GLenum = 0x1E01;
pub const GL_INCR: GLenum = 0x1E02;
pub const GL_DECR: GLenum = 0x1E03;
pub const GL_ALWAYS: GLenum = 0x0207;
pub const GL_EQUAL: GLenum = 0x0202;
pub const GL_NOTEQUAL: GLenum = 0x0205;
pub const GL_LESS: GLenum = 0x0201;
pub const GL_TRUE: GLboolean = 1;
} }
pub use gl::{GLenum, GLuint, GLint, GLsizei}; pub use gl::{GLenum, GLuint, GLint, GLsizei};
@@ -161,7 +179,59 @@ void main() {
texColor.rgb = texColor.rgb * u_multiplyColor.rgb; texColor.rgb = texColor.rgb * u_multiplyColor.rgb;
texColor.rgb = texColor.rgb + u_screenColor.rgb - (texColor.rgb * u_screenColor.rgb); texColor.rgb = texColor.rgb + u_screenColor.rgb - (texColor.rgb * u_screenColor.rgb);
vec4 color = texColor * u_baseColor; vec4 color = texColor * u_baseColor;
gl_FragColor = vec4(color.rgb * color.a, color.a); gl_FragColor = vec4(color.rgb, color.a);
}
"#;
/// 背景渐变着色器(全屏四边形,从底部到底部到顶部做线性插值)
pub const GRAD_VERT_SHADER_SRC: &str = r#"#version 100
attribute vec2 a_pos;
varying float v_y;
void main() {
v_y = a_pos.y;
gl_Position = vec4(a_pos, 0.0, 1.0);
}
"#;
pub const GRAD_FRAG_SHADER_SRC: &str = r#"#version 100
precision highp float;
varying float v_y;
uniform vec4 u_top;
uniform vec4 u_bottom;
void main() {
float t = (v_y + 1.0) * 0.5;
vec3 c = mix(u_bottom.rgb, u_top.rgb, t);
gl_FragColor = vec4(c, 1.0);
}
"#;
/// 遮罩mask着色器仅把 mask drawable 的不透明区域写入 stencil
/// 用于裁切被遮罩的 drawable如夹在身体里的后臂、被头发遮挡的脸
pub const MASK_VERT_SHADER_SRC: &str = r#"#version 100
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * a_position;
v_texCoord = a_texCoord;
v_texCoord.y = 1.0 - v_texCoord.y;
}
"#;
pub const MASK_FRAG_SHADER_SRC: &str = r#"#version 100
precision highp float;
varying vec2 v_texCoord;
uniform sampler2D s_texture0;
uniform vec4 u_multiplyColor;
uniform vec4 u_screenColor;
void main() {
vec4 c = texture2D(s_texture0, v_texCoord);
c.rgb = c.rgb * u_multiplyColor.rgb;
c.rgb = c.rgb + u_screenColor.rgb - (c.rgb * u_screenColor.rgb);
// 仅不透明区域写入 stencil裁切形状由 alpha 决定)
if (c.a < 0.5) discard;
gl_FragColor = vec4(1.0);
} }
"#; "#;
@@ -209,6 +279,7 @@ pub struct Gl {
pub gl_clear: unsafe extern "C" fn(mask: gl::GLbitfield), pub gl_clear: unsafe extern "C" fn(mask: gl::GLbitfield),
pub gl_clear_color: unsafe extern "C" fn(r: f32, g: f32, b: f32, a: f32), pub gl_clear_color: unsafe extern "C" fn(r: f32, g: f32, b: f32, a: f32),
pub gl_enable: unsafe extern "C" fn(cap: gl::GLenum), pub gl_enable: unsafe extern "C" fn(cap: gl::GLenum),
pub gl_disable: unsafe extern "C" fn(cap: gl::GLenum),
pub gl_blend_func: unsafe extern "C" fn(sfactor: gl::GLenum, dfactor: gl::GLenum), pub gl_blend_func: unsafe extern "C" fn(sfactor: gl::GLenum, dfactor: gl::GLenum),
pub gl_viewport: unsafe extern "C" fn(x: gl::GLint, y: gl::GLint, w: gl::GLsizei, h: gl::GLsizei), pub gl_viewport: unsafe extern "C" fn(x: gl::GLint, y: gl::GLint, w: gl::GLsizei, h: gl::GLsizei),
pub gl_create_shader: unsafe extern "C" fn(t: gl::GLenum) -> gl::GLuint, pub gl_create_shader: unsafe extern "C" fn(t: gl::GLenum) -> gl::GLuint,
@@ -290,8 +361,18 @@ pub struct Gl {
type_: gl::GLenum, type_: gl::GLenum,
indices: *const c_void_raw, indices: *const c_void_raw,
), ),
pub gl_draw_arrays: unsafe extern "C" fn(mode: gl::GLenum, first: gl::GLint, count: gl::GLsizei),
pub gl_get_error: unsafe extern "C" fn() -> gl::GLenum, pub gl_get_error: unsafe extern "C" fn() -> gl::GLenum,
pub gl_delete_buffers: unsafe extern "C" fn(n: gl::GLsizei, buffers: *const gl::GLuint), pub gl_delete_buffers: unsafe extern "C" fn(n: gl::GLsizei, buffers: *const gl::GLuint),
pub gl_stencil_func: unsafe extern "C" fn(func: gl::GLenum, ref_: gl::GLint, mask: gl::GLuint),
pub gl_stencil_op: unsafe extern "C" fn(fail: gl::GLenum, zfail: gl::GLenum, zpass: gl::GLenum),
pub gl_stencil_mask: unsafe extern "C" fn(mask: gl::GLuint),
pub gl_color_mask: unsafe extern "C" fn(
r: gl::GLboolean,
g: gl::GLboolean,
b: gl::GLboolean,
a: gl::GLboolean,
),
pub x_open_display: unsafe extern "C" fn(name: *const c_char) -> *mut x11::Display, pub x_open_display: unsafe extern "C" fn(name: *const c_char) -> *mut x11::Display,
pub x_create_window: unsafe extern "C" fn( pub x_create_window: unsafe extern "C" fn(
@@ -360,6 +441,7 @@ impl Gl {
gl_clear: *lib_gl.get(b"glClear\0")?, gl_clear: *lib_gl.get(b"glClear\0")?,
gl_clear_color: *lib_gl.get(b"glClearColor\0")?, gl_clear_color: *lib_gl.get(b"glClearColor\0")?,
gl_enable: *lib_gl.get(b"glEnable\0")?, gl_enable: *lib_gl.get(b"glEnable\0")?,
gl_disable: *lib_gl.get(b"glDisable\0")?,
gl_blend_func: *lib_gl.get(b"glBlendFunc\0")?, gl_blend_func: *lib_gl.get(b"glBlendFunc\0")?,
gl_viewport: *lib_gl.get(b"glViewport\0")?, gl_viewport: *lib_gl.get(b"glViewport\0")?,
gl_create_shader: *lib_gl.get(b"glCreateShader\0")?, gl_create_shader: *lib_gl.get(b"glCreateShader\0")?,
@@ -389,8 +471,13 @@ impl Gl {
gl_enable_vertex_attrib_array: *lib_gl.get(b"glEnableVertexAttribArray\0")?, gl_enable_vertex_attrib_array: *lib_gl.get(b"glEnableVertexAttribArray\0")?,
gl_vertex_attrib_pointer: *lib_gl.get(b"glVertexAttribPointer\0")?, gl_vertex_attrib_pointer: *lib_gl.get(b"glVertexAttribPointer\0")?,
gl_draw_elements: *lib_gl.get(b"glDrawElements\0")?, gl_draw_elements: *lib_gl.get(b"glDrawElements\0")?,
gl_draw_arrays: *lib_gl.get(b"glDrawArrays\0")?,
gl_get_error: *lib_gl.get(b"glGetError\0")?, gl_get_error: *lib_gl.get(b"glGetError\0")?,
gl_delete_buffers: *lib_gl.get(b"glDeleteBuffers\0")?, gl_delete_buffers: *lib_gl.get(b"glDeleteBuffers\0")?,
gl_stencil_func: *lib_gl.get(b"glStencilFunc\0")?,
gl_stencil_op: *lib_gl.get(b"glStencilOp\0")?,
gl_stencil_mask: *lib_gl.get(b"glStencilMask\0")?,
gl_color_mask: *lib_gl.get(b"glColorMask\0")?,
x_open_display: *lib_x11.get(b"XOpenDisplay\0")?, x_open_display: *lib_x11.get(b"XOpenDisplay\0")?,
x_create_window: *lib_x11.get(b"XCreateWindow\0")?, x_create_window: *lib_x11.get(b"XCreateWindow\0")?,
@@ -431,6 +518,18 @@ pub struct RenderContext {
pub uniform_base_color: GLint, pub uniform_base_color: GLint,
pub uniform_multiply_color: GLint, pub uniform_multiply_color: GLint,
pub uniform_screen_color: GLint, pub uniform_screen_color: GLint,
pub grad_program: GLuint,
pub grad_attrib_pos: GLint,
pub grad_uniform_top: GLint,
pub grad_uniform_bottom: GLint,
// 遮罩mask程序
pub mask_program: GLuint,
pub mask_attrib_position: GLint,
pub mask_attrib_tex_coord: GLint,
pub mask_uniform_matrix: GLint,
pub mask_uniform_texture: GLint,
pub mask_uniform_multiply_color: GLint,
pub mask_uniform_screen_color: GLint,
pub width: i32, pub width: i32,
pub height: i32, pub height: i32,
} }
@@ -476,11 +575,12 @@ impl RenderContext {
let screen = 0; // XDefaultScreen(display),通常 0 let screen = 0; // XDefaultScreen(display),通常 0
let screen_w = unsafe { (gl.x_display_width)(x_display, screen) }; let screen_w = unsafe { (gl.x_display_width)(x_display, screen) };
let screen_h = unsafe { (gl.x_display_height)(x_display, screen) }; let screen_h = unsafe { (gl.x_display_height)(x_display, screen) };
// 用传入的 width/height 创建窗口(配置值),不用屏幕分辨率 // 全屏渲染:直接使用显示器实际分辨率创建窗口与 EGL surface避免
// EGL surface 尺寸需与窗口匹配,大窗口可能导致 GPU 限制问题 // 「帧缓冲 800x800 被放进 1920x1080 窗口、模型只出现在左下角」的问题
let win_w = width; // 若 X11 取不到屏幕分辨率,再退回传入的 width/height。
let win_h = height; let win_w = if screen_w > 0 && screen_h > 0 { screen_w } else { width };
println!("[Live2D] 窗口尺寸: {}x{} (屏幕 {}x{})", win_w, win_h, screen_w, screen_h); let win_h = if screen_w > 0 && screen_h > 0 { screen_h } else { height };
println!("[Live2D] 窗口尺寸: {}x{} (屏幕实际 {}x{})", win_w, win_h, screen_w, screen_h);
let mut attrs = x11::XSetWindowAttributes { let mut attrs = x11::XSetWindowAttributes {
background_pixmap: 0, background_pixmap: 0,
@@ -569,7 +669,20 @@ impl RenderContext {
} }
println!("[Live2D] EGL {}.{}", major, minor); println!("[Live2D] EGL {}.{}", major, minor);
// 主配置请求 depth+stencilLive2D 遮罩需要 stencil
// 若设备不支持该组合,退回不带 depth/stencil 的配置。
let config_attrs = [ let config_attrs = [
egl::EGL_RED_SIZE, 8,
egl::EGL_GREEN_SIZE, 8,
egl::EGL_BLUE_SIZE, 8,
egl::EGL_ALPHA_SIZE, 8,
egl::EGL_SURFACE_TYPE, egl::EGL_WINDOW_BIT,
egl::EGL_RENDERABLE_TYPE, egl::EGL_OPENGL_ES2_BIT,
egl::EGL_DEPTH_SIZE, 16,
egl::EGL_STENCIL_SIZE, 8,
egl::EGL_NONE,
];
let config_attrs_fallback = [
egl::EGL_RED_SIZE, 8, egl::EGL_RED_SIZE, 8,
egl::EGL_GREEN_SIZE, 8, egl::EGL_GREEN_SIZE, 8,
egl::EGL_BLUE_SIZE, 8, egl::EGL_BLUE_SIZE, 8,
@@ -580,18 +693,26 @@ impl RenderContext {
]; ];
let mut config: egl::EGLConfig = std::ptr::null_mut(); let mut config: egl::EGLConfig = std::ptr::null_mut();
let mut num_config = 0; let mut num_config = 0;
if unsafe { let mut choose = |attrs: &[c_int]| -> bool {
(gl.egl_choose_config)( let mut cfg: egl::EGLConfig = std::ptr::null_mut();
display, let mut n = 0;
config_attrs.as_ptr(), let ok = unsafe {
&mut config, (gl.egl_choose_config)(display, attrs.as_ptr(), &mut cfg, 1, &mut n)
1, } != 0 && n > 0;
&mut num_config, if ok {
) config = cfg;
} == 0 || num_config == 0 num_config = n;
{ }
ok
};
if !choose(&config_attrs) && !choose(&config_attrs_fallback) {
return Err(anyhow!("eglChooseConfig 失败")); return Err(anyhow!("eglChooseConfig 失败"));
} }
println!(
"[Live2D] EGL 配置选定: 含stencil={} (num_config={})",
config_attrs.len() > 9,
num_config
);
let surface = unsafe { let surface = unsafe {
(gl.egl_create_window_surface)(display, config, x_window as egl::NativeWindowType, std::ptr::null()) (gl.egl_create_window_surface)(display, config, x_window as egl::NativeWindowType, std::ptr::null())
@@ -653,11 +774,87 @@ impl RenderContext {
let uniform_multiply_color = getu("u_multiplyColor")?; let uniform_multiply_color = getu("u_multiplyColor")?;
let uniform_screen_color = getu("u_screenColor")?; let uniform_screen_color = getu("u_screenColor")?;
// 背景渐变程序
let grad_vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, GRAD_VERT_SHADER_SRC)?;
let grad_frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, GRAD_FRAG_SHADER_SRC)?;
let grad_program = unsafe {
let p = (gl.gl_create_program)();
(gl.gl_attach_shader)(p, grad_vert);
(gl.gl_attach_shader)(p, grad_frag);
(gl.gl_link_program)(p);
let mut status = 0;
(gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status);
if status == 0 {
let mut log: Vec<u8> = vec![0u8; 1024];
(gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr() as *mut c_char);
return Err(anyhow!("gradient shader link failed: {}", String::from_utf8_lossy(&log)));
}
p
};
let grad_attrib_pos = {
let c = std::ffi::CString::new("a_pos")?;
unsafe { (gl.gl_get_attrib_location)(grad_program, c.as_ptr()) }
};
let grad_uniform_top = {
let c = std::ffi::CString::new("u_top")?;
unsafe { (gl.gl_get_uniform_location)(grad_program, c.as_ptr()) }
};
let grad_uniform_bottom = {
let c = std::ffi::CString::new("u_bottom")?;
unsafe { (gl.gl_get_uniform_location)(grad_program, c.as_ptr()) }
};
// 遮罩mask程序仅用于把 mask drawable 写入 stencil
let mask_vert = compile_shader(&gl, gl::GL_VERTEX_SHADER, MASK_VERT_SHADER_SRC)?;
let mask_frag = compile_shader(&gl, gl::GL_FRAGMENT_SHADER, MASK_FRAG_SHADER_SRC)?;
let mask_program = unsafe {
let p = (gl.gl_create_program)();
(gl.gl_attach_shader)(p, mask_vert);
(gl.gl_attach_shader)(p, mask_frag);
(gl.gl_link_program)(p);
let mut status = 0;
(gl.gl_get_programiv)(p, gl::GL_LINK_STATUS, &mut status);
if status == 0 {
let mut log: Vec<u8> = vec![0u8; 1024];
(gl.gl_get_program_info_log)(p, 1024, std::ptr::null_mut(), log.as_mut_ptr() as *mut c_char);
return Err(anyhow!("mask shader link failed: {}", String::from_utf8_lossy(&log)));
}
p
};
let mask_attrib_position = {
let c = std::ffi::CString::new("a_position")?;
unsafe { (gl.gl_get_attrib_location)(mask_program, c.as_ptr()) }
};
let mask_attrib_tex_coord = {
let c = std::ffi::CString::new("a_texCoord")?;
unsafe { (gl.gl_get_attrib_location)(mask_program, c.as_ptr()) }
};
let mask_uniform_matrix = {
let c = std::ffi::CString::new("u_matrix")?;
unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) }
};
let mask_uniform_texture = {
let c = std::ffi::CString::new("s_texture0")?;
unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) }
};
let mask_uniform_multiply_color = {
let c = std::ffi::CString::new("u_multiplyColor")?;
unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) }
};
let mask_uniform_screen_color = {
let c = std::ffi::CString::new("u_screenColor")?;
unsafe { (gl.gl_get_uniform_location)(mask_program, c.as_ptr()) }
};
println!( println!(
"[Live2D] GL 程序就绪: program={} pos={} tex={} mat={} tex_u={} base={} mult={} scr={}", "[Live2D] GL 程序就绪: program={} pos={} tex={} mat={} tex_u={} base={} mult={} scr={}",
program, attrib_position, attrib_tex_coord, uniform_matrix, program, attrib_position, attrib_tex_coord, uniform_matrix,
uniform_texture, uniform_base_color, uniform_multiply_color, uniform_screen_color uniform_texture, uniform_base_color, uniform_multiply_color, uniform_screen_color
); );
println!(
"[Live2D] 渐变程序就绪: grad={} pos={} top={} bottom={}",
grad_program, grad_attrib_pos, grad_uniform_top, grad_uniform_bottom
);
Ok(Self { Ok(Self {
gl, gl,
@@ -674,8 +871,19 @@ impl RenderContext {
uniform_base_color, uniform_base_color,
uniform_multiply_color, uniform_multiply_color,
uniform_screen_color, uniform_screen_color,
width, grad_program,
height, grad_attrib_pos,
grad_uniform_top,
grad_uniform_bottom,
mask_program,
mask_attrib_position,
mask_attrib_tex_coord,
mask_uniform_matrix,
mask_uniform_texture,
mask_uniform_multiply_color,
mask_uniform_screen_color,
width: win_w,
height: win_h,
}) })
} }
@@ -713,6 +921,47 @@ impl RenderContext {
} }
} }
/// 绘制全屏渐变背景在模型之前调用。top/bottom 为 RGB范围 0..1。
pub fn draw_background(&self, top: [f32; 3], bottom: [f32; 3]) {
unsafe {
// 背景不透明,先关闭混合
(self.gl.gl_disable)(gl::GL_BLEND);
(self.gl.gl_use_program)(self.grad_program);
let verts: [f32; 8] = [
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
1.0, 1.0,
];
let mut vbo = 0;
(self.gl.gl_gen_buffers)(1, &mut vbo);
(self.gl.gl_bind_buffer)(gl::GL_ARRAY_BUFFER, vbo);
(self.gl.gl_buffer_data)(
gl::GL_ARRAY_BUFFER,
(verts.len() * 4) as isize,
verts.as_ptr() as *const c_void,
gl::GL_STATIC_DRAW,
);
(self.gl.gl_enable_vertex_attrib_array)(self.grad_attrib_pos as u32);
(self.gl.gl_vertex_attrib_pointer)(
self.grad_attrib_pos as u32,
2,
gl::GL_FLOAT,
gl::GL_FALSE,
0,
std::ptr::null(),
);
(self.gl.gl_uniform4f)(self.grad_uniform_top, top[0], top[1], top[2], 1.0);
(self.gl.gl_uniform4f)(self.grad_uniform_bottom, bottom[0], bottom[1], bottom[2], 1.0);
(self.gl.gl_draw_arrays)(gl::GL_TRIANGLE_STRIP, 0, 4);
(self.gl.gl_delete_buffers)(1, &vbo);
// 恢复混合,供后续模型绘制
(self.gl.gl_enable)(gl::GL_BLEND);
}
}
pub fn clear(&self) { pub fn clear(&self) {
unsafe { (self.gl.gl_clear)(gl::GL_COLOR_BUFFER_BIT); } unsafe { (self.gl.gl_clear)(gl::GL_COLOR_BUFFER_BIT); }
} }

View File

@@ -11,6 +11,8 @@ use std::path::Path;
/// 一个 drawable 的渲染所需数据快照 /// 一个 drawable 的渲染所需数据快照
#[derive(Clone)] #[derive(Clone)]
pub struct DrawableSnapshot { pub struct DrawableSnapshot {
/// 在 Core drawable 数组中的原始索引(用于查 mask 引用)
pub index: usize,
pub texture_index: i32, pub texture_index: i32,
pub draw_order: i32, pub draw_order: i32,
pub render_order: i32, pub render_order: i32,
@@ -23,6 +25,8 @@ pub struct DrawableSnapshot {
pub indices: Vec<u16>, pub indices: Vec<u16>,
pub multiply_color: [f32; 4], pub multiply_color: [f32; 4],
pub screen_color: [f32; 4], pub screen_color: [f32; 4],
/// 本 drawable 依赖的 mask drawable 索引(空表示无遮罩)
pub mask_indices: Vec<i32>,
pub mask_counts: i32, pub mask_counts: i32,
} }
@@ -148,8 +152,8 @@ impl CubismModel {
}; };
println!( println!(
"[Live2D] 模型加载成功: 参数={} drawable={} canvas={:?}x{:.0}ppu", "[Live2D] 模型加载成功: 参数={} drawable={} canvas={:?} origin=({:.1},{:.1})x{:.0}ppu",
param_count, drawable_count, canvas.size, canvas.pixels_per_unit param_count, drawable_count, canvas.size, canvas.origin[0], canvas.origin[1], canvas.pixels_per_unit
); );
Ok(Self { Ok(Self {
@@ -254,6 +258,7 @@ impl CubismModel {
let indices = (core.csm_get_drawable_indices)(self.model); let indices = (core.csm_get_drawable_indices)(self.model);
let multiply_colors = (core.csm_get_drawable_multiply_colors)(self.model); let multiply_colors = (core.csm_get_drawable_multiply_colors)(self.model);
let screen_colors = (core.csm_get_drawable_screen_colors)(self.model); let screen_colors = (core.csm_get_drawable_screen_colors)(self.model);
let masks = (core.csm_get_drawable_masks)(self.model);
// 先收集每个 drawable 的原始索引 // 先收集每个 drawable 的原始索引
let mut items: Vec<(usize, i32)> = (0..count) let mut items: Vec<(usize, i32)> = (0..count)
@@ -287,7 +292,18 @@ impl CubismModel {
let mc = *multiply_colors.add(i); let mc = *multiply_colors.add(i);
let sc = *screen_colors.add(i); let sc = *screen_colors.add(i);
// 取出本 drawable 的 mask 索引列表
let mk = *mask_counts.add(i) as usize;
let mut mask_indices: Vec<i32> = Vec::with_capacity(mk);
if mk > 0 {
let mptr = *masks.add(i);
for k in 0..mk {
mask_indices.push(*mptr.add(k));
}
}
DrawableSnapshot { DrawableSnapshot {
index: i,
texture_index: *texture_indices.add(i), texture_index: *texture_indices.add(i),
draw_order: *draw_orders.add(i), draw_order: *draw_orders.add(i),
render_order: ro, render_order: ro,
@@ -300,7 +316,8 @@ impl CubismModel {
indices: idx_vec, indices: idx_vec,
multiply_color: [mc.x, mc.y, mc.z, mc.w], multiply_color: [mc.x, mc.y, mc.z, mc.w],
screen_color: [sc.x, sc.y, sc.z, sc.w], screen_color: [sc.x, sc.y, sc.z, sc.w],
mask_counts: *mask_counts.add(i), mask_indices,
mask_counts: mk as i32,
} }
}) })
.collect() .collect()

View File

@@ -52,7 +52,8 @@ impl Live2DRenderer {
println!("[Live2D] Uploaded {} textures", texture_ids.len()); println!("[Live2D] Uploaded {} textures", texture_ids.len());
let canvas = model.canvas; let canvas = model.canvas;
let model_matrix = compute_model_matrix(canvas, width, height); // 用真实窗口尺寸(已被 gl_ctx 修正为显示器实际分辨率)计算矩阵
let model_matrix = compute_model_matrix(canvas, gl_ctx.width, gl_ctx.height);
model.reset_to_default(&core); model.reset_to_default(&core);
@@ -84,8 +85,16 @@ impl Live2DRenderer {
unsafe { unsafe {
let gl = &self.gl_ctx.gl; let gl = &self.gl_ctx.gl;
// 每帧校正 viewport 为窗口实际尺寸,避免窗口被 WM 调整大小后
// 仍用旧的小尺寸(如 800x800绘制导致模型只出现在左下角。
(gl.gl_viewport)(0, 0, self.gl_ctx.width, self.gl_ctx.height);
(gl.gl_clear_color)(0.0, 0.0, 0.0, 1.0); (gl.gl_clear_color)(0.0, 0.0, 0.0, 1.0);
(gl.gl_clear)(gl_const::GL_COLOR_BUFFER_BIT); (gl.gl_clear)(gl_const::GL_COLOR_BUFFER_BIT);
// 先画渐变背景再画模型draw_background 内部会恢复 BLEND
self.gl_ctx.draw_background(
[0.05, 0.09, 0.17], // 顶部:深蓝(全息风)
[0.01, 0.02, 0.04], // 底部:近黑
);
(gl.gl_enable)(gl_const::GL_BLEND); (gl.gl_enable)(gl_const::GL_BLEND);
(gl.gl_use_program)(self.gl_ctx.program); (gl.gl_use_program)(self.gl_ctx.program);
(gl.gl_uniform_matrix4fv)( (gl.gl_uniform_matrix4fv)(
@@ -96,11 +105,19 @@ impl Live2DRenderer {
); );
} }
// 建立 原始索引 -> 快照 的查找表,供被遮罩 drawable 引用其 mask
let snap_by_index: std::collections::HashMap<usize, &DrawableSnapshot> =
snapshots.iter().map(|s| (s.index, s)).collect();
for snap in &snapshots { for snap in &snapshots {
if snap.opacity <= 0.001 { if snap.opacity <= 0.001 {
continue; continue;
} }
self.draw_drawable(snap); if snap.mask_indices.is_empty() {
self.draw_drawable(snap);
} else {
self.draw_masked_drawable(snap, &snap_by_index);
}
} }
self.model.reset_dynamic_flags(&self.core); self.model.reset_dynamic_flags(&self.core);
@@ -211,6 +228,147 @@ impl Live2DRenderer {
} }
} }
/// 绘制一个被遮罩masked的 drawable
/// 先用 mask 着色器把所有 mask drawable 写入 stencil 缓冲,再以 stencil 裁切
/// 实际 drawable从而把后臂/头发等限制在 mask 形状内、不再整块溢出。
fn draw_masked_drawable(
&self,
snap: &DrawableSnapshot,
snap_by_index: &std::collections::HashMap<usize, &DrawableSnapshot>,
) {
let gl = &self.gl_ctx.gl;
unsafe {
// 1) 用 mask 程序把 mask drawable 形状写入 stencil颜色关闭
(gl.gl_enable)(gl_const::GL_STENCIL_TEST);
(gl.gl_stencil_mask)(0xFF);
(gl.gl_clear)(gl_const::GL_STENCIL_BUFFER_BIT);
(gl.gl_color_mask)(0, 0, 0, 0);
(gl.gl_stencil_func)(gl_const::GL_ALWAYS, 1, 0xFF);
(gl.gl_stencil_op)(gl_const::GL_KEEP, gl_const::GL_KEEP, gl_const::GL_REPLACE);
(gl.gl_use_program)(self.gl_ctx.mask_program);
(gl.gl_uniform_matrix4fv)(
self.gl_ctx.mask_uniform_matrix,
1,
gl_const::GL_FALSE,
self.model_matrix.as_ptr(),
);
for &mi in &snap.mask_indices {
if let Some(m) = snap_by_index.get(&(mi as usize)) {
self.draw_mask_drawable(m);
}
}
// 2) 用 stencil 裁切实际 drawable
(gl.gl_color_mask)(1, 1, 1, 1);
(gl.gl_stencil_op)(gl_const::GL_KEEP, gl_const::GL_KEEP, gl_const::GL_KEEP);
let inverted =
(snap.constant_flags & super::core_ffi::constant_flags::IS_INVERTED_MASK) != 0;
if inverted {
(gl.gl_stencil_func)(gl_const::GL_EQUAL, 0, 0xFF);
} else {
(gl.gl_stencil_func)(gl_const::GL_NOTEQUAL, 0, 0xFF);
}
(gl.gl_use_program)(self.gl_ctx.program);
self.draw_drawable(snap);
// 复位,避免影响后续无遮罩 drawable
(gl.gl_disable)(gl_const::GL_STENCIL_TEST);
(gl.gl_stencil_mask)(0xFF);
}
}
/// 用 mask 着色器把一个 drawable 画进 stencil颜色关闭仅 alpha 形状)。
fn draw_mask_drawable(&self, snap: &DrawableSnapshot) {
let gl = &self.gl_ctx.gl;
unsafe {
let tex_idx = snap.texture_index as usize;
if tex_idx < self.texture_ids.len() {
(gl.gl_active_texture)(gl_const::GL_TEXTURE0);
(gl.gl_bind_texture)(gl_const::GL_TEXTURE_2D, self.texture_ids[tex_idx]);
(gl.gl_uniform1i)(self.gl_ctx.mask_uniform_texture, 0);
}
(gl.gl_uniform4f)(
self.gl_ctx.mask_uniform_multiply_color,
snap.multiply_color[0],
snap.multiply_color[1],
snap.multiply_color[2],
snap.multiply_color[3],
);
(gl.gl_uniform4f)(
self.gl_ctx.mask_uniform_screen_color,
snap.screen_color[0],
snap.screen_color[1],
snap.screen_color[2],
snap.screen_color[3],
);
let vc = snap.vertex_positions.len();
let mut vertex_data: Vec<f32> = Vec::with_capacity(vc * 4);
for i in 0..vc {
vertex_data.push(snap.vertex_positions[i][0]);
vertex_data.push(snap.vertex_positions[i][1]);
vertex_data.push(snap.vertex_uvs[i][0]);
vertex_data.push(snap.vertex_uvs[i][1]);
}
let mut vbo = 0u32;
(gl.gl_gen_buffers)(1, &mut vbo);
(gl.gl_bind_buffer)(gl_const::GL_ARRAY_BUFFER, vbo);
(gl.gl_buffer_data)(
gl_const::GL_ARRAY_BUFFER,
(vertex_data.len() * 4) as isize,
vertex_data.as_ptr() as *const std::ffi::c_void,
gl_const::GL_STATIC_DRAW,
);
let mut ibo = 0u32;
(gl.gl_gen_buffers)(1, &mut ibo);
(gl.gl_bind_buffer)(gl_const::GL_ELEMENT_ARRAY_BUFFER, ibo);
(gl.gl_buffer_data)(
gl_const::GL_ELEMENT_ARRAY_BUFFER,
(snap.indices.len() * 2) as isize,
snap.indices.as_ptr() as *const std::ffi::c_void,
gl_const::GL_STATIC_DRAW,
);
let stride = 4 * 4;
if self.gl_ctx.mask_attrib_position >= 0 {
(gl.gl_enable_vertex_attrib_array)(self.gl_ctx.mask_attrib_position as u32);
(gl.gl_vertex_attrib_pointer)(
self.gl_ctx.mask_attrib_position as u32,
2,
gl_const::GL_FLOAT,
gl_const::GL_FALSE,
stride,
std::ptr::null(),
);
}
if self.gl_ctx.mask_attrib_tex_coord >= 0 {
(gl.gl_enable_vertex_attrib_array)(self.gl_ctx.mask_attrib_tex_coord as u32);
(gl.gl_vertex_attrib_pointer)(
self.gl_ctx.mask_attrib_tex_coord as u32,
2,
gl_const::GL_FLOAT,
gl_const::GL_FALSE,
stride,
(2 * 4) as *const std::ffi::c_void,
);
}
(gl.gl_draw_elements)(
gl_const::GL_TRIANGLES,
snap.indices.len() as i32,
gl_const::GL_UNSIGNED_SHORT,
std::ptr::null(),
);
(gl.gl_delete_buffers)(1, &vbo);
(gl.gl_delete_buffers)(1, &ibo);
}
}
pub fn run_loop(&mut self) -> Result<()> { pub fn run_loop(&mut self) -> Result<()> {
self.running = true; self.running = true;
let frame_duration = Duration::from_millis(16); let frame_duration = Duration::from_millis(16);
@@ -235,22 +393,36 @@ impl Live2DRenderer {
} }
/// Compute model -> NDC transform matrix (column-major 4x4). /// Compute model -> NDC transform matrix (column-major 4x4).
/// Cubism coords: origin at canvas center, Y up, unit = pixel. ///
/// `csmGetDrawableVertexPositions` 返回的顶点位于**模型单位**坐标系,范围约为
/// ±(canvas_w/2 / ppu)。`pixels_per_unit`(ppu) 指明 1 模型单位 = ppu 个画布像素。
/// 因此顶点必须先乘 `ppu` 回到画布像素,再映射到 [-1,1] 裁剪空间。
///
/// 实测 Haru 的 `origin = (canvas_w/2, canvas_h/2)`,即模型坐标原点本身就位于
/// 画布中心,所以**无需再做原点平移**平移反而会把模型推出屏幕。Cubism 顶点
/// 以画布中心为原点、Y 轴向上,与裁剪空间一致,故无需平移或翻转。
///
/// 适配策略:**contain整只角色恰好放入视口**,单一均匀缩放即可。
/// NDC 立方体由视口自动拉伸到窗口,因此**绝对不能**对 x/y 轴分别乘不同系数,
/// 否则会破坏长宽比(手臂被拉变形)。这里只按画布较长边归一,使整只角色完整
/// 可见、不裁切、不拉伸;窗口为横屏时角色会填满高度、两侧留渐变背景。
fn compute_model_matrix( fn compute_model_matrix(
canvas: super::model::CanvasInfo, canvas: super::model::CanvasInfo,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> [f32; 16] { ) -> [f32; 16] {
let canvas_w = canvas.size[0].max(1.0); let cw = canvas.size[0].max(1.0);
let canvas_h = canvas.size[1].max(1.0); let ch = canvas.size[1].max(1.0);
let ppu = canvas.pixels_per_unit.max(1.0);
let scale_x = 2.0 / canvas_w; // 均匀缩放按画布较长边把整只角色映射到裁剪空间margin=1.0 即恰好贴满,
let scale_y = 2.0 / canvas_h; // 若想留一点边距可改小,如 0.95)。
let scale = scale_x.min(scale_y); let margin = 1.0;
let u = 2.0 * ppu * margin / cw.max(ch);
[ [
scale, 0.0, 0.0, 0.0, u, 0.0, 0.0, 0.0,
0.0, scale, 0.0, 0.0, 0.0, u, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
] ]