feat(renderer): 添加遮罩支持,优化绘制流程和背景渐变
This commit is contained in:
@@ -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+stencil(Live2D 遮罩需要 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); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
if snap.mask_indices.is_empty() {
|
||||||
self.draw_drawable(snap);
|
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,
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user