Files
window-axis-innovators-box/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java

836 lines
33 KiB
Java
Raw Normal View History

package com.chuangzhou.vivid2D.render;
import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.chuangzhou.vivid2D.render.model.buffer.BufferBuilder;
import com.chuangzhou.vivid2D.render.model.util.LightSource;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem;
import org.joml.Matrix3f;
import org.joml.Vector2f;
import org.joml.Vector4f;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
/**
* vivid2D 模型完整渲染系统
*
* <p>该系统提供了完整的 vivid2D 模型加载渲染和显示功能支持多种渲染模式和效果</p>
*
* <ul>
* <li>基础模型渲染</li>
* <li>光照效果渲染</li>
* <li>纹理贴图渲染</li>
* <li>模型加载与解析</li>
* </ul>
*
* <h3>使用示例</h3>
* <ul>
* <li>{@link com.chuangzhou.vivid2D.test.ModelLoadTest} - 模型加载测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderLightingTest} - 光照渲染测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTest} - 基础渲染测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTest2} - 进阶渲染测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTextureTest} - 纹理渲染测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelTest} - 基础模型测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelTest2} - 进阶模型测试</li>
* </ul>
*
* @author tzdwindows
* @version 1.0
* @since 2025-10-13
*/
public final class ModelRender {
private static final Logger logger = LoggerFactory.getLogger(ModelRender.class);
private ModelRender() { /* no instances */ }
// ================== 全局状态 ==================
private static boolean initialized = false;
private static int viewportWidth = 800;
private static int viewportHeight = 600;
private static final Vector4f CLEAR_COLOR = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
private static final boolean enableDepthTest = false;
private static final boolean enableBlending = true;
// 着色器与资源
private static final Map<String, ShaderProgram> shaderMap = new HashMap<>();
private static ShaderProgram defaultProgram = null;
private static final Map<Mesh2D, MeshGLResources> meshResources = new HashMap<>();
private static final AtomicInteger textureUnitAllocator = new AtomicInteger(0);
// 默认白色纹理
private static int defaultTextureId = 0;
// ================== 碰撞箱渲染配置 ==================
// 是否在渲染时绘制碰撞箱(线框)
public static boolean renderColliders = false;
// 碰撞箱线宽
public static float colliderLineWidth = 2.0f;
// 碰撞箱颜色(默认白色)
public static Vector4f colliderColor = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
// 圆形碰撞体绘制细分(越高越圆)
private static final int CIRCLE_SEGMENTS = 32;
// 是否在渲染时绘制碰撞箱
public static boolean renderLightPositions = true;
// ================== 内部类ShaderProgram ==================
private static class ShaderProgram {
final int programId;
final Map<String, Integer> uniformCache = new HashMap<>();
ShaderProgram(int programId) {
this.programId = programId;
}
void use() {
GL20.glUseProgram(programId);
}
void stop() {
GL20.glUseProgram(0);
}
int getUniformLocation(String name) {
return uniformCache.computeIfAbsent(name, k -> {
int loc = glGetUniformLocation(programId, k);
if (loc == -1) {
// debug 时可以打开
logger.warn("Warning: uniform not found: {}", k);
}
return loc;
});
}
void delete() {
if (GL20.glIsProgram(programId)) GL20.glDeleteProgram(programId);
}
}
// ================== 内部类MeshGLResources ==================
private static class MeshGLResources {
int vao = 0;
int vbo = 0;
int ebo = 0;
int vertexCount = 0;
boolean initialized = false;
void dispose() {
if (ebo != 0) { GL15.glDeleteBuffers(ebo); ebo = 0; }
if (vbo != 0) { GL15.glDeleteBuffers(vbo); vbo = 0; }
if (vao != 0) { GL30.glDeleteVertexArrays(vao); vao = 0; }
initialized = false;
}
}
// ================== 着色器源 ==================
private static final String VERTEX_SHADER_SRC =
"""
#version 330 core
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 vTexCoord;
out vec2 vWorldPos;
uniform mat3 uModelMatrix;
uniform mat3 uViewMatrix;
uniform mat3 uProjectionMatrix;
void main() {
vec3 p = uProjectionMatrix * uViewMatrix * uModelMatrix * vec3(aPosition, 1.0);
gl_Position = vec4(p.xy, 0.0, 1.0);
vTexCoord = aTexCoord;
vWorldPos = (uModelMatrix * vec3(aPosition, 1.0)).xy;
}
""";
private static final String FRAGMENT_SHADER_SRC =
"""
#version 330 core
in vec2 vTexCoord;
in vec2 vWorldPos;
out vec4 FragColor;
uniform sampler2D uTexture;
uniform vec4 uColor;
uniform float uOpacity;
uniform int uBlendMode;
uniform int uDebugMode;
#define MAX_LIGHTS 8
uniform vec2 uLightsPos[MAX_LIGHTS];
uniform vec3 uLightsColor[MAX_LIGHTS];
uniform float uLightsIntensity[MAX_LIGHTS];
uniform int uLightsIsAmbient[MAX_LIGHTS];
uniform int uLightCount;
// 辉光相关
uniform int uLightsIsGlow[MAX_LIGHTS];
uniform vec2 uLightsGlowDir[MAX_LIGHTS];
uniform float uLightsGlowIntensity[MAX_LIGHTS];
uniform float uLightsGlowRadius[MAX_LIGHTS];
uniform float uLightsGlowAmount[MAX_LIGHTS];
// 常用衰减系数(可在 shader 内微调)
const float ATT_CONST = 1.0;
const float ATT_LINEAR = 0.09;
const float ATT_QUAD = 0.032;
// 简单 Reinhard tone mapping避免过曝
vec3 toneMap(vec3 color) {
return color / (color + vec3(1.0));
}
void main() {
// 先采样纹理
vec4 tex = texture(uTexture, vTexCoord);
float alpha = tex.a * uOpacity;
if (alpha <= 0.001) discard;
// 基础颜色(纹理 * 部件颜色)
vec3 baseColor = tex.rgb * uColor.rgb;
// 如果没有光源,仅返回基础颜色(节约性能)
if (uLightCount == 0) {
vec3 outCol = clamp(baseColor, 0.0, 1.0);
if (uBlendMode == 1) outCol = tex.rgb + uColor.rgb;
else if (uBlendMode == 2) outCol = tex.rgb * uColor.rgb;
else if (uBlendMode == 3) outCol = 1.0 - (1.0 - tex.rgb) * (1.0 - uColor.rgb);
FragColor = vec4(outCol, alpha);
return;
}
// 环境光基线
vec3 ambientBase = vec3(0.06);
vec3 lighting = vec3(0.0);
vec3 glowAccum = vec3(0.0);
vec3 specularAccum = vec3(0.0);
// 累积显式标记为环境光的光源
for (int i = 0; i < uLightCount; ++i) {
if (uLightsIsAmbient[i] == 1) {
lighting += uLightsColor[i] * uLightsIntensity[i];
}
}
lighting += ambientBase;
// 对每个非环境光源计算物理式衰减 + 漫反射 + 简单高光 + 辉光(若启用)
for (int i = 0; i < uLightCount; ++i) {
if (uLightsIsAmbient[i] == 1) continue;
vec2 toLight2 = uLightsPos[i] - vWorldPos;
float dist = length(toLight2);
// 物理风格衰减
float attenuation = ATT_CONST / (ATT_CONST + ATT_LINEAR * dist + ATT_QUAD * dist * dist);
float radiance = uLightsIntensity[i] * attenuation;
// 漫反射(在二维中基于距离模拟衰减的明暗)
// 使用更平滑的距离曲线max(0, 1 - (dist / (radiusApprox)))
float radiusApprox = max(1.0, 1000.0 * attenuation); // 通过衰减估算影响半径
float diffuseFactor = clamp(1.0 - (dist / (radiusApprox + 0.0001)), 0.0, 1.0);
vec3 diff = uLightsColor[i] * radiance * diffuseFactor;
lighting += diff;
// 简单高光(在 2D 中模拟亮点)
vec3 viewDir = vec3(0.0, 0.0, 1.0);
vec3 lightDir3 = normalize(vec3(toLight2, 0.0));
vec3 normal = vec3(0.0, 0.0, 1.0);
vec3 reflectDir = reflect(-lightDir3, normal);
float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
float specIntensity = 0.25;
specularAccum += uLightsColor[i] * radiance * specFactor * specIntensity;
// 若启用了辉光glow使用高斯风格衰减并支持方向性辉光
if (uLightsIsGlow[i] == 1) {
float glowRadius = max(0.0001, uLightsGlowRadius[i]);
float gdist = dist;
// 高斯分布exp(-(d^2) / (2 * sigma^2))
float sigma = glowRadius * 0.5; // sigma = radius * 0.5(经验值)
float gauss = exp(- (gdist * gdist) / (2.0 * sigma * sigma));
// 方向性因子:如果给出方向,使用方向与片元向量点积来增强朝向一侧的辉光
float dirFactor = 1.0;
vec2 glowDir = uLightsGlowDir[i];
if (length(glowDir) > 0.0001) {
vec2 ndir = normalize(glowDir);
vec2 toFrag = normalize(vWorldPos - uLightsPos[i]);
dirFactor = max(dot(ndir, toFrag), 0.0); // 只在方向半球贡献
}
float gIntensity = uLightsGlowIntensity[i];
float gAmount = uLightsGlowAmount[i];
vec3 glow = uLightsColor[i] * gauss * gIntensity * gAmount * dirFactor;
glowAccum += glow;
}
}
// 合并直接光照与高光后进行简单的色调映射(避免过曝)
vec3 totalLighting = lighting + specularAccum;
// 防止数值过大,进行 Reinhard tone mapping
vec3 litMapped = toneMap(totalLighting);
vec3 finalColor = baseColor * litMapped;
// 将辉光作为屏幕加色(加法混合),然后再做一次 tone map 以稳定输出
finalColor += glowAccum;
finalColor = toneMap(finalColor);
// 支持简单的 blend 模式(保留已有行为)
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);
}
""";
// ================== 初始化 / 清理 ==================
public static synchronized void initialize() {
if (initialized) return;
logger.info("Initializing ModelRender...");
// 需要在外部创建 OpenGL 上下文并调用 GL.createCapabilities()
logGLInfo();
// 初始 GL 状态
setupGLState();
// 创建默认 shader
try {
compileDefaultShader();
} catch (RuntimeException ex) {
logger.error("Failed to compile default shader: {}", ex.getMessage());
throw ex;
}
// 创建默认纹理
createDefaultTexture();
// 初始化视口
GL11.glViewport(0, 0, viewportWidth, viewportHeight);
initialized = true;
logger.info("ModelRender initialized successfully");
}
private static void logGLInfo() {
logger.info("OpenGL Vendor: {}", GL11.glGetString(GL11.GL_VENDOR));
logger.info("OpenGL Renderer: {}", GL11.glGetString(GL11.GL_RENDERER));
logger.info("OpenGL Version: {}", GL11.glGetString(GL11.GL_VERSION));
logger.info("GLSL Version: {}", GL20.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION));
}
private static void uploadLightsToShader(ShaderProgram sp, Model2D model) {
List<com.chuangzhou.vivid2D.render.model.util.LightSource> lights = model.getLights();
int idx = 0;
// 只上传已启用的光源,最多 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;
// 基础属性
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);
// 辉光相关(如果没有被设置也安全地上传默认值)
setUniformIntInternal(sp, "uLightsIsGlow[" + idx + "]", l.isGlow() ? 1 : 0);
setUniformVec2Internal(sp, "uLightsGlowDir[" + idx + "]", l.getGlowDirection() != null ? l.getGlowDirection() : new org.joml.Vector2f(0f, 0f));
setUniformFloatInternal(sp, "uLightsGlowIntensity[" + idx + "]", l.getGlowIntensity());
setUniformFloatInternal(sp, "uLightsGlowRadius[" + idx + "]", l.getGlowRadius());
setUniformFloatInternal(sp, "uLightsGlowAmount[" + idx + "]", l.getGlowAmount());
idx++;
}
// 上传实际有效光源数量
setUniformIntInternal(sp, "uLightCount", idx);
// 禁用剩余槽位(确保 shader 中不会读取到垃圾值)
for (int i = idx; i < 8; i++) {
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f));
setUniformVec2Internal(sp, "uLightsPos[" + i + "]", new org.joml.Vector2f(0f, 0f));
// 关闭辉光槽
setUniformIntInternal(sp, "uLightsIsGlow[" + i + "]", 0);
setUniformVec2Internal(sp, "uLightsGlowDir[" + i + "]", new org.joml.Vector2f(0f, 0f));
setUniformFloatInternal(sp, "uLightsGlowIntensity[" + i + "]", 0f);
setUniformFloatInternal(sp, "uLightsGlowRadius[" + i + "]", 0f);
setUniformFloatInternal(sp, "uLightsGlowAmount[" + i + "]", 0f);
}
}
private static void setupGLState() {
GL11.glClearColor(CLEAR_COLOR.x, CLEAR_COLOR.y, CLEAR_COLOR.z, CLEAR_COLOR.w);
if (enableBlending) {
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
} else {
GL11.glDisable(GL11.GL_BLEND);
}
if (enableDepthTest) {
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glDepthFunc(GL11.GL_LEQUAL);
} else {
GL11.glDisable(GL11.GL_DEPTH_TEST);
}
GL11.glDisable(GL11.GL_CULL_FACE);
checkGLError("setupGLState");
}
private static void compileDefaultShader() {
int vs = compileShader(GL20.GL_VERTEX_SHADER, VERTEX_SHADER_SRC);
int fs = compileShader(GL20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SRC);
int prog = linkProgram(vs, fs);
ShaderProgram sp = new ShaderProgram(prog);
shaderMap.put("default", sp);
defaultProgram = sp;
sp.use();
setUniformIntInternal(sp, "uTexture", 0);
setUniformFloatInternal(sp, "uOpacity", 1.0f);
setUniformVec4Internal(sp, "uColor", new Vector4f(1,1,1,1));
setUniformIntInternal(sp, "uBlendMode", 0);
setUniformIntInternal(sp, "uDebugMode", 0);
setUniformIntInternal(sp, "uLightCount", 0); // 默认没有光源
sp.stop();
}
private static int compileShader(int type, String src) {
int shader = GL20.glCreateShader(type);
GL20.glShaderSource(shader, src);
GL20.glCompileShader(shader);
int status = GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS);
if (status == GL11.GL_FALSE) {
String log = GL20.glGetShaderInfoLog(shader);
GL20.glDeleteShader(shader);
throw new RuntimeException("Shader compilation failed: " + log);
}
return shader;
}
private static int linkProgram(int vs, int fs) {
int prog = GL20.glCreateProgram();
GL20.glAttachShader(prog, vs);
GL20.glAttachShader(prog, fs);
GL20.glLinkProgram(prog);
int status = GL20.glGetProgrami(prog, GL20.GL_LINK_STATUS);
if (status == GL11.GL_FALSE) {
String log = GL20.glGetProgramInfoLog(prog);
GL20.glDeleteProgram(prog);
throw new RuntimeException("Program link failed: " + log);
}
// shaders can be deleted after linking
GL20.glDetachShader(prog, vs);
GL20.glDetachShader(prog, fs);
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
return prog;
}
private static void createDefaultTexture() {
// 使用 GL11.glGenTextures() 获取单个 id更直观避免 IntBuffer 问题)
defaultTextureId = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, defaultTextureId);
ByteBuffer white = MemoryUtil.memAlloc(4);
white.put((byte)255).put((byte)255).put((byte)255).put((byte)255).flip();
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 1, 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, white);
MemoryUtil.memFree(white);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
checkGLError("createDefaultTexture");
}
public static synchronized void cleanup() {
if (!initialized) return;
logger.info("Cleaning up ModelRender...");
// mesh resources
for (MeshGLResources r : meshResources.values()) r.dispose();
meshResources.clear();
// shaders
for (ShaderProgram sp : shaderMap.values()) sp.delete();
shaderMap.clear();
defaultProgram = null;
// textures
if (defaultTextureId != 0) {
GL11.glDeleteTextures(defaultTextureId);
defaultTextureId = 0;
}
initialized = false;
logger.info("ModelRender cleaned up");
}
// ================== 渲染流程 (已修改) ==================
public static void render(float deltaTime, Model2D model) {
if (!initialized) throw new IllegalStateException("ModelRender not initialized");
if (model == null) return;
// 物理系统更新
PhysicsSystem physics = model.getPhysics();
if (physics != null && physics.isEnabled()) {
physics.update(deltaTime, model);
}
model.update(deltaTime);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | (enableDepthTest ? GL11.GL_DEPTH_BUFFER_BIT : 0));
defaultProgram.use();
// 设置投影与视图
Matrix3f proj = buildOrthoProjection(viewportWidth, viewportHeight);
setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj);
setUniformMatrix3(defaultProgram, "uViewMatrix", new Matrix3f().identity());
// 添加光源数据上传
uploadLightsToShader(defaultProgram, model);
renderLightPositions(model);
// 递归渲染所有根部件
Matrix3f identity = new Matrix3f().identity();
for (ModelPart p : model.getParts()) {
if (p.getParent() != null) continue;
renderPartRecursive(p, identity);
}
if (renderColliders && physics != null) {
renderPhysicsColliders(physics);
}
defaultProgram.stop();
checkGLError("render");
}
private static void renderLightPositions(Model2D model) {
if (!renderLightPositions) return;
GL11.glPointSize(10.0f);
setUniformIntInternal(defaultProgram, "uDebugMode", 1);
for (LightSource light : model.getLights()) {
if (!light.isEnabled()) continue;
// 绘制光源位置
BufferBuilder bb =
new BufferBuilder(1 * 4);
bb.begin(GL11.GL_POINTS, 1);
bb.vertex(light.getPosition().x, light.getPosition().y, 0.5f, 0.5f);
bb.end();
}
setUniformIntInternal(defaultProgram, "uDebugMode", 0);
GL11.glPointSize(1.0f);
}
/**
* 关键修改点在渲染前确保更新 part worldTransform
* 然后直接使用 part.getWorldTransform() 作为 uModelMatrix 传入 shader
*/
private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) {
// 确保 part 的 local/world 矩阵被计算(会更新 transformDirty
part.updateWorldTransform(parentMat, false);
// 直接使用已经计算好的 worldTransform
Matrix3f world = part.getWorldTransform();
// 先设置部件相关的 uniformopacity / blendMode / color 等)
setPartUniforms(defaultProgram, part);
// 把 world 矩阵传给 shaderuModelMatrix
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
// 绘制本节点的所有 mesh将 world 传入 renderMesh
for (Mesh2D mesh : part.getMeshes()) {
renderMesh(mesh, world);
}
// 递归渲染子节点,继续传入当前 world 作为子节点的 parent
for (ModelPart child : part.getChildren()) {
renderPartRecursive(child, world);
}
}
private static void renderMesh(Mesh2D mesh, Matrix3f modelMatrix) {
if (!mesh.isVisible()) return;
// 如果 mesh 已经被烘焙到世界坐标,则传 identity 矩阵给 shader防止重复变换
Matrix3f matToUse = mesh.isBakedToWorld() ? new Matrix3f().identity() : modelMatrix;
// 设置纹理相关的uniform
if (mesh.getTexture() != null) {
mesh.getTexture().bind(0); // 绑定到纹理单元0
setUniformIntInternal(defaultProgram, "uTexture", 0);
} else {
// 使用默认白色纹理
GL11.glBindTexture(GL11.GL_TEXTURE_2D, defaultTextureId);
setUniformIntInternal(defaultProgram, "uTexture", 0);
}
// 将模型矩阵设置为当前 mesh 使用的矩阵shader 内名为 uModelMatrix
setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse);
// 调用 Mesh2D 的 draw 方法,传入当前使用的着色器程序和变换矩阵
mesh.draw(defaultProgram.programId, matToUse);
checkGLError("renderMesh");
}
// ================== 渲染碰撞箱相关实现 ==================
private static void renderPhysicsColliders(PhysicsSystem physics) {
// 设置渲染状态
GL11.glLineWidth(colliderLineWidth);
// 绑定默认纹理shader 依赖 uTexture并设置颜色/opacity
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, defaultTextureId);
setUniformIntInternal(defaultProgram, "uTexture", 0);
setUniformVec4Internal(defaultProgram, "uColor", colliderColor);
setUniformFloatInternal(defaultProgram, "uOpacity", 1.0f);
setUniformIntInternal(defaultProgram, "uBlendMode", 0);
setUniformIntInternal(defaultProgram, "uDebugMode", 0);
// 使用单位矩阵作为 model碰撞体顶点按世界坐标提供
setUniformMatrix3(defaultProgram, "uModelMatrix", new Matrix3f().identity());
for (PhysicsSystem.PhysicsCollider collider : physics.getColliders()) {
if (!collider.isEnabled()) continue;
if (collider instanceof PhysicsSystem.CircleCollider) {
PhysicsSystem.CircleCollider c = (PhysicsSystem.CircleCollider) collider;
drawCircleColliderWire(c.getCenter(), c.getRadius());
} else if (collider instanceof PhysicsSystem.RectangleCollider) {
PhysicsSystem.RectangleCollider r = (PhysicsSystem.RectangleCollider) collider;
drawRectangleColliderWire(r.getCenter(), r.getWidth(), r.getHeight());
} else {
// 未知类型:尝试调用 collidesWith 以获取位置(跳过)
}
}
// 恢复默认线宽
GL11.glLineWidth(1.0f);
}
/**
* 绘制圆形碰撞框线框
* 使用临时 VAO/VBO每帧创建并删除简单实现
*/
private static void drawCircleColliderWire(Vector2f center, float radius) {
int segments = Math.max(8, CIRCLE_SEGMENTS);
BufferBuilder bb = new BufferBuilder(segments * 4);
bb.begin(GL11.GL_LINE_LOOP, segments);
for (int i = 0; i < segments; i++) {
double ang = 2.0 * Math.PI * i / segments;
float x = center.x + radius * (float) Math.cos(ang);
float y = center.y + radius * (float) Math.sin(ang);
// 给常量 texcoord
bb.vertex(x, y, 0.5f, 0.5f);
}
bb.end();
}
/**
* 绘制矩形碰撞框线框
*/
private static void drawRectangleColliderWire(Vector2f center, float width, float height) {
float halfW = width / 2.0f;
float halfH = height / 2.0f;
BufferBuilder bb = new BufferBuilder(4 * 4);
bb.begin(GL11.GL_LINE_LOOP, 4);
bb.vertex(center.x - halfW, center.y - halfH, 0.5f, 0.5f);
bb.vertex(center.x + halfW, center.y - halfH, 0.5f, 0.5f);
bb.vertex(center.x + halfW, center.y + halfH, 0.5f, 0.5f);
bb.vertex(center.x - halfW, center.y + halfH, 0.5f, 0.5f);
bb.end();
}
/**
* float[] (x,y,u,v interleaved) 绘制 GL_LINE_LOOP
*/
private static void drawLineLoopFromFloatArray(float[] interleavedXYUV, int vertexCount) {
// 创建 VAO/VBO
int vao = GL30.glGenVertexArrays();
int vbo = GL15.glGenBuffers();
GL30.glBindVertexArray(vao);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
// 上传数据
FloatBuffer fb = MemoryUtil.memAllocFloat(interleavedXYUV.length);
fb.put(interleavedXYUV).flip();
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, fb, GL15.GL_DYNAMIC_DRAW);
MemoryUtil.memFree(fb);
// attrib 0 -> aPosition (vec2)
GL20.glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 4 * Float.BYTES, 0);
// attrib 1 -> aTexCoord (vec2) (提供常量 texcoord
GL20.glEnableVertexAttribArray(1);
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, 4 * Float.BYTES, 2 * Float.BYTES);
// 绘制线环
GL11.glDrawArrays(GL11.GL_LINE_LOOP, 0, vertexCount);
// 清理
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GL30.glDeleteVertexArrays(vao);
checkGLError("drawLineLoopFromFloatArray");
}
// ================== uniform 设置辅助(内部使用,确保 program 已绑定) ==================
private static void setUniformIntInternal(ShaderProgram sp, String name, int value) {
int loc = sp.getUniformLocation(name);
if (loc != -1) GL20.glUniform1i(loc, value);
}
private static void setUniformVec3Internal(ShaderProgram sp, String name, org.joml.Vector3f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) {
GL20.glUniform3f(loc, vec.x, vec.y, vec.z);
}
}
private static void setUniformVec2Internal(ShaderProgram sp, String name, org.joml.Vector2f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) {
GL20.glUniform2f(loc, vec.x, vec.y);
}
}
private static void setUniformFloatInternal(ShaderProgram sp, String name, float value) {
int loc = sp.getUniformLocation(name);
if (loc != -1) GL20.glUniform1f(loc, value);
}
private static void setUniformVec4Internal(ShaderProgram sp, String name, Vector4f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) GL20.glUniform4f(loc, vec.x, vec.y, vec.z, vec.w);
}
private static void setUniformMatrix3(ShaderProgram sp, String name, Matrix3f m) {
int loc = sp.getUniformLocation(name);
if (loc == -1) return;
FloatBuffer fb = MemoryUtil.memAllocFloat(9);
try {
m.get(fb);
GL20.glUniformMatrix3fv(loc, false, fb);
} finally {
MemoryUtil.memFree(fb);
}
}
// 外部可用的统一设置(会自动切换到默认程序)
private static void setUniformInt(String name, int value) {
defaultProgram.use();
setUniformIntInternal(defaultProgram, name, value);
defaultProgram.stop();
}
private static void setUniformFloat(String name, float value) {
defaultProgram.use();
setUniformFloatInternal(defaultProgram, name, value);
defaultProgram.stop();
}
private static void setUniformVec4(String name, Vector4f v) {
defaultProgram.use();
setUniformVec4Internal(defaultProgram, name, v);
defaultProgram.stop();
}
// ================== 部件属性 ==================
private static void setPartUniforms(ShaderProgram sp, ModelPart part) {
setUniformFloatInternal(sp, "uOpacity", part.getOpacity());
int blend = 0;
ModelPart.BlendMode bm = part.getBlendMode();
if (bm != null) {
switch (bm) {
case ADDITIVE: blend = 1; break;
case MULTIPLY: blend = 2; break;
case SCREEN: blend = 3; break;
case NORMAL: default: blend = 0;
}
} else {
blend = 0;
}
setUniformIntInternal(sp, "uBlendMode", blend);
// 这里保留为白色,若需要部件 tint 请替换为 part 的 color 属性
setUniformVec4Internal(sp, "uColor", new Vector4f(1,1,1,1));
}
// ================== 工具 ==================
private static Matrix3f buildOrthoProjection(int width, int height) {
Matrix3f m = new Matrix3f();
m.set(
2.0f / width, 0.0f, -1.0f,
0.0f, -2.0f / height, 1.0f,
0.0f, 0.0f, 1.0f
);
return m;
}
public static void setViewport(int width, int height) {
viewportWidth = Math.max(1, width);
viewportHeight = Math.max(1, height);
GL11.glViewport(0, 0, viewportWidth, viewportHeight);
}
public static void setClearColor(float r, float g, float b, float a) {
GL11.glClearColor(r,g,b,a);
}
private static void checkGLError(String op) {
int e = GL11.glGetError();
if (e != GL11.GL_NO_ERROR) {
//logger.error("OpenGL error during {}: {}", op, getGLErrorString(e));
}
}
private static String getGLErrorString(int err) {
switch (err) {
case GL11.GL_INVALID_ENUM: return "GL_INVALID_ENUM";
case GL11.GL_INVALID_VALUE: return "GL_INVALID_VALUE";
case GL11.GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
case GL11.GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
default: return "Unknown(0x" + Integer.toHexString(err) + ")";
}
}
// ================== 辅助:外部获取状态 ==================
public static boolean isInitialized() { return initialized; }
public static int getLoadedMeshCount() { return meshResources.size(); }
}