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_NONE: c_int = 0x3038;
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 EGLConfig = *mut c_void;
@@ -94,13 +96,14 @@ pub mod x11 {
// ============ GLES2 ============
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 GLuint = c_uint;
pub type GLint = c_int;
pub type GLsizei = c_int;
pub type GLbitfield = c_uint;
pub type GLboolean = c_uchar;
pub const GL_FLOAT: GLenum = 0x1406;
pub const GL_ARRAY_BUFFER: GLenum = 0x8892;
@@ -121,6 +124,7 @@ pub mod gl {
pub const GL_COMPILE_STATUS: GLenum = 0x8B81;
pub const GL_LINK_STATUS: GLenum = 0x8B82;
pub const GL_TRIANGLES: GLenum = 0x0004;
pub const GL_TRIANGLE_STRIP: GLenum = 0x0005;
pub const GL_BLEND: GLenum = 0x0BE2;
pub const GL_SRC_ALPHA: GLenum = 0x0302;
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_FALSE: 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};
@@ -161,7 +179,59 @@ void main() {
texColor.rgb = texColor.rgb * u_multiplyColor.rgb;
texColor.rgb = texColor.rgb + u_screenColor.rgb - (texColor.rgb * u_screenColor.rgb);
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_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_disable: unsafe extern "C" fn(cap: 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_create_shader: unsafe extern "C" fn(t: gl::GLenum) -> gl::GLuint,
@@ -290,8 +361,18 @@ pub struct Gl {
type_: gl::GLenum,
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_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_create_window: unsafe extern "C" fn(
@@ -360,6 +441,7 @@ impl Gl {
gl_clear: *lib_gl.get(b"glClear\0")?,
gl_clear_color: *lib_gl.get(b"glClearColor\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_viewport: *lib_gl.get(b"glViewport\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_vertex_attrib_pointer: *lib_gl.get(b"glVertexAttribPointer\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_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_create_window: *lib_x11.get(b"XCreateWindow\0")?,
@@ -431,6 +518,18 @@ pub struct RenderContext {
pub uniform_base_color: GLint,
pub uniform_multiply_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 height: i32,
}
@@ -476,11 +575,12 @@ impl RenderContext {
let screen = 0; // XDefaultScreen(display),通常 0
let screen_w = unsafe { (gl.x_display_width)(x_display, screen) };
let screen_h = unsafe { (gl.x_display_height)(x_display, screen) };
// 用传入的 width/height 创建窗口(配置值),不用屏幕分辨率
// EGL surface 尺寸需与窗口匹配,大窗口可能导致 GPU 限制问题
let win_w = width;
let win_h = height;
println!("[Live2D] 窗口尺寸: {}x{} (屏幕 {}x{})", win_w, win_h, screen_w, screen_h);
// 全屏渲染:直接使用显示器实际分辨率创建窗口与 EGL surface避免
// 「帧缓冲 800x800 被放进 1920x1080 窗口、模型只出现在左下角」的问题
// 若 X11 取不到屏幕分辨率,再退回传入的 width/height。
let win_w = if screen_w > 0 && screen_h > 0 { screen_w } else { width };
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 {
background_pixmap: 0,
@@ -569,7 +669,20 @@ impl RenderContext {
}
println!("[Live2D] EGL {}.{}", major, minor);
// 主配置请求 depth+stencilLive2D 遮罩需要 stencil
// 若设备不支持该组合,退回不带 depth/stencil 的配置。
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_GREEN_SIZE, 8,
egl::EGL_BLUE_SIZE, 8,
@@ -580,18 +693,26 @@ impl RenderContext {
];
let mut config: egl::EGLConfig = std::ptr::null_mut();
let mut num_config = 0;
if unsafe {
(gl.egl_choose_config)(
display,
config_attrs.as_ptr(),
&mut config,
1,
&mut num_config,
)
} == 0 || num_config == 0
{
let mut choose = |attrs: &[c_int]| -> bool {
let mut cfg: egl::EGLConfig = std::ptr::null_mut();
let mut n = 0;
let ok = unsafe {
(gl.egl_choose_config)(display, attrs.as_ptr(), &mut cfg, 1, &mut n)
} != 0 && n > 0;
if ok {
config = cfg;
num_config = n;
}
ok
};
if !choose(&config_attrs) && !choose(&config_attrs_fallback) {
return Err(anyhow!("eglChooseConfig 失败"));
}
println!(
"[Live2D] EGL 配置选定: 含stencil={} (num_config={})",
config_attrs.len() > 9,
num_config
);
let surface = unsafe {
(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_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!(
"[Live2D] GL 程序就绪: program={} pos={} tex={} mat={} tex_u={} base={} mult={} scr={}",
program, attrib_position, attrib_tex_coord, uniform_matrix,
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 {
gl,
@@ -674,8 +871,19 @@ impl RenderContext {
uniform_base_color,
uniform_multiply_color,
uniform_screen_color,
width,
height,
grad_program,
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) {
unsafe { (self.gl.gl_clear)(gl::GL_COLOR_BUFFER_BIT); }
}