Files
agame/character/player/face/face_paint.gdshader
T
2026-10-03 17:26:42 +08:00

74 lines
3.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
shader_type spatial;
// 全身 shader:body_mesh 只有一个材质 mat_body —— 身体底色照常取它的 BaseColor。
// 脸不再靠 UV 矩形,而是「贴在身体正面上的一块贴花」:用**模型本地坐标**的 (x, y) 定位,
// 再用「法线朝前的程度」当遮罩。
// 这样它和 UV 孤岛怎么排完全无关 —— 表情可以放在身体正面任意位置,也不会再越界糊到肚子上。
//
// 贴图 alpha 只代表「墨线覆盖率」,绝不写进空间 ALPHA —— body_mesh 的面片必须保持不透明。
// ================= 调表情就改这一段 =================
// 中心点是**模型本地坐标**(和 mesh 顶点同一个空间,模型整体大约 x -3~3、y -3~3)。
// 想往下挪就减小 .y,往左挪就减小 .x。
uniform vec2 face_center = vec2(0.0, 0.6);
// 表情大小:.x 是左右宽度,.y 是上下高度
uniform vec2 face_size = vec2(4.0, 4.0);
// 只在身体正面出现:朝**模型本地 -Z**(模型自己的正面)的那面才画。
// 调大就只留最正面的一小块,调小会往侧面包过去。
uniform float face_front_edge : hint_range(0.0, 1.0) = 0.0;
// 上下颠倒了就改成 false
uniform bool face_flip_y = true;
// ====================================================
uniform sampler2D body_tex : source_color, filter_linear, repeat_disable,
hint_default_white;
uniform vec4 body_tint : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float surface_roughness : hint_range(0.0, 1.0) = 0.65;
uniform float surface_metallic : hint_range(0.0, 1.0) = 0.0;
// 表情底图:内置表情是保留原排布、已去底的透明图集;本地表情是整张透明 PNG。
uniform sampler2D base_tex : source_color, filter_linear, repeat_disable,
hint_default_transparent;
uniform vec2 base_uv_min = vec2(0.0, 0.0);
uniform vec2 base_uv_size = vec2(1.0, 1.0);
// 玩家笔画层(透明底)
uniform sampler2D paint_tex : source_color, filter_linear, repeat_disable,
hint_default_transparent;
// 贴花要用「模型本地坐标」,而 fragment 里拿不到,所以从顶点阶段传过来
varying vec3 object_position;
varying vec3 object_normal;
void vertex() {
object_position = VERTEX;
object_normal = NORMAL;
}
void fragment() {
// 全身底色都来自 mat_body 的 BaseColor,表情只是盖在脸上
vec3 albedo = texture(body_tex, UV).rgb * body_tint.rgb;
vec2 face_uv = (object_position.xy - (face_center - face_size * 0.5)) / face_size;
if (face_flip_y) {
face_uv.y = 1.0 - face_uv.y;
}
// 用 step 而不是 if:采样留在统一控制流里,免得 mipmap 的导数在分支里未定义
float inside = step(0.0, face_uv.x) * step(face_uv.x, 1.0)
* step(0.0, face_uv.y) * step(face_uv.y, 1.0);
// 背面的点 (x, y) 和正面一样,也会落进矩形,所以要再按「朝不朝前」筛一遍。
// 注意模型自己的正面是**本地 -Z**(预览和玩家场景里那层 Transform 都把它转了 180°),
// 所以这里取 -normal.z;写成 +normal.z 表情就会跑到背上。
float front = smoothstep(face_front_edge, face_front_edge + 0.3,
normalize(object_normal).z);
float mask = inside * front;
vec4 base = texture(base_tex, base_uv_min + face_uv * base_uv_size);
vec4 paint = texture(paint_tex, face_uv);
// 透明处保持身体底色 —— 所以脸和周围不可能出现底色差
albedo = mix(albedo, base.rgb, base.a * mask);
albedo = mix(albedo, paint.rgb, paint.a * mask);
ALBEDO = albedo;
ROUGHNESS = surface_roughness;
METALLIC = surface_metallic;
}