现代Minecraft渲染导论 第二章:Minecraft渲染管线和Blaze3D(计算机图形学)

前言

本章主要介绍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();

虽然看起来像是缓冲模式,但底层仍然是:

  1. 在 CPU 端构建顶点数据
  2. 每次 draw() 都上传到 GPU
  3. 使用固定管线或简单着色器

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 的作用:

  1. 状态缓存:避免重复设置相同状态
  2. 统一接口:封装 OpenGL 调用
  3. 调试支持:更容易追踪状态变化

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 不使用更现代的方案?

  1. 兼容性:需要支持大量老旧硬件
  2. 稳定性:游戏已经很成熟,大改风险高
  3. 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 的步骤

  1. 确定顶点格式
  2. 确定图元模式
  3. 选择/创建着色器
  4. 配置渲染状态(混合、深度、剔除等)
  5. 使用 RenderType.create() 创建

2.6 RenderTarget 与帧缓冲

本节目标:掌握 RenderTarget 的使用,理解后处理效果的实现原理

2.6.1 RenderTarget 概述

RenderTarget 是 Minecraft 对 OpenGL 帧缓冲对象(FBO)的封装。

类路径: com.mojang.blaze3d.pipeline.RenderTarget

java 复制代码
// RenderTarget 封装了:
// - 帧缓冲对象 (FBO)
// - 颜色纹理附件
// - 深度缓冲附件(可选)

主要用途

  1. 离屏渲染:渲染到纹理而不是屏幕
  2. 后处理效果:对渲染结果进行处理
  3. 多 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 纹理采样器

创建自定义着色器步骤

  1. 创建 JSON 配置文件
  2. 创建顶点着色器 (.vsh)
  3. 创建片段着色器 (.fsh)
  4. 在 RegisterShadersEvent 中注册
  5. 使用 RenderSystem.setShader() 设置

第二章总结

应掌握:

  1. 立即模式 - 老版本渲染方式
  2. Blaze3D 架构 - 现代渲染系统概述
  3. RenderSystem - 状态管理 API
  4. BufferBuilder - 顶点数据构建
  5. RenderType - 渲染类型系统
  6. RenderTarget - 帧缓冲和后处理
  7. ShaderInstance - 着色器系统
游客

全部评论 (0)

暂无评论,快来抢沙发吧~