feat(model): 添加液化笔划数据的序列化与反序列化支持

- 在 ModelData.PartData 中新增 liquifyStrokes 字段用于保存液化笔划- 实现通过反射读取 ModelPart 的液化笔划数据(兼容旧版本)- 支持多种数据结构形式的液化点读取(Vector2f、自定义类、Map)
- 反序列化时自动重放液化笔划到 ModelPart- 添加 LiquifyStrokeData 和 LiquifyPointData 用于序列化存储
- 提供深度拷贝支持以确保 liquifyStrokes 数据完整复制
- 增加 ModelLoadTest 测试类用于验证模型加载与结构检查
This commit is contained in:
tzdwindows 7
2025-10-12 08:01:25 +08:00
parent 16af846e48
commit 22c3661d6e
6 changed files with 1436 additions and 56 deletions

View File

@@ -149,43 +149,83 @@ public final class ModelRender {
uniform int uLightsIsAmbient[MAX_LIGHTS];
uniform int uLightCount;
// 常用衰减系数(可在 shader 内微调)
const float ATT_CONST = 1.0;
const float ATT_LINEAR = 0.09;
const float ATT_QUAD = 0.032;
void main() {
if (uDebugMode == 1) {
FragColor = vec4(vWorldPos * 0.5 + 0.5, 0.0, 1.0);
// 先采样纹理
vec4 tex = texture(uTexture, vTexCoord);
float alpha = tex.a * uOpacity;
if (alpha <= 0.001) discard;
// 如果没有光源,跳过光照计算(性能更好并且保持原始贴图色)
if (uLightCount == 0) {
vec3 base = tex.rgb * uColor.rgb;
// 简单的色调映射(防止数值过大)
base = clamp(base, 0.0, 1.0);
FragColor = vec4(base, alpha);
return;
}
vec4 tex = texture(uTexture, vTexCoord);
vec3 finalColor = tex.rgb * uColor.rgb;
vec3 lighting = vec3(0.0);
// 基础颜色(纹理 * 部件颜色)
vec3 baseColor = tex.rgb * uColor.rgb;
for (int i = 0; i < uLightCount; i++) {
// 全局环境光基线(可以适度提高以避免全黑)
vec3 ambient = vec3(0.06); // 小环境光补偿
vec3 lighting = vec3(0.0);
vec3 specularAccum = vec3(0.0);
// 累积环境光(来自被标记为环境光的光源)
for (int i = 0; i < uLightCount; ++i) {
if (uLightsIsAmbient[i] == 1) {
lighting += uLightsColor[i] * uLightsIntensity[i];
}
}
for (int i = 0; i < uLightCount; i++) {
// 加上基线环境光
lighting += ambient;
// 对每个非环境光计算基于距离的衰减与简单高光
for (int i = 0; i < uLightCount; ++i) {
if (uLightsIsAmbient[i] == 1) continue;
float intensity = uLightsIntensity[i];
if (intensity <= 0.0) continue;
vec2 lightDir = uLightsPos[i] - vWorldPos;
float dist = length(lightDir);
float atten = 1.0 / (1.0 + 0.1 * dist + 0.01 * dist * dist);
lighting += uLightsColor[i] * intensity * atten;
vec2 toLight = uLightsPos[i] - vWorldPos;
float dist = length(toLight);
// 标准物理式衰减
float attenuation = ATT_CONST / (ATT_CONST + ATT_LINEAR * dist + ATT_QUAD * dist * dist);
// 强度受光源强度和衰减影响
float radiance = uLightsIntensity[i] * attenuation;
// 漫反射在纯2D情景下法线与视线近似固定Z向
// 所以漫反射对所有片元是恒定的。我们用一个基于距离的柔和因子来模拟明暗变化。
float diffuseFactor = clamp(1.0 - (dist * 0.0015), 0.0, 1.0); // 通过调节常数控制半径感觉
vec3 diff = uLightsColor[i] * radiance * diffuseFactor;
lighting += diff;
// 简单高光(基于视向与反射的大致模拟,产生亮点)
vec3 lightDir3 = normalize(vec3(toLight, 0.0));
vec3 viewDir = vec3(0.0, 0.0, 1.0);
vec3 normal = vec3(0.0, 0.0, 1.0);
vec3 reflectDir = reflect(-lightDir3, normal);
float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), 16.0); // 16 为高光粗糙度,可调
float specIntensity = 0.2; // 高光强度系数
specularAccum += uLightsColor[i] * radiance * specFactor * specIntensity;
}
finalColor *= min(lighting, vec3(2.0));
if (uBlendMode == 1) finalColor.rgb = tex.rgb + uColor.rgb;
else if (uBlendMode == 2) finalColor.rgb = tex.rgb * uColor.rgb;
else if (uBlendMode == 3) finalColor.rgb = 1.0 - (1.0 - tex.rgb) * (1.0 - uColor.rgb);
float alpha = tex.a * uOpacity;
if (alpha <= 0.001) discard;
// 限制光照的最大值以避免过曝(可根据场景调整)
vec3 totalLighting = min(lighting + specularAccum, vec3(2.0));
// 将光照应用到基础颜色
vec3 finalColor = baseColor * totalLighting;
// 支持简单混合模式(保留原有行为)
if (uBlendMode == 1) finalColor = tex.rgb + uColor.rgb;
else if (uBlendMode == 2) finalColor = tex.rgb * uColor.rgb;
else if (uBlendMode == 3) finalColor = 1.0 - (1.0 - tex.rgb) * (1.0 - uColor.rgb);
finalColor = clamp(finalColor, 0.0, 1.0);
FragColor = vec4(finalColor, alpha);
}
""";
@@ -228,31 +268,33 @@ public final class ModelRender {
}
private static void uploadLightsToShader(ShaderProgram sp, Model2D model) {
List<LightSource> lights = model.getLights();
int lightCount = Math.min(lights.size(), 8);
List<com.chuangzhou.vivid2D.render.model.util.LightSource> lights = model.getLights();
int idx = 0;
// 设置光源数量
setUniformIntInternal(sp, "uLightCount", lightCount);
for (int i = 0; i < lightCount; i++) {
LightSource l = lights.get(i);
// 只上传已启用的光源,最多 MAX_LIGHTS8
for (int i = 0; i < lights.size() && idx < 8; i++) {
com.chuangzhou.vivid2D.render.model.util.LightSource l = lights.get(i);
if (!l.isEnabled()) continue;
// 设置光源位置环境光位置设为0
Vector2f pos = l.isAmbient() ? new Vector2f(0, 0) : l.getPosition();
setUniformVec2Internal(sp, "uLightsPos[" + i + "]", pos);
// 环境光的 position 在 shader 中不会用于距离计算,但我们也上传(安全
setUniformVec2Internal(sp, "uLightsPos[" + idx + "]", l.isAmbient() ? new org.joml.Vector2f(0f, 0f) : l.getPosition());
setUniformVec3Internal(sp, "uLightsColor[" + idx + "]", l.getColor());
setUniformFloatInternal(sp, "uLightsIntensity[" + idx + "]", l.getIntensity());
setUniformIntInternal(sp, "uLightsIsAmbient[" + idx + "]", l.isAmbient() ? 1 : 0);
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", l.getColor());
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", l.getIntensity());
// 设置是否为环境光
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", l.isAmbient() ? 1 : 0);
idx++;
}
// 禁用未使用的光源
for (int i = lightCount; i < 8; i++) {
// 上传实际有效光源数量
setUniformIntInternal(sp, "uLightCount", idx);
// 禁用剩余槽位(确保 shader 中不会读取到垃圾值)
for (int i = idx; i < 8; i++) {
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
// color/pos 不严格必要,但清零更稳健
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f));
setUniformVec2Internal(sp, "uLightsPos[" + i + "]", new org.joml.Vector2f(0f, 0f));
}
}