前言
本章主要介绍Blaze3D,并穿插一部分Arc3D的知识
2.1 老版本渲染:立即模式
本节目标:了解 Minecraft 早期版本的渲染方式,理解为什么需要迁移到现代渲染
2.1.1 什么是立即模式?
立即模式(Immediate Mode) 是 OpenGL 1.x/2.x 时代的渲染方式。
c
// 立即模式渲染一个三角形
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); // 红色
glVertex3f(-0.5f, -0.5f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f); // 绿色
glVertex3f(0.5f, -0.5f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f); // 蓝色
glVertex3f(0.0f, 0.5f, 0.0f);
glEnd();
特点
| 特点 | 说明 |
|---|---|
| 简单直观 | 代码即所见,容易理解 |
| 每帧重传数据 | 顶点数据每帧都从 CPU 传到 GPU |
| 固定管线 | 使用预定义的光照、雾效等 |
| 状态机驱动 | glColor、glNormal 等设置当前状态 |
为什么叫"立即模式"?
因为每个 glVertex 调用都立即把顶点数据发送给 GPU,没有缓存。
2.1.2 Minecraft 早期的 Tessellator
Minecraft 早期版本(1.12 及以前)使用 Tessellator 类封装立即模式:
java
// Minecraft 1.12 风格的渲染代码
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
buffer.pos(x1, y1, z1).tex(u1, v1).color(r, g, b, a).endVertex();
buffer.pos(x2, y2, z2).tex(u2, v2).color(r, g, b, a).endVertex();
buffer.pos(x3, y3, z3).tex(u3, v3).color(r, g, b, a).endVertex();
buffer.pos(x4, y4, z4).tex(u4, v4).color(r, g, b, a).endVertex();
tessellator.draw();
虽然看起来像是缓冲模式,但底层仍然是:
- 在 CPU 端构建顶点数据
- 每次
draw()都上传到 GPU - 使用固定管线或简单着色器
2.1.3 固定管线 vs 可编程管线
固定管线(Fixed Function Pipeline)
plaintext
顶点数据 ──► 固定变换 ──► 固定光照 ──► 固定雾效 ──► 输出
│ │ │
│ │ │
不可修改 不可修改 不可修改
OpenGL 提供了一些预定义的功能:
glLight*- 光照glFog*- 雾效glTexEnv*- 纹理环境
你只能配置参数,不能改变算法。
可编程管线(Programmable Pipeline)
plaintext
顶点数据 ──► 顶点着色器 ──► 片段着色器 ──► 输出
│ │
│ │
你的代码 你的代码
你可以完全控制渲染算法。
2.1.4 GlStateManager 的诞生
为了在 Minecraft 中更好地管理 OpenGL 状态,Mojang 引入了 GlStateManager:
java
// 直接调用 OpenGL(容易出错)
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// 通过 GlStateManager(更安全)
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager 的作用:
- 状态缓存:避免重复设置相同状态
- 统一接口:封装 OpenGL 调用
- 调试支持:更容易追踪状态变化
2.1.5 立即模式的问题
性能问题
plaintext
每一帧:
CPU: 构建顶点 ──传输──► GPU: 渲染 ──传输──► CPU: 构建顶点 ──传输──► GPU: 渲染
[====] [==] [==] [==] [====] [==] [==]
大量时间浪费在 CPU-GPU 数据传输上!
驱动开销
每个 glVertex 调用都有驱动开销:
- 参数验证
- 状态检查
- 数据打包
渲染 10000 个三角形 = 30000 次 glVertex 调用 = 巨大开销
无法利用现代 GPU
- 无法使用 GPU 实例化(Instancing)
- 无法使用计算着色器
- 无法使用高级纹理格式
2.1.6 为什么要迁移?
| 老版本 | 新版本 |
|---|---|
| OpenGL 2.1 | OpenGL 3.2+ Core |
| 立即模式 | 缓冲对象 |
| 固定管线 | 可编程着色器 |
| 每帧上传数据 | 数据常驻显存 |
| 单线程渲染 | 可多线程准备 |
Minecraft 1.17 开始要求 OpenGL 3.2,正式告别立即模式。
2.2 Blaze3D 架构概述
本节目标:理解 Minecraft 现代渲染系统 Blaze3D 的整体架构
2.2.1 什么是 Blaze3D?
Blaze3D 是 Minecraft 1.17+ 使用的渲染系统,是对老版本渲染代码的现代化重构。
Blaze3D 的主要改进:
┌─────────────────────────────────────────────────────────────┐
│ 老版本 → Blaze3D │
├─────────────────────────────────────────────────────────────┤
│ OpenGL 2.1 → OpenGL 3.2+ Core │
│ 立即模式 → 缓冲对象 (VBO/VAO) │
│ 固定管线 → 可编程着色器 │
│ GlStateManager → RenderSystem │
│ Tessellator (老版) → BufferBuilder (新版) │
│ 无统一渲染类型 → RenderType 系统 │
└─────────────────────────────────────────────────────────────┘
名称由来
"Blaze3D" 这个名字来自 Minecraft 中的烈焰人(Blaze),暗示这是一个"火热"的新渲染引擎。
2.2.2 Blaze3D 核心组件
┌─────────────────────────────────────────────────────────────────────┐
│ Blaze3D 架构图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 应用层 (Game Code) │ │
│ │ WorldRenderer, EntityRenderer, BlockRenderer, GUI... │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────────┐ │
│ │ RenderType 系统 │ │
│ │ 定义渲染状态组合:着色器 + 纹理 + 混合 + 深度测试... │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────────┐ │
│ │ 顶点构建系统 │ │
│ │ BufferBuilder, Tesselator, VertexFormat │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────────┐ │
│ │ 渲染状态管理 │ │
│ │ RenderSystem, ShaderInstance, RenderTarget │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────────┐ │
│ │ 底层 OpenGL 封装 │ │
│ │ GlStateManager, LWJGL 3 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
核心类一览
| 类 | 包路径 | 职责 |
|---|---|---|
RenderSystem |
com.mojang.blaze3d.systems |
渲染状态管理 |
GlStateManager |
com.mojang.blaze3d.platform |
底层 GL 封装 |
RenderType |
net.minecraft.client.renderer |
渲染类型定义 |
RenderTarget |
com.mojang.blaze3d.pipeline |
帧缓冲封装 |
ShaderInstance |
net.minecraft.client.renderer |
着色器实例 |
BufferBuilder |
com.mojang.blaze3d.vertex |
顶点数据构建 |
Tesselator |
com.mojang.blaze3d.vertex |
顶点构建器单例 |
VertexFormat |
com.mojang.blaze3d.vertex |
顶点格式定义 |
PoseStack |
com.mojang.blaze3d.vertex |
矩阵栈 |
BufferUploader |
com.mojang.blaze3d.vertex |
缓冲上传绘制 |
2.2.3 与 Arc3D 的对比
Arc3D 是一个更现代的图形引擎,我们可以通过对比来理解 Blaze3D 的设计:
| 特性 | Blaze3D | Arc3D |
|---|---|---|
| 最低 OpenGL | 3.2 Core | 3.3 Core / ES 3.0 |
| DSA 支持 | 不使用 | 支持 (4.5+) |
| Vulkan 支持 | 无 | 有 |
| 着色器编译 | 运行时 GLSL | 自研编译器,支持 SPIR-V |
| 资源管理 | 简单引用 | 引用计数 (RefCnt) |
| 能力检测 | 基础 | 详细 (GLCaps) |
| 线程模型 | 单线程渲染 | 支持异步提交 |
Arc3D 的现代特性示例
java
// Arc3D 的 DSA 支持检测
public abstract class GLCaps extends Caps {
boolean mDSASupport; // Direct State Access
boolean mBufferStorageSupport; // 持久映射缓冲
boolean mCopyImageSupport; // glCopyImageSubData
boolean mSPIRVSupport; // SPIR-V 着色器
// ...
}
// Arc3D 的线程安全渲染调用
public void executeRenderCall(Consumer<GLDevice> renderCall) {
if (isOnExecutingThread()) {
renderCall.accept(this); // 直接执行
} else {
recordRenderCall(renderCall); // 延迟到渲染线程
}
}
Blaze3D 相比之下更保守,主要是为了兼容更多硬件。
2.2.4 渲染流程概览
一帧的渲染流程:
plaintext
┌─────────────────────────────────────────────────────────────────────┐
│ 一帧渲染流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 帧开始 │
│ ├─ 清除帧缓冲 │
│ └─ 设置相机和投影矩阵 │
│ │
│ 2. 世界渲染 │
│ ├─ 天空盒 │
│ ├─ 地形(分层:solid → cutout → translucent) │
│ ├─ 实体 │
│ ├─ 方块实体 │
│ ├─ 粒子 │
│ └─ 天气效果 │
│ │
│ 3. 后处理 │
│ ├─ 着色器效果(如果启用) │
│ └─ 屏幕叠加 │
│ │
│ 4. GUI 渲染 │
│ ├─ HUD │
│ ├─ 聊天 │
│ └─ 菜单界面 │
│ │
│ 5. 帧结束 │
│ └─ 交换缓冲区 │
│ │
└─────────────────────────────────────────────────────────────────────┘
渲染顺序的重要性
plaintext
不透明物体(任意顺序,深度测试)
↓
半透明物体(从远到近,混合)
↓
GUI(正交投影,最后渲染)
2.2.5 关键设计决策
1. 核心配置文件 (Core Profile)
Blaze3D 使用 OpenGL 3.2 Core Profile,这意味着:
- 移除了所有废弃功能(立即模式、固定管线)
- 必须使用着色器
- 必须使用 VAO/VBO
java
// GLFW 窗口创建时指定
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
2. 着色器驱动
所有渲染都通过着色器:
plaintext
assets/minecraft/shaders/core/
├── position.json # 纯位置
├── position_color.json # 位置 + 颜色
├── position_tex.json # 位置 + 纹理
├── particle.json # 粒子
├── block.json # 方块
├── entity.json # 实体
└── ...
3. RenderType 抽象
RenderType 封装了一组渲染状态,简化了渲染代码:
java
// 不需要手动设置每个状态
RenderType type = RenderType.translucent();
type.setupRenderState(); // 自动设置所有状态
// 渲染...
type.clearRenderState(); // 自动清理
4. 延迟状态应用
RenderSystem 的状态设置是延迟的,在实际绘制时才应用:
java
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, texture);
// 状态被记录,但还没应用
BufferUploader.drawWithShader(buffer);
// 这里才真正应用状态并绘制
2.2.6 Blaze3D 的局限性
尽管 Blaze3D 是一次重大升级,但它仍有一些局限:
| 局限 | 说明 | Arc3D 的解决方案 |
|---|---|---|
| 无 DSA | 每次操作都要绑定对象 | 支持 DSA,减少状态切换 |
| 单线程 | 渲染必须在主线程 | 支持异步命令提交 |
| 无 Vulkan | 只支持 OpenGL | 多后端支持 |
| 简单缓存 | 资源管理较简单 | 完整的资源缓存系统 |
| 无能力查询 | 假设所有功能可用 | 详细的能力检测 |
为什么 Minecraft 不使用更现代的方案?
- 兼容性:需要支持大量老旧硬件
- 稳定性:游戏已经很成熟,大改风险高
- Mod 生态:需要保持 API 稳定
小结
| 概念 | 说明 |
|---|---|
| Blaze3D | MC 1.17+ 的现代渲染系统 |
| Core Profile | OpenGL 3.2+ 核心配置文件 |
| RenderType | 渲染状态组合的抽象 |
| RenderSystem | 渲染状态管理器 |
| 着色器驱动 | 所有渲染都通过着色器 |
2.3 RenderSystem 详解
本节目标:掌握 RenderSystem 的所有 API,理解其状态管理机制
2.3.1 RenderSystem 概述
RenderSystem 是 Blaze3D 的核心状态管理类,封装了所有渲染状态操作。
类路径: com.mojang.blaze3d.systems.RenderSystem
java
// RenderSystem 是一个静态工具类
public class RenderSystem {
// 所有方法都是静态的
public static void enableBlend() { ... }
public static void setShader(Supplier<ShaderInstance> shader) { ... }
// ...
}
2.3.2 线程安全
RenderSystem 的操作必须在渲染线程执行:
java
// 检查是否在渲染线程
if (RenderSystem.isOnRenderThread()) {
// 可以直接调用
RenderSystem.enableBlend();
} else {
// 需要延迟到渲染线程
RenderSystem.recordRenderCall(() -> {
RenderSystem.enableBlend();
});
}
// 断言在渲染线程(调试用)
RenderSystem.assertOnRenderThread();
与 Arc3D 对比
Arc3D 有类似的机制,但更灵活:
java
// Arc3D 的 GLDevice
public void executeRenderCall(Consumer<GLDevice> renderCall) {
if (isOnExecutingThread()) {
renderCall.accept(this); // 直接执行
} else {
recordRenderCall(renderCall); // 加入队列
}
}
// 批量执行队列中的调用
public void flushRenderCalls() {
Consumer<GLDevice> r;
while ((r = mRenderCalls.poll()) != null) {
r.accept(this);
}
}
2.3.3 着色器相关 API
设置着色器
java
// 设置当前着色器(使用 Supplier 延迟获取)
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShader(GameRenderer::getParticleShader);
RenderSystem.setShader(() -> myCustomShader);
// 常用的内置着色器获取方法
GameRenderer.getPositionShader() // 纯位置
GameRenderer.getPositionColorShader() // 位置 + 颜色
GameRenderer.getPositionTexShader() // 位置 + 纹理
GameRenderer.getPositionTexColorShader() // 位置 + 纹理 + 颜色
GameRenderer.getParticleShader() // 粒子
GameRenderer.getBlockShader() // 方块
GameRenderer.getNewEntityShader() // 实体
设置着色器纹理
java
// 使用 ResourceLocation
RenderSystem.setShaderTexture(0, new ResourceLocation("minecraft", "textures/block/dirt.png"));
// 使用纹理 ID
RenderSystem.setShaderTexture(0, textureId);
// 多个纹理单元
RenderSystem.setShaderTexture(0, diffuseTexture); // 主纹理
RenderSystem.setShaderTexture(1, overlayTexture); // 覆盖层
RenderSystem.setShaderTexture(2, lightmapTexture); // 光照贴图
设置着色器颜色
java
// 设置颜色调制器(乘以顶点颜色)
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); // 白色,无调制
RenderSystem.setShaderColor(1.0f, 0.0f, 0.0f, 1.0f); // 红色调制
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 0.5f); // 半透明
设置雾效
java
RenderSystem.setShaderFogStart(10.0f); // 雾开始距离
RenderSystem.setShaderFogEnd(100.0f); // 雾结束距离
RenderSystem.setShaderFogColor(0.5f, 0.5f, 0.5f, 1.0f); // 雾颜色
RenderSystem.setShaderFogShape(FogShape.SPHERE); // 雾形状
2.3.4 混合模式 API
启用/禁用混合
java
RenderSystem.enableBlend(); // 启用混合
RenderSystem.disableBlend(); // 禁用混合
设置混合函数
java
// 简单混合函数
RenderSystem.blendFunc(
GlStateManager.SourceFactor.SRC_ALPHA,
GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA
);
// 分离混合函数(RGB 和 Alpha 分开)
RenderSystem.blendFuncSeparate(
GlStateManager.SourceFactor.SRC_ALPHA, // srcRGB
GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, // dstRGB
GlStateManager.SourceFactor.ONE, // srcAlpha
GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA // dstAlpha
);
// 默认混合函数(标准透明)
RenderSystem.defaultBlendFunc();
// 等同于 blendFuncSeparate(SRC_ALPHA, ONE_MINUS_SRC_ALPHA, ONE, ONE_MINUS_SRC_ALPHA)
常用混合模式配方
java
// 标准透明
RenderSystem.defaultBlendFunc();
// 加法混合(发光效果)
RenderSystem.blendFunc(
GlStateManager.SourceFactor.SRC_ALPHA,
GlStateManager.DestFactor.ONE
);
// 预乘 Alpha
RenderSystem.blendFunc(
GlStateManager.SourceFactor.ONE,
GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA
);
// 乘法混合(阴影)
RenderSystem.blendFunc(
GlStateManager.SourceFactor.DST_COLOR,
GlStateManager.DestFactor.ZERO
);
2.3.5 深度测试 API
java
// 启用/禁用深度测试
RenderSystem.enableDepthTest();
RenderSystem.disableDepthTest();
// 设置深度函数
RenderSystem.depthFunc(GL11.GL_LEQUAL); // 小于等于(默认)
RenderSystem.depthFunc(GL11.GL_LESS); // 小于
RenderSystem.depthFunc(GL11.GL_ALWAYS); // 总是通过
// 深度写入
RenderSystem.depthMask(true); // 允许写入深度
RenderSystem.depthMask(false); // 禁止写入深度(半透明物体常用)
深度测试使用场景
java
// 渲染不透明物体
RenderSystem.enableDepthTest();
RenderSystem.depthMask(true);
renderOpaqueObjects();
// 渲染半透明物体
RenderSystem.enableDepthTest(); // 仍然测试
RenderSystem.depthMask(false); // 但不写入
renderTranslucentObjects();
// 渲染 GUI(不需要深度)
RenderSystem.disableDepthTest();
renderGUI();
2.3.6 面剔除 API
java
// 启用/禁用面剔除
RenderSystem.enableCull(); // 启用(剔除背面)
RenderSystem.disableCull(); // 禁用(双面渲染)
使用场景
java
// 渲染普通方块(单面)
RenderSystem.enableCull();
renderBlocks();
// 渲染树叶、草(双面)
RenderSystem.disableCull();
renderFoliage();
// 渲染粒子(双面)
RenderSystem.disableCull();
renderParticles();
2.3.7 多边形偏移 API
用于解决 Z-Fighting 问题:
java
// 启用/禁用
RenderSystem.enablePolygonOffset();
RenderSystem.disablePolygonOffset();
// 设置偏移值
RenderSystem.polygonOffset(-1.0f, -1.0f); // 向相机方向偏移
RenderSystem.polygonOffset(1.0f, 1.0f); // 远离相机偏移
2.3.8 颜色遮罩 API
控制哪些颜色通道可以写入:
java
// 允许所有通道写入
RenderSystem.colorMask(true, true, true, true);
// 只写入 RGB,不写入 Alpha
RenderSystem.colorMask(true, true, true, false);
// 不写入任何颜色(只写深度/模板)
RenderSystem.colorMask(false, false, false, false);
2.3.9 视口和裁剪 API
视口
java
// 设置视口
RenderSystem.viewport(0, 0, width, height);
裁剪测试
java
// 启用裁剪(只渲染指定矩形内的像素)
RenderSystem.enableScissor(x, y, width, height);
// 禁用裁剪
RenderSystem.disableScissor();
2.3.10 清除操作
java
// 设置清除颜色
RenderSystem.clearColor(0.0f, 0.0f, 0.0f, 1.0f); // 黑色
// 清除缓冲
RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT, Minecraft.ON_OSX);
RenderSystem.clear(GL11.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX);
RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX);
2.3.11 矩阵操作
RenderSystem 也管理一些矩阵:
java
// 获取/设置模型视图矩阵
Matrix4f modelView = RenderSystem.getModelViewMatrix();
RenderSystem.setModelViewMatrix(matrix);
// 获取/设置投影矩阵
Matrix4f projection = RenderSystem.getProjectionMatrix();
RenderSystem.setProjectionMatrix(matrix);
// 应用模型视图矩阵
RenderSystem.applyModelViewMatrix();
2.3.12 完整渲染示例
java
public void renderCustomEffect(PoseStack poseStack, float partialTick) {
// 1. 保存当前状态(通过 RenderType 或手动)
// 2. 设置渲染状态
RenderSystem.enableBlend();
RenderSystem.blendFunc(
GlStateManager.SourceFactor.SRC_ALPHA,
GlStateManager.DestFactor.ONE // 加法混合
);
RenderSystem.enableDepthTest();
RenderSystem.depthMask(false); // 不写入深度
RenderSystem.disableCull(); // 双面渲染
// 3. 设置着色器和纹理
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
RenderSystem.setShaderTexture(0, myTexture);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 0.8f);
// 4. 构建顶点数据
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
Matrix4f matrix = poseStack.last().pose();
buffer.vertex(matrix, x1, y1, z1).uv(u1, v1).color(r, g, b, a).endVertex();
buffer.vertex(matrix, x2, y2, z2).uv(u2, v2).color(r, g, b, a).endVertex();
buffer.vertex(matrix, x3, y3, z3).uv(u3, v3).color(r, g, b, a).endVertex();
buffer.vertex(matrix, x4, y4, z4).uv(u4, v4).color(r, g, b, a).endVertex();
// 5. 上传并绘制
BufferUploader.drawWithShader(tesselator.end());
// 6. 恢复状态
RenderSystem.depthMask(true);
RenderSystem.enableCull();
RenderSystem.defaultBlendFunc();
RenderSystem.disableBlend();
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
}
2.3.13 RenderSystem API 速查表
着色器
| 方法 | 用途 |
|---|---|
setShader(Supplier) |
设置着色器 |
setShaderTexture(unit, texture) |
设置纹理 |
setShaderColor(r, g, b, a) |
设置颜色调制 |
setShaderFogStart/End/Color/Shape |
设置雾效 |
混合
| 方法 | 用途 |
|---|---|
enableBlend() / disableBlend() |
启用/禁用混合 |
blendFunc(src, dst) |
设置混合函数 |
blendFuncSeparate(...) |
分离混合函数 |
defaultBlendFunc() |
默认混合 |
深度
| 方法 | 用途 |
|---|---|
enableDepthTest() / disableDepthTest() |
启用/禁用深度测试 |
depthFunc(func) |
设置深度函数 |
depthMask(flag) |
设置深度写入 |
其他
| 方法 | 用途 |
|---|---|
enableCull() / disableCull() |
面剔除 |
enablePolygonOffset() / polygonOffset(...) |
多边形偏移 |
colorMask(r, g, b, a) |
颜色写入遮罩 |
viewport(x, y, w, h) |
设置视口 |
enableScissor(...) / disableScissor() |
裁剪测试 |
clearColor(...) / clear(...) |
清除操作 |
线程
| 方法 | 用途 |
|---|---|
isOnRenderThread() |
检查是否在渲染线程 |
assertOnRenderThread() |
断言在渲染线程 |
recordRenderCall(Runnable) |
延迟到渲染线程执行 |
小结
| 概念 | 说明 |
|---|---|
| RenderSystem | Blaze3D 的状态管理核心 |
| 线程安全 | 必须在渲染线程调用 |
| 延迟执行 | 状态在绘制时才真正应用 |
| 状态恢复 | 修改后需要手动恢复 |
2.4 BufferBuilder 与顶点构建
本节目标:掌握 BufferBuilder 的使用,理解顶点数据的构建流程
2.4.1 顶点构建系统概述
Blaze3D 的顶点构建系统由以下类组成:
plaintext
┌─────────────────────────────────────────────────────────────┐
│ 顶点构建系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Tesselator (单例) │
│ │ │
│ └──► BufferBuilder (顶点数据构建器) │
│ │ │
│ ├──► VertexFormat (顶点格式定义) │
│ │ │
│ └──► RenderedBuffer (构建完成的数据) │
│ │ │
│ └──► BufferUploader (上传并绘制) │
│ │
└─────────────────────────────────────────────────────────────┘
2.4.2 Tesselator
Tesselator 是一个单例,提供全局的 BufferBuilder 实例。
类路径: com.mojang.blaze3d.vertex.Tesselator
java
// 获取单例
Tesselator tesselator = Tesselator.getInstance();
// 获取 BufferBuilder
BufferBuilder buffer = tesselator.getBuilder();
// 构建顶点...
buffer.begin(...);
// ...添加顶点...
// 结束并获取渲染数据
BufferBuilder.RenderedBuffer renderedBuffer = tesselator.end();
// 上传并绘制
BufferUploader.drawWithShader(renderedBuffer);
为什么使用单例?
- 避免频繁创建/销毁缓冲区
- 内部缓冲区可以复用
- 简化 API 使用
注意事项
java
// ❌ 错误:嵌套使用同一个 Tesselator
tesselator.getBuilder().begin(...);
tesselator.getBuilder().begin(...); // 错误!上一个还没结束
// ✓ 正确:一个 begin 对应一个 end
buffer.begin(...);
// ...
tesselator.end();
2.4.3 BufferBuilder
BufferBuilder 是实际构建顶点数据的类。
类路径: com.mojang.blaze3d.vertex.BufferBuilder
基本流程
java
BufferBuilder buffer = tesselator.getBuilder();
// 1. 开始构建
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
// 2. 添加顶点
buffer.vertex(x, y, z).uv(u, v).color(r, g, b, a).endVertex();
buffer.vertex(...).uv(...).color(...).endVertex();
buffer.vertex(...).uv(...).color(...).endVertex();
buffer.vertex(...).uv(...).color(...).endVertex();
// 3. 结束构建
BufferBuilder.RenderedBuffer rendered = tesselator.end();
// 4. 上传绘制
BufferUploader.drawWithShader(rendered);
begin() 方法
java
buffer.begin(VertexFormat.Mode mode, VertexFormat format);
// mode: 图元类型
// format: 顶点格式
顶点属性方法
BufferBuilder 实现了 VertexConsumer 接口,提供链式调用:
java
// 位置
buffer.vertex(double x, double y, double z)
buffer.vertex(Matrix4f matrix, float x, float y, float z) // 带矩阵变换
// 颜色
buffer.color(int red, int green, int blue, int alpha) // 0-255
buffer.color(float red, float green, float blue, float alpha) // 0.0-1.0
buffer.color(int argb) // 打包的 ARGB
// 纹理坐标
buffer.uv(float u, float v) // UV0 主纹理
// 覆盖层坐标
buffer.overlayCoords(int u, int v) // UV1 覆盖层
// 光照坐标
buffer.uv2(int u, int v) // UV2 光照贴图
buffer.uv2(int packedLight) // 打包的光照值
// 法线
buffer.normal(float x, float y, float z)
buffer.normal(Matrix3f matrix, float x, float y, float z) // 带矩阵变换
// 结束当前顶点
buffer.endVertex()
链式调用示例
java
// 完整的顶点(BLOCK 格式)
buffer.vertex(matrix, x, y, z)
.color(255, 255, 255, 255)
.uv(u, v)
.uv2(light)
.normal(normalMatrix, nx, ny, nz)
.endVertex();
// 简单的顶点(POSITION_COLOR 格式)
buffer.vertex(x, y, z)
.color(255, 0, 0, 255)
.endVertex();
2.4.4 VertexFormat
VertexFormat 定义了顶点包含哪些属性。
类路径: com.mojang.blaze3d.vertex.VertexFormat
预定义格式 (DefaultVertexFormat)
| 格式 | 属性 | 字节数 | 用途 |
|---|---|---|---|
POSITION |
pos | 12 | 纯位置 |
POSITION_COLOR |
pos, color | 16 | 调试、简单图形 |
POSITION_TEX |
pos, uv0 | 20 | 简单纹理 |
POSITION_TEX_COLOR |
pos, uv0, color | 24 | GUI、2D 图形 |
POSITION_COLOR_TEX_LIGHTMAP |
pos, color, uv0, uv2 | 28 | 带光照的 2D |
PARTICLE |
pos, uv0, color, uv2 | 28 | 粒子 |
BLOCK |
pos, color, uv0, uv2, normal | 32 | 方块 |
NEW_ENTITY |
pos, color, uv0, uv1, uv2, normal | 36 | 实体 |
格式详解
PARTICLE 格式内存布局:
┌──────────┬──────────┬──────────┬──────────┐
│ Position │ UV0 │ Color │ UV2 │
│ 3×float │ 2×float │ 4×ubyte │ 2×short │
│ 12 bytes │ 8 bytes │ 4 bytes │ 4 bytes │
└──────────┴──────────┴──────────┴──────────┘
总计:28 字节
BLOCK 格式内存布局:
┌──────────┬──────────┬──────────┬──────────┬──────────┐
│ Position │ Color │ UV0 │ UV2 │ Normal │
│ 3×float │ 4×ubyte │ 2×float │ 2×short │ 3×byte │
│ 12 bytes │ 4 bytes │ 8 bytes │ 4 bytes │ 4 bytes │
└──────────┴──────────┴──────────┴──────────┴──────────┘
总计:32 字节
顶点属性必须按顺序
java
// ✓ 正确:按 POSITION_TEX_COLOR 的顺序
buffer.begin(Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
buffer.vertex(x, y, z) // 1. Position
.uv(u, v) // 2. UV0
.color(r, g, b, a) // 3. Color
.endVertex();
// ❌ 错误:顺序不对
buffer.vertex(x, y, z)
.color(r, g, b, a) // 应该先 uv
.uv(u, v)
.endVertex();
2.4.5 VertexFormat.Mode
定义图元类型:
java
public enum Mode {
LINES, // 线段,每 2 个顶点
LINE_STRIP, // 线带
DEBUG_LINES, // 调试线
DEBUG_LINE_STRIP,// 调试线带
TRIANGLES, // 三角形,每 3 个顶点
TRIANGLE_STRIP, // 三角形带
TRIANGLE_FAN, // 三角形扇
QUADS // 四边形,每 4 个顶点(内部转换为三角形)
}
QUADS 的特殊处理
OpenGL Core Profile 不支持 GL_QUADS,Minecraft 内部会转换:
QUADS 输入:v0, v1, v2, v3
内部转换为 TRIANGLES:
三角形1: v0, v1, v2
三角形2: v0, v2, v3
顶点顺序(逆时针):
v3 ─── v2
│ │
│ │
v0 ─── v1
2.4.6 BufferUploader
BufferUploader 负责上传顶点数据并执行绘制。
类路径: com.mojang.blaze3d.vertex.BufferUploader
java
// 使用当前着色器绘制
BufferUploader.drawWithShader(renderedBuffer);
// 直接绘制(不设置着色器)
BufferUploader.draw(renderedBuffer);
// 重置状态
BufferUploader.reset();
drawWithShader 内部流程
1. 获取当前着色器
2. 应用着色器
3. 设置 Uniform(矩阵、颜色等)
4. 上传顶点数据到 VBO
5. 配置 VAO
6. 执行 glDrawArrays 或 glDrawElements
7. 清理
2.4.7 完整示例
渲染一个带纹理的四边形
java
public void renderTexturedQuad(PoseStack poseStack, ResourceLocation texture,
float x, float y, float width, float height) {
// 设置渲染状态
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, texture);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
// 获取矩阵
Matrix4f matrix = poseStack.last().pose();
// 构建顶点
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX);
// 四个顶点(逆时针)
buffer.vertex(matrix, x, y + height, 0).uv(0, 1).endVertex(); // 左下
buffer.vertex(matrix, x + width, y + height, 0).uv(1, 1).endVertex(); // 右下
buffer.vertex(matrix, x + width, y, 0).uv(1, 0).endVertex(); // 右上
buffer.vertex(matrix, x, y, 0).uv(0, 0).endVertex(); // 左上
// 绘制
BufferUploader.drawWithShader(tesselator.end());
// 恢复状态
RenderSystem.disableBlend();
}
渲染一个彩色三角形
java
public void renderColoredTriangle(PoseStack poseStack) {
RenderSystem.setShader(GameRenderer::getPositionColorShader);
RenderSystem.disableCull();
Matrix4f matrix = poseStack.last().pose();
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
buffer.begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION_COLOR);
// 三个顶点,三种颜色
buffer.vertex(matrix, 0, 1, 0).color(255, 0, 0, 255).endVertex(); // 顶部,红色
buffer.vertex(matrix, -1, -1, 0).color(0, 255, 0, 255).endVertex(); // 左下,绿色
buffer.vertex(matrix, 1, -1, 0).color(0, 0, 255, 255).endVertex(); // 右下,蓝色
BufferUploader.drawWithShader(tesselator.end());
RenderSystem.enableCull();
}
渲染粒子
java
public void renderParticle(PoseStack poseStack, Camera camera,
float x, float y, float z, float size,
float u0, float v0, float u1, float v1,
int light, int color) {
// 计算面向相机的四个角
Vec3 cameraPos = camera.getPosition();
Quaternionf rotation = camera.rotation();
// Billboard 计算...
Vector3f[] corners = calculateBillboardCorners(x, y, z, size, rotation);
Matrix4f matrix = poseStack.last().pose();
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE);
int r = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = color & 0xFF;
int a = (color >> 24) & 0xFF;
buffer.vertex(matrix, corners[0].x, corners[0].y, corners[0].z)
.uv(u1, v1).color(r, g, b, a).uv2(light).endVertex();
buffer.vertex(matrix, corners[1].x, corners[1].y, corners[1].z)
.uv(u1, v0).color(r, g, b, a).uv2(light).endVertex();
buffer.vertex(matrix, corners[2].x, corners[2].y, corners[2].z)
.uv(u0, v0).color(r, g, b, a).uv2(light).endVertex();
buffer.vertex(matrix, corners[3].x, corners[3].y, corners[3].z)
.uv(u0, v1).color(r, g, b, a).uv2(light).endVertex();
BufferUploader.drawWithShader(tesselator.end());
}
2.4.8 性能优化
批量渲染
java
// ❌ 低效:每个物体单独绘制
for (MyObject obj : objects) {
buffer.begin(...);
addVertices(buffer, obj);
BufferUploader.drawWithShader(tesselator.end()); // 每次都上传和绘制
}
// ✓ 高效:批量绘制
buffer.begin(...);
for (MyObject obj : objects) {
addVertices(buffer, obj); // 只添加顶点
}
BufferUploader.drawWithShader(tesselator.end()); // 一次上传和绘制
按状态分组
java
// ❌ 低效:频繁切换纹理
for (MyObject obj : objects) {
RenderSystem.setShaderTexture(0, obj.texture); // 每次都切换
render(obj);
}
// ✓ 高效:按纹理分组
Map<ResourceLocation, List<MyObject>> grouped = groupByTexture(objects);
for (var entry : grouped.entrySet()) {
RenderSystem.setShaderTexture(0, entry.getKey()); // 每组只切换一次
buffer.begin(...);
for (MyObject obj : entry.getValue()) {
addVertices(buffer, obj);
}
BufferUploader.drawWithShader(tesselator.end());
}
2.4.9 与 Arc3D 对比
Arc3D 的顶点构建更底层,但也更灵活:
java
// Arc3D 的 VertexInputLayout
public final class VertexInputLayout {
// 定义顶点属性
// 支持更多属性类型
// 支持实例化渲染
}
// Arc3D 的缓冲管理
public final class GpuBufferPool {
// 缓冲池,复用缓冲区
// 减少内存分配
}
Blaze3D 的 BufferBuilder 更简单易用,但 Arc3D 提供了更多控制。
小结
| 类 | 职责 |
|---|---|
Tesselator |
单例,提供 BufferBuilder |
BufferBuilder |
构建顶点数据 |
VertexFormat |
定义顶点属性 |
VertexFormat.Mode |
定义图元类型 |
BufferUploader |
上传并绘制 |
使用流程
1. Tesselator.getInstance()
2. buffer.begin(mode, format)
3. buffer.vertex(...).uv(...).color(...).endVertex() // 重复
4. tesselator.end()
5. BufferUploader.drawWithShader(...)
2.5 RenderType 系统
本节目标:理解 RenderType 的设计理念,学会创建自定义 RenderType
2.5.1 什么是 RenderType?
RenderType 是 Blaze3D 中最重要的抽象之一,它封装了一组完整的渲染状态。
RenderType = VertexFormat + Shader + 渲染状态组合
渲染状态包括:
- 着色器
- 纹理
- 混合模式
- 深度测试
- 面剔除
- 光照贴图
- 输出目标
- ...
为什么需要 RenderType?
java
// ❌ 没有 RenderType:手动管理每个状态
RenderSystem.enableBlend();
RenderSystem.blendFunc(...);
RenderSystem.enableDepthTest();
RenderSystem.depthMask(false);
RenderSystem.disableCull();
RenderSystem.setShader(...);
RenderSystem.setShaderTexture(0, texture);
// 渲染...
// 还要手动恢复所有状态!
// ✓ 有 RenderType:一行搞定
RenderType type = RenderType.translucent();
type.setupRenderState(); // 自动设置所有状态
// 渲染...
type.clearRenderState(); // 自动恢复
2.5.2 预定义 RenderType
Minecraft 提供了大量预定义的 RenderType:
方块渲染类型
| 类型 | 用途 | 特点 |
|---|---|---|
solid() |
不透明方块 | 无混合,深度写入 |
cutout() |
镂空方块(玻璃板) | Alpha 测试,无混合 |
cutoutMipped() |
带 Mipmap 的镂空(树叶) | Alpha 测试,Mipmap |
translucent() |
半透明方块(水、冰) | 混合,按距离排序 |
tripwire() |
绊线 | 特殊混合 |
实体渲染类型
| 类型 | 用途 |
|---|---|
entitySolid(texture) |
不透明实体 |
entityCutout(texture) |
镂空实体 |
entityCutoutNoCull(texture) |
镂空实体,双面 |
entityTranslucent(texture) |
半透明实体 |
entityTranslucentCull(texture) |
半透明实体,单面 |
entitySmoothCutout(texture) |
平滑镂空 |
entityDecal(texture) |
贴花效果 |
entityNoOutline(texture) |
无轮廓实体 |
eyes(texture) |
发光眼睛 |
energySwirl(texture, u, v) |
能量漩涡 |
entityGlint() |
附魔光泽 |
armorGlint() |
盔甲光泽 |
其他渲染类型
| 类型 | 用途 |
|---|---|
text(texture) |
文字渲染 |
textSeeThrough(texture) |
透视文字 |
lightning() |
闪电 |
lines() |
线条 |
lineStrip() |
线带 |
debugQuads() |
调试四边形 |
debugLineStrip(width) |
调试线带 |
waterMask() |
水面遮罩 |
outline(texture) |
轮廓 |
glint() |
光泽效果 |
crumbling(texture) |
方块破坏效果 |
2.5.3 使用 RenderType
基本使用
java
// 获取 RenderType
RenderType renderType = RenderType.entityTranslucent(myTexture);
// 设置渲染状态
renderType.setupRenderState();
// 渲染...
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
buffer.begin(renderType.mode(), renderType.format());
// 添加顶点...
BufferUploader.drawWithShader(tesselator.end());
// 清理渲染状态
renderType.clearRenderState();
使用 MultiBufferSource
更常见的方式是使用 MultiBufferSource:
java
public void render(PoseStack poseStack, MultiBufferSource bufferSource,
int packedLight, int packedOverlay) {
// 获取指定 RenderType 的 VertexConsumer
VertexConsumer consumer = bufferSource.getBuffer(
RenderType.entityTranslucent(myTexture)
);
// 直接添加顶点
Matrix4f matrix = poseStack.last().pose();
consumer.vertex(matrix, x, y, z)
.color(255, 255, 255, 255)
.uv(u, v)
.overlayCoords(packedOverlay)
.uv2(packedLight)
.normal(poseStack.last().normal(), 0, 1, 0)
.endVertex();
// ...
// MultiBufferSource 会自动管理绘制
}
MultiBufferSource 的优势
java
// MultiBufferSource 会自动:
// 1. 按 RenderType 分组顶点
// 2. 批量绘制相同 RenderType 的顶点
// 3. 按正确顺序渲染(不透明 → 半透明)
2.5.4 RenderType 的内部结构
CompositeState
RenderType 由多个 RenderStateShard 组成:
java
RenderType.CompositeState state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_SHADER)
.setTextureState(new TextureStateShard(texture, false, false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.setCullState(NO_CULL)
.setWriteMaskState(COLOR_DEPTH_WRITE)
.createCompositeState(true); // true = 需要排序
RenderStateShard 类型
| Shard | 作用 |
|---|---|
ShaderStateShard |
着色器 |
TextureStateShard |
纹理 |
TransparencyStateShard |
透明度/混合 |
DepthTestStateShard |
深度测试 |
CullStateShard |
面剔除 |
LightmapStateShard |
光照贴图 |
OverlayStateShard |
覆盖层 |
LayeringStateShard |
分层 |
OutputStateShard |
输出目标 |
WriteMaskStateShard |
写入遮罩 |
LineStateShard |
线宽 |
ColorLogicStateShard |
颜色逻辑操作 |
预定义的 Shard
java
// 透明度
RenderStateShard.NO_TRANSPARENCY // 无混合
RenderStateShard.ADDITIVE_TRANSPARENCY // 加法混合
RenderStateShard.LIGHTNING_TRANSPARENCY // 闪电混合
RenderStateShard.GLINT_TRANSPARENCY // 光泽混合
RenderStateShard.CRUMBLING_TRANSPARENCY // 破坏效果混合
RenderStateShard.TRANSLUCENT_TRANSPARENCY // 标准透明混合
// 深度测试
RenderStateShard.NO_DEPTH_TEST // 无深度测试
RenderStateShard.EQUAL_DEPTH_TEST // 等于
RenderStateShard.LEQUAL_DEPTH_TEST // 小于等于
// 面剔除
RenderStateShard.NO_CULL // 不剔除
RenderStateShard.CULL // 剔除背面
// 写入遮罩
RenderStateShard.COLOR_WRITE // 只写颜色
RenderStateShard.DEPTH_WRITE // 只写深度
RenderStateShard.COLOR_DEPTH_WRITE // 写颜色和深度
// 光照
RenderStateShard.LIGHTMAP // 使用光照贴图
RenderStateShard.NO_LIGHTMAP // 不使用光照贴图
// 覆盖层
RenderStateShard.OVERLAY // 使用覆盖层
RenderStateShard.NO_OVERLAY // 不使用覆盖层
2.5.5 创建自定义 RenderType
方法一:使用 create()
java
public static final RenderType MY_CUSTOM_TYPE = RenderType.create(
"my_mod:custom", // 名称
DefaultVertexFormat.POSITION_TEX_COLOR, // 顶点格式
VertexFormat.Mode.QUADS, // 图元模式
256, // 缓冲区大小
false, // affectsCrumbling
true, // sortOnUpload(半透明需要)
RenderType.CompositeState.builder()
.setShaderState(new ShaderStateShard(() -> myShader))
.setTextureState(new TextureStateShard(myTexture, false, false))
.setTransparencyState(RenderStateShard.TRANSLUCENT_TRANSPARENCY)
.setDepthTestState(RenderStateShard.LEQUAL_DEPTH_TEST)
.setCullState(RenderStateShard.NO_CULL)
.setLightmapState(RenderStateShard.NO_LIGHTMAP)
.setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE)
.createCompositeState(false)
);
方法二:继承 RenderType
java
public class MyRenderTypes extends RenderType {
// 必须有这个构造函数(不会被调用)
public MyRenderTypes(String name, VertexFormat format, VertexFormat.Mode mode,
int bufferSize, boolean affectsCrumbling, boolean sortOnUpload,
Runnable setupState, Runnable clearState) {
super(name, format, mode, bufferSize, affectsCrumbling, sortOnUpload,
setupState, clearState);
}
// 自定义 RenderType
private static final RenderType GLOW = create(
"my_mod:glow",
DefaultVertexFormat.POSITION_TEX_COLOR,
VertexFormat.Mode.QUADS,
256,
false,
true,
CompositeState.builder()
.setShaderState(POSITION_TEX_COLOR_SHADER)
.setTextureState(new TextureStateShard(
new ResourceLocation("my_mod", "textures/glow.png"),
false, false))
.setTransparencyState(ADDITIVE_TRANSPARENCY)
.setCullState(NO_CULL)
.setDepthTestState(LEQUAL_DEPTH_TEST)
.setWriteMaskState(COLOR_WRITE) // 不写深度
.createCompositeState(false)
);
public static RenderType glow() {
return GLOW;
}
// 带参数的 RenderType(每个纹理一个实例)
private static final Function<ResourceLocation, RenderType> CUSTOM_ENTITY =
Util.memoize(texture -> create(
"my_mod:custom_entity",
DefaultVertexFormat.NEW_ENTITY,
VertexFormat.Mode.QUADS,
256,
true,
true,
CompositeState.builder()
.setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_SHADER)
.setTextureState(new TextureStateShard(texture, false, false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.setCullState(NO_CULL)
.createCompositeState(true)
));
public static RenderType customEntity(ResourceLocation texture) {
return CUSTOM_ENTITY.apply(texture);
}
}
方法三:使用 Mixin 修改现有 RenderType
java
@Mixin(RenderType.class)
public abstract class RenderTypeMixin {
@Inject(method = "translucent", at = @At("RETURN"), cancellable = true)
private static void modifyTranslucent(CallbackInfoReturnable<RenderType> cir) {
// 修改返回的 RenderType
// 注意:这会影响所有使用 translucent() 的地方
}
}
2.5.6 RenderType 参数详解
create() 参数
java
RenderType.create(
String name, // 唯一名称,用于调试
VertexFormat format, // 顶点格式
VertexFormat.Mode mode,// 图元模式
int bufferSize, // 缓冲区大小(顶点数)
boolean affectsCrumbling, // 是否参与方块破坏效果
boolean sortOnUpload, // 是否需要排序(半透明需要)
CompositeState state // 渲染状态组合
)
bufferSize 选择
java
// 小型渲染(GUI、单个物体)
bufferSize = 256;
// 中型渲染(多个实体)
bufferSize = 1024;
// 大型渲染(方块、地形)
bufferSize = 2097152; // 2MB
sortOnUpload
java
// 不透明物体:false
// 半透明物体:true(需要按距离排序)
2.5.7 Photon 特效系统的 RenderType
在 Photon 特效系统中,我们创建了自定义 RenderType:
kotlin
object PhotonRenderTypes : RenderType("photon", ...) {
// HDR 粒子渲染类型
private val HDR_PARTICLE = create(
"photon:hdr_particle",
DefaultVertexFormat.PARTICLE,
VertexFormat.Mode.QUADS,
256,
false,
true,
CompositeState.builder()
.setShaderState(ShaderStateShard { PhotonShaderManager.getHDRParticleShader() })
.setTextureState(/* 动态纹理 */)
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setCullState(NO_CULL)
.setDepthTestState(LEQUAL_DEPTH_TEST)
.setWriteMaskState(COLOR_WRITE)
.setOutputState(/* MRT 输出 */)
.createCompositeState(true)
)
// Bloom 发光渲染类型
private val BLOOM_PARTICLE = create(
"photon:bloom_particle",
DefaultVertexFormat.PARTICLE,
VertexFormat.Mode.QUADS,
256,
false,
true,
CompositeState.builder()
.setShaderState(ShaderStateShard { PhotonShaderManager.getBloomShader() })
.setTransparencyState(ADDITIVE_TRANSPARENCY) // 加法混合
.setCullState(NO_CULL)
.setWriteMaskState(COLOR_WRITE)
.createCompositeState(false)
)
}
2.5.8 调试 RenderType
查看当前 RenderType
java
// 在渲染代码中打印
System.out.println("Current RenderType: " + renderType.toString());
常见问题
| 问题 | 可能原因 |
|---|---|
| 物体不显示 | 深度测试失败、面剔除、着色器错误 |
| 透明不正确 | 混合模式错误、未排序 |
| 闪烁 | Z-Fighting、深度写入问题 |
| 颜色错误 | 着色器 uniform 未设置 |
小结
| 概念 | 说明 |
|---|---|
| RenderType | 渲染状态的完整封装 |
| CompositeState | 多个 RenderStateShard 的组合 |
| RenderStateShard | 单个渲染状态 |
| MultiBufferSource | 自动管理多个 RenderType |
创建自定义 RenderType 的步骤
- 确定顶点格式
- 确定图元模式
- 选择/创建着色器
- 配置渲染状态(混合、深度、剔除等)
- 使用
RenderType.create()创建
2.6 RenderTarget 与帧缓冲
本节目标:掌握 RenderTarget 的使用,理解后处理效果的实现原理
2.6.1 RenderTarget 概述
RenderTarget 是 Minecraft 对 OpenGL 帧缓冲对象(FBO)的封装。
类路径: com.mojang.blaze3d.pipeline.RenderTarget
java
// RenderTarget 封装了:
// - 帧缓冲对象 (FBO)
// - 颜色纹理附件
// - 深度缓冲附件(可选)
主要用途
- 离屏渲染:渲染到纹理而不是屏幕
- 后处理效果:对渲染结果进行处理
- 多 Pass 渲染:分阶段渲染
2.6.2 创建 RenderTarget
基本创建
java
// 创建带深度缓冲的 RenderTarget
RenderTarget target = new RenderTarget(
width, // 宽度
height, // 高度
true, // 是否使用深度缓冲
Minecraft.ON_OSX // Mac 兼容性标志
);
// 创建不带深度缓冲的 RenderTarget
RenderTarget target = new RenderTarget(width, height, false, Minecraft.ON_OSX);
获取主渲染目标
java
// Minecraft 的主渲染目标(屏幕)
RenderTarget mainTarget = Minecraft.getInstance().getMainRenderTarget();
2.6.3 RenderTarget API
绑定操作
java
// 绑定为渲染目标(写入)
target.bindWrite(true); // true = 同时设置视口
target.bindWrite(false); // false = 不设置视口
// 解绑写入
target.unbindWrite();
// 绑定为读取源
target.bindRead();
// 解绑读取
target.unbindRead();
清除操作
java
// 设置清除颜色
target.setClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 透明黑色
target.setClearColor(1.0f, 1.0f, 1.0f, 1.0f); // 不透明白色
// 清除缓冲
target.clear(Minecraft.ON_OSX);
尺寸操作
java
// 获取尺寸
int width = target.width;
int height = target.height;
// 调整大小
target.resize(newWidth, newHeight, Minecraft.ON_OSX);
纹理访问
java
// 获取颜色纹理 ID
int colorTextureId = target.getColorTextureId();
// 获取深度缓冲 ID
int depthBufferId = target.getDepthTextureId();
// 获取帧缓冲 ID
int frameBufferId = target.frameBufferId;
复制操作
java
// 复制到屏幕
target.blitToScreen(screenWidth, screenHeight);
// 复制深度缓冲
target.copyDepthFrom(otherTarget);
过滤模式
java
// 设置纹理过滤模式
target.setFilterMode(GlConst.GL_NEAREST); // 最近邻(像素化)
target.setFilterMode(GlConst.GL_LINEAR); // 线性(平滑)
销毁
java
// 销毁缓冲
target.destroyBuffers();
2.6.4 渲染到 RenderTarget
基本流程
java
// 1. 保存当前状态
RenderTarget previousTarget = Minecraft.getInstance().getMainRenderTarget();
// 2. 绑定自定义目标
myTarget.bindWrite(true);
// 3. 清除
myTarget.clear(Minecraft.ON_OSX);
// 4. 渲染内容
renderMyContent();
// 5. 恢复
previousTarget.bindWrite(true);
完整示例
java
public class MyRenderer {
private RenderTarget offscreenTarget;
public void init(int width, int height) {
offscreenTarget = new RenderTarget(width, height, true, Minecraft.ON_OSX);
offscreenTarget.setClearColor(0, 0, 0, 0);
}
public void render(PoseStack poseStack) {
Minecraft mc = Minecraft.getInstance();
// 渲染到离屏目标
offscreenTarget.bindWrite(true);
offscreenTarget.clear(Minecraft.ON_OSX);
// 渲染场景...
renderScene(poseStack);
// 恢复主目标
mc.getMainRenderTarget().bindWrite(true);
// 使用离屏纹理进行后处理
applyPostProcess(offscreenTarget.getColorTextureId());
}
public void resize(int width, int height) {
offscreenTarget.resize(width, height, Minecraft.ON_OSX);
}
public void cleanup() {
offscreenTarget.destroyBuffers();
}
}
2.6.5 后处理效果实现
后处理流程
plaintext
┌─────────────────────────────────────────────────────────────┐
│ 后处理流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 渲染场景到 FBO A │
│ ┌─────────┐ │
│ │ 场景 │ ──► FBO A (颜色纹理) │
│ └─────────┘ │
│ │
│ 2. 后处理 Pass 1:FBO A → FBO B │
│ ┌─────────┐ ┌─────────────┐ ┌─────────┐ │
│ │ FBO A │ ──► │ 模糊着色器 │ ──► │ FBO B │ │
│ └─────────┘ └─────────────┘ └─────────┘ │
│ │
│ 3. 后处理 Pass 2:FBO B → 屏幕 │
│ ┌─────────┐ ┌─────────────┐ ┌─────────┐ │
│ │ FBO B │ ──► │ 合成着色器 │ ──► │ 屏幕 │ │
│ └─────────┘ └─────────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
全屏四边形渲染
后处理需要渲染一个覆盖整个屏幕的四边形:
java
public void renderFullscreenQuad() {
RenderSystem.setShader(GameRenderer::getPositionTexShader);
Matrix4f matrix = new Matrix4f().setOrtho(0, 1, 1, 0, -1, 1);
RenderSystem.setProjectionMatrix(matrix, VertexSorting.ORTHOGRAPHIC_Z);
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX);
buffer.vertex(0, 0, 0).uv(0, 0).endVertex();
buffer.vertex(0, 1, 0).uv(0, 1).endVertex();
buffer.vertex(1, 1, 0).uv(1, 1).endVertex();
buffer.vertex(1, 0, 0).uv(1, 0).endVertex();
BufferUploader.drawWithShader(tesselator.end());
}
简单模糊效果
java
public class BlurEffect {
private RenderTarget tempTarget;
private ShaderInstance blurShader;
public void apply(RenderTarget source, RenderTarget destination) {
// 水平模糊
tempTarget.bindWrite(true);
tempTarget.clear(Minecraft.ON_OSX);
RenderSystem.setShader(() -> blurShader);
RenderSystem.setShaderTexture(0, source.getColorTextureId());
blurShader.getUniform("Direction").set(1.0f, 0.0f); // 水平
renderFullscreenQuad();
// 垂直模糊
destination.bindWrite(true);
destination.clear(Minecraft.ON_OSX);
RenderSystem.setShaderTexture(0, tempTarget.getColorTextureId());
blurShader.getUniform("Direction").set(0.0f, 1.0f); // 垂直
renderFullscreenQuad();
}
}
2.6.6 Ping-Pong 缓冲
多 Pass 后处理常用 Ping-Pong 技术:
java
public class PingPongBuffer {
private RenderTarget[] targets = new RenderTarget[2];
private int currentIndex = 0;
public PingPongBuffer(int width, int height) {
targets[0] = new RenderTarget(width, height, false, Minecraft.ON_OSX);
targets[1] = new RenderTarget(width, height, false, Minecraft.ON_OSX);
}
public RenderTarget getSource() {
return targets[currentIndex];
}
public RenderTarget getDestination() {
return targets[1 - currentIndex];
}
public void swap() {
currentIndex = 1 - currentIndex;
}
public void resize(int width, int height) {
targets[0].resize(width, height, Minecraft.ON_OSX);
targets[1].resize(width, height, Minecraft.ON_OSX);
}
}
// 使用
PingPongBuffer pingPong = new PingPongBuffer(width, height);
// 初始内容写入
pingPong.getSource().bindWrite(true);
renderInitialContent();
// 多次处理
for (int i = 0; i < iterations; i++) {
pingPong.getDestination().bindWrite(true);
RenderSystem.setShaderTexture(0, pingPong.getSource().getColorTextureId());
applyEffect();
pingPong.swap();
}
// 最终结果在 pingPong.getSource()
2.6.7 与 Arc3D 对比
Arc3D 的帧缓冲管理更加完善:
java
// Arc3D 的 GLFramebuffer
public final class GLFramebuffer extends Framebuffer {
// 支持多个颜色附件(MRT)
// 支持深度/模板附件
// 自动管理生命周期
}
// Arc3D 的帧缓冲缓存
public final class FramebufferCache {
// 缓存和复用帧缓冲
// 自动清理过期帧缓冲
public Framebuffer findFramebuffer(FramebufferDesc desc);
public void insertFramebuffer(FramebufferDesc desc, Framebuffer fb);
public void purgeStaleFramebuffers();
}
Minecraft 的 RenderTarget 相对简单,但足够满足基本需求。
2.6.8 常见问题
深度缓冲共享
java
// 复制深度缓冲到另一个 RenderTarget
targetB.copyDepthFrom(targetA);
// 或者手动复制
GlStateManager._glBindFramebuffer(GL_READ_FRAMEBUFFER, targetA.frameBufferId);
GlStateManager._glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetB.frameBufferId);
GL30.glBlitFramebuffer(
0, 0, width, height,
0, 0, width, height,
GL_DEPTH_BUFFER_BIT,
GL_NEAREST
);
窗口大小变化
java
// 监听窗口大小变化
public void onResize(int width, int height) {
if (myTarget != null) {
myTarget.resize(width, height, Minecraft.ON_OSX);
}
}
纹理采样问题
java
// 确保纹理过滤模式正确
target.setFilterMode(GL_LINEAR); // 后处理通常用线性
// 确保纹理环绕模式正确
GL11.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
小结
| 概念 | 说明 |
|---|---|
| RenderTarget | FBO 的封装 |
| 离屏渲染 | 渲染到纹理 |
| 后处理 | 对渲染结果进行处理 |
| Ping-Pong | 多 Pass 处理技术 |
| MRT | 多渲染目标 |
| HDR | 高动态范围渲染 |
后处理流程
场景 → FBO → 后处理着色器 → 屏幕
2.7 ShaderInstance 详解
本节目标:掌握 Minecraft 着色器系统,学会创建和使用自定义着色器
2.7.1 ShaderInstance 概述
ShaderInstance 是 Minecraft 对 OpenGL 着色器程序的封装。
类路径: net.minecraft.client.renderer.ShaderInstance
java
// ShaderInstance 封装了:
// - 顶点着色器
// - 片段着色器
// - Uniform 变量
// - Sampler(纹理采样器)
2.7.2 着色器文件结构
Minecraft 着色器由三个文件组成:
plaintext
assets/<namespace>/shaders/core/
├── <name>.json # 配置文件
├── <name>.vsh # 顶点着色器
└── <name>.fsh # 片段着色器
JSON 配置文件
json
{
"blend": {
"func": "add",
"srcrgb": "srcalpha",
"dstrgb": "1-srcalpha"
},
"vertex": "minecraft:particle",
"fragment": "minecraft:particle",
"attributes": [
"Position",
"UV0",
"Color",
"UV2"
],
"samplers": [
{ "name": "Sampler0" },
{ "name": "Sampler2" }
],
"uniforms": [
{
"name": "ModelViewMat",
"type": "matrix4x4",
"count": 16,
"values": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
},
{
"name": "ProjMat",
"type": "matrix4x4",
"count": 16,
"values": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
},
{
"name": "ColorModulator",
"type": "float",
"count": 4,
"values": [1.0, 1.0, 1.0, 1.0]
},
{
"name": "FogStart",
"type": "float",
"count": 1,
"values": [0.0]
},
{
"name": "FogEnd",
"type": "float",
"count": 1,
"values": [1.0]
},
{
"name": "FogColor",
"type": "float",
"count": 4,
"values": [0.0, 0.0, 0.0, 0.0]
},
{
"name": "FogShape",
"type": "int",
"count": 1,
"values": [0]
}
]
}
JSON 字段说明
| 字段 | 说明 |
|---|---|
blend |
混合模式配置 |
vertex |
顶点着色器路径 |
fragment |
片段着色器路径 |
attributes |
顶点属性列表 |
samplers |
纹理采样器列表 |
uniforms |
Uniform 变量列表 |
Uniform 类型
| type | GLSL 类型 | count |
|---|---|---|
int |
int | 1 |
float |
float | 1-4 |
matrix4x4 |
mat4 | 16 |
matrix3x3 |
mat3 | 9 |
matrix2x2 |
mat2 | 4 |
顶点着色器 (.vsh)
glsl
#version 150
#moj_import <fog.glsl>
in vec3 Position;
in vec2 UV0;
in vec4 Color;
in ivec2 UV2;
uniform sampler2D Sampler2;
uniform mat4 ModelViewMat;
uniform mat4 ProjMat;
uniform int FogShape;
out float vertexDistance;
out vec2 texCoord0;
out vec4 vertexColor;
void main() {
gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
vertexDistance = fog_distance(ModelViewMat, Position, FogShape);
texCoord0 = UV0;
vertexColor = Color * texelFetch(Sampler2, UV2 / 16, 0);
}
片段着色器 (.fsh)
glsl
#version 150
#moj_import <fog.glsl>
uniform sampler2D Sampler0;
uniform vec4 ColorModulator;
uniform float FogStart;
uniform float FogEnd;
uniform vec4 FogColor;
in float vertexDistance;
in vec2 texCoord0;
in vec4 vertexColor;
out vec4 fragColor;
void main() {
vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator;
if (color.a < 0.01) {
discard;
}
fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor);
}
2.7.3 #moj_import 指令
Minecraft 支持自定义的 #moj_import 指令来包含其他文件:
glsl
#moj_import <fog.glsl> // 从 minecraft:shaders/include/fog.glsl
#moj_import <light.glsl> // 从 minecraft:shaders/include/light.glsl
#moj_import <matrix.glsl> // 从 minecraft:shaders/include/matrix.glsl
内置 include 文件
| 文件 | 内容 |
|---|---|
fog.glsl |
雾效计算函数 |
light.glsl |
光照计算函数 |
matrix.glsl |
矩阵工具函数 |
fog.glsl 内容
glsl
// 计算雾距离
float fog_distance(mat4 modelViewMat, vec3 pos, int shape) {
if (shape == 0) {
// 球形雾
return length((modelViewMat * vec4(pos, 1.0)).xyz);
} else {
// 圆柱形雾
float distXZ = length((modelViewMat * vec4(pos, 1.0)).xz);
float distY = abs((modelViewMat * vec4(pos, 1.0)).y);
return max(distXZ, distY);
}
}
// 线性雾混合
vec4 linear_fog(vec4 inColor, float vertexDistance, float fogStart, float fogEnd, vec4 fogColor) {
if (vertexDistance <= fogStart) {
return inColor;
}
float fogValue = vertexDistance < fogEnd
? smoothstep(fogStart, fogEnd, vertexDistance)
: 1.0;
return vec4(mix(inColor.rgb, fogColor.rgb, fogValue * fogColor.a), inColor.a);
}
2.7.4 加载 ShaderInstance
使用 ResourceProvider
java
// 从资源系统加载
ResourceProvider resourceProvider = Minecraft.getInstance().getResourceManager();
ShaderInstance shader = new ShaderInstance(
resourceProvider,
new ResourceLocation("my_mod", "my_shader"),
DefaultVertexFormat.POSITION_TEX_COLOR
);
在 RegisterShadersEvent 中注册
java
@SubscribeEvent
public static void onRegisterShaders(RegisterShadersEvent event) throws IOException {
event.registerShader(
new ShaderInstance(
event.getResourceProvider(),
new ResourceLocation("my_mod", "my_shader"),
DefaultVertexFormat.POSITION_TEX_COLOR
),
shader -> myShader = shader // 保存引用
);
}
2.7.5 使用 ShaderInstance
基本使用
java
// 设置为当前着色器
RenderSystem.setShader(() -> myShader);
// 设置 Uniform
Uniform uniform = myShader.getUniform("MyValue");
if (uniform != null) {
uniform.set(1.0f);
}
// 设置 Sampler
myShader.setSampler("MySampler", () -> textureId);
// 应用着色器
myShader.apply();
// 渲染...
// 清除
myShader.clear();
Uniform 操作
java
// 获取 Uniform
Uniform uniform = shader.getUniform("UniformName");
// 设置不同类型的值
uniform.set(1.0f); // float
uniform.set(1.0f, 2.0f); // vec2
uniform.set(1.0f, 2.0f, 3.0f); // vec3
uniform.set(1.0f, 2.0f, 3.0f, 4.0f); // vec4
uniform.set(intValue); // int
uniform.set(matrix); // mat4
uniform.setMat2x2(m00, m01, m10, m11); // mat2
uniform.setMat3x3(...); // mat3
Sampler 操作
java
// 设置采样器(使用 IntSupplier)
shader.setSampler("Sampler0", () -> textureId);
// 注意:必须使用 lambda 或 IntSupplier
// ❌ 错误
shader.setSampler("Sampler0", textureId); // 编译错误
// ✓ 正确
shader.setSampler("Sampler0", () -> textureId);
2.7.6 创建自定义着色器
步骤 1:创建 JSON 配置
assets/my_mod/shaders/core/glow.json:
json
{
"blend": {
"func": "add",
"srcrgb": "srcalpha",
"dstrgb": "one"
},
"vertex": "my_mod:glow",
"fragment": "my_mod:glow",
"attributes": [
"Position",
"UV0",
"Color"
],
"samplers": [
{ "name": "Sampler0" }
],
"uniforms": [
{
"name": "ModelViewMat",
"type": "matrix4x4",
"count": 16,
"values": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
},
{
"name": "ProjMat",
"type": "matrix4x4",
"count": 16,
"values": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
},
{
"name": "GlowIntensity",
"type": "float",
"count": 1,
"values": [1.0]
},
{
"name": "GlowColor",
"type": "float",
"count": 3,
"values": [1.0, 1.0, 1.0]
},
{
"name": "Time",
"type": "float",
"count": 1,
"values": [0.0]
}
]
}
步骤 2:创建顶点着色器
assets/my_mod/shaders/core/glow.vsh:
glsl
#version 150
in vec3 Position;
in vec2 UV0;
in vec4 Color;
uniform mat4 ModelViewMat;
uniform mat4 ProjMat;
out vec2 texCoord0;
out vec4 vertexColor;
void main() {
gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
texCoord0 = UV0;
vertexColor = Color;
}
步骤 3:创建片段着色器
assets/my_mod/shaders/core/glow.fsh:
glsl
#version 150
uniform sampler2D Sampler0;
uniform float GlowIntensity;
uniform vec3 GlowColor;
uniform float Time;
in vec2 texCoord0;
in vec4 vertexColor;
out vec4 fragColor;
void main() {
vec4 texColor = texture(Sampler0, texCoord0);
if (texColor.a < 0.01) {
discard;
}
// 脉动效果
float pulse = 0.5 + 0.5 * sin(Time * 3.0);
float intensity = GlowIntensity * (0.8 + 0.2 * pulse);
// 应用发光颜色
vec3 glow = texColor.rgb * GlowColor * intensity;
fragColor = vec4(glow, texColor.a) * vertexColor;
}
步骤 4:注册着色器
java
public class MyShaders {
private static ShaderInstance glowShader;
public static void register(RegisterShadersEvent event) throws IOException {
event.registerShader(
new ShaderInstance(
event.getResourceProvider(),
new ResourceLocation("my_mod", "glow"),
DefaultVertexFormat.POSITION_TEX_COLOR
),
shader -> glowShader = shader
);
}
public static ShaderInstance getGlowShader() {
return glowShader;
}
}
步骤 5:使用着色器
java
public void renderGlowingQuad(PoseStack poseStack, float time) {
ShaderInstance shader = MyShaders.getGlowShader();
RenderSystem.setShader(() -> shader);
RenderSystem.setShaderTexture(0, myTexture);
// 设置自定义 Uniform
shader.getUniform("GlowIntensity").set(2.0f);
shader.getUniform("GlowColor").set(1.0f, 0.5f, 0.0f); // 橙色
shader.getUniform("Time").set(time);
// 渲染
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
// 添加顶点...
BufferUploader.drawWithShader(tesselator.end());
}
2.7.7 与 Arc3D 对比
Arc3D 有自己的着色器编译器,比 Minecraft 更强大:
java
// Arc3D 的着色器编译器
public final class ShaderCompiler {
// 支持 Arc3D 着色语言
// 编译到 SPIR-V、GLSL、ESSL
// 比 glslang 快 6-70 倍
public TranslationUnit parse(String source, ShaderKind kind);
public byte[] generateSPIRV(TranslationUnit unit);
public String generateGLSL(TranslationUnit unit);
}
// Arc3D 的 GLProgram
public final class GLProgram extends ManagedResource {
// 管理着色器程序
// 支持 Uniform Block
// 支持 SSBO
}
Minecraft 的 ShaderInstance 更简单,但 Arc3D 提供了更多高级功能。
2.7.9 调试着色器
常见错误
| 错误 | 原因 |
|---|---|
| 着色器编译失败 | GLSL 语法错误 |
| Uniform 为 null | 名称拼写错误或未使用 |
| 纹理不显示 | Sampler 未设置或纹理未绑定 |
| 颜色错误 | Uniform 值错误 |
调试技巧
glsl
// 在片段着色器中输出调试颜色
fragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色 = 着色器在工作
// 输出 UV 坐标作为颜色
fragColor = vec4(texCoord0, 0.0, 1.0);
// 输出法线作为颜色
fragColor = vec4(normal * 0.5 + 0.5, 1.0);
检查 Uniform
java
// 打印所有 Uniform
for (String name : shader.getUniformNames()) {
Uniform uniform = shader.getUniform(name);
System.out.println(name + ": " + uniform);
}
小结
| 概念 | 说明 |
|---|---|
| ShaderInstance | 着色器程序封装 |
| JSON 配置 | 定义着色器属性 |
| .vsh / .fsh | 顶点/片段着色器 |
| #moj_import | 包含其他文件 |
| Uniform | 着色器参数 |
| Sampler | 纹理采样器 |
创建自定义着色器步骤
- 创建 JSON 配置文件
- 创建顶点着色器 (.vsh)
- 创建片段着色器 (.fsh)
- 在 RegisterShadersEvent 中注册
- 使用 RenderSystem.setShader() 设置
第二章总结
应掌握:
- 立即模式 - 老版本渲染方式
- Blaze3D 架构 - 现代渲染系统概述
- RenderSystem - 状态管理 API
- BufferBuilder - 顶点数据构建
- RenderType - 渲染类型系统
- RenderTarget - 帧缓冲和后处理
- ShaderInstance - 着色器系统
全部评论 (0)
暂无评论,快来抢沙发吧~