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_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+stencil(Live2D 遮罩需要 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); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user