NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
Vulkan 렌더링 파이프라인 심화
부록: Vulkan 심화

Vulkan 렌더링 파이프라인 심화

NeoForge 26의 Vulkan 백엔드 구조, RenderType 매핑, GLSL에서 SPIR-V 컴파일까지 커스텀 셰이더 작성의 전체 흐름을 다룹니다.

⚠️ 중요: 렌더 백엔드 사실 정보

NeoForge 26의 기본 렌더 백엔드는 OpenGL이며, Vulkan은 선택적 옵션으로 기본 활성화되지 않습니다.

본 코스 검증 환경(NVIDIA GeForce RTX 3070, 드라이버 595.79)에서 runClient 실행 시 F3 화면에 OpenGL 3.3.0 NVIDIA 595.79로 표시됩니다. GPU/드라이버 환경에 따라 Vulkan이 활성화될 수 있으나, 이 챕터는 NeoForge 렌더링 추상화 내부 구조(Vulkan 백엔드 포함)에 대한 심화 자료입니다.

왜 Vulkan인가

Minecraft 26.1.2의 렌더링 스택이 현대화됐습니다. 기본 백엔드는 OpenGL이지만, NeoForge는 내부적으로 Vulkan 개념(파이프라인, 커맨드 버퍼 등)을 참고한 추상화 레이어를 제공합니다. 모더 입장에서 이 변화가 중요한 이유는 단순합니다. 커스텀 렌더러나 셰이더를 작성할 때 Vulkan 파이프라인의 흐름을 이해해야 디버깅이 가능합니다.

NeoForge는 추상화 레이어를 제공하므로 Vulkan API를 직접 호출할 일은 없습니다. 하지만 내부에서 어떤 일이 벌어지는지 알아야 RenderType, ShaderProgram, VertexBuffer 같은 API를 올바르게 사용할 수 있습니다.

⚠️ 직접 VK 호출 금지

// ❌ LWJGL Vulkan API 직접 호출
VkCommandBuffer cmd = VK10.vkAllocateCommandBuffers(...);
 
// ✅ NeoForge 추상화 사용
RenderSystem.setShader(GameRenderer::getPositionTexShader);

NeoForge 26의 Vulkan 호출은 백엔드가 처리합니다. 모드는 추상 API만 사용하세요.


Vulkan 백엔드 구조

NeoForge 26의 렌더링 스택은 네 개의 핵심 객체로 구성됩니다.

VkInstance

Vulkan 컨텍스트 자체입니다. 게임 시작 시 한 번 초기화되고, 종료 시 해제됩니다. 어떤 Vulkan 확장을 사용할지, 검증 레이어를 활성화할지 여기서 결정합니다.

모더가 직접 건드릴 일은 없지만, 개발 빌드에서 Vulkan 검증 레이어가 활성화되면 콘솔에 상세한 오류 메시지가 출력됩니다. 렌더링 버그를 추적할 때 이 메시지가 핵심 단서가 됩니다.

VkDevice

논리적 GPU를 나타냅니다. 물리적 GPU(그래픽 카드)를 추상화한 드라이버 인터페이스입니다. 실제 렌더링 명령은 모두 이 객체를 통해 전달됩니다.

NeoForge는 게임 시작 시 사용 가능한 GPU 중 가장 적합한 것을 자동으로 선택합니다. 멀티 GPU 환경에서 특정 GPU를 강제하려면 JVM 인수로 설정할 수 있습니다.

VkSwapChain

화면에 표시할 프레임 버퍼 집합입니다. 이중 버퍼링(더블 버퍼링)이나 삼중 버퍼링을 통해 화면 찢김(tearing) 없이 부드러운 렌더링을 구현합니다.

프레임 N 렌더링 중 → 프레임 N-1 화면 표시
프레임 N 완료 → 스왑 → 프레임 N 화면 표시

V-Sync 설정이 여기서 작동합니다. V-Sync 켜면 스왑 타이밍이 모니터 주사율에 동기화됩니다.

VkCommandBuffer

렌더 명령을 기록하는 버퍼입니다. CPU가 "이 메시를 이 셰이더로 그려라"는 명령을 미리 기록해두면, GPU가 나중에 일괄 실행합니다.

이 구조 덕분에 CPU와 GPU가 병렬로 작동할 수 있습니다. CPU는 다음 프레임 명령을 기록하는 동안 GPU는 이전 프레임을 렌더링합니다.


전체 파이프라인 흐름

다이어그램 렌더링 중…

Java 코드에서 PoseStack과 VertexConsumer로 정점 데이터를 쌓으면, NeoForge가 이를 Vulkan 명령으로 변환해 VkCommandBuffer에 기록합니다. 셰이더는 별도로 GLSL에서 SPIR-V로 컴파일되어 VkShaderModule로 로드됩니다. 두 경로가 VkCommandBuffer에서 합쳐져 GPU로 제출됩니다.


RenderType의 Vulkan 매핑

RenderType은 NeoForge가 제공하는 렌더링 상태 묶음입니다. 각 RenderType은 내부적으로 특정 Vulkan 파이프라인 설정에 매핑됩니다.

solid (불투명 파이프라인)

RenderType.solid()

알파 블렌딩 없음. 깊이 테스트 활성화. 가장 빠른 렌더링 경로입니다. 일반 블록 대부분이 여기 해당합니다.

Vulkan 내부에서는 VkPipelineColorBlendAttachmentState의 blendEnable이 VK_FALSE로 설정됩니다.

translucent (알파 블렌딩 파이프라인)

RenderType.translucent()

알파 블렌딩 활성화. 물, 유리, 반투명 블록에 사용합니다. 렌더링 순서가 중요합니다. 뒤에서 앞으로 정렬해야 올바른 투명도가 나옵니다.

// 블렌딩 수식: src * srcAlpha + dst * (1 - srcAlpha)
// Vulkan: VK_BLEND_FACTOR_SRC_ALPHA + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA

cutout (알파 임계값 파이프라인)

RenderType.cutout()

알파값이 임계값(기본 0.1) 미만인 픽셀을 완전히 버립니다(discard). 나뭇잎, 잔디, 꽃 같은 식물 블록에 사용합니다. 블렌딩보다 빠르고, 깊이 정렬 문제도 없습니다.

GLSL 셰이더에서는 이렇게 구현됩니다.

if (color.a < 0.1) discard;

GLSL에서 SPIR-V 컴파일

Vulkan은 GLSL을 직접 이해하지 못합니다. 중간 표현인 SPIR-V(Standard Portable Intermediate Representation)로 컴파일해야 합니다. NeoForge가 빌드 시 이 과정을 자동으로 처리합니다.

파일 구조

assets/<modId>/shaders/core/
├── sparkle.json        # 셰이더 메타데이터
├── sparkle.vsh         # 버텍스 셰이더 (GLSL)
└── sparkle.fsh         # 프래그먼트 셰이더 (GLSL)

메타데이터 파일 (JSON)

{
  "blend": {
    "func": "add",
    "srcrgb": "srcalpha",
    "dstrgb": "1-srcalpha"
  },
  "vertex": "sparkle",
  "fragment": "sparkle",
  "attributes": ["Position", "UV0"],
  "samplers": [
    { "name": "Sampler0" }
  ],
  "uniforms": [
    { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] },
    { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] },
    { "name": "GameTime", "type": "float", "count": 1, "values": [0] }
  ]
}

버텍스 셰이더 (.vsh)

#version 150
 
in vec3 Position;
in vec2 UV0;
 
out vec2 texCoord;
 
uniform mat4 ModelViewMat;
uniform mat4 ProjMat;
 
void main() {
    gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
    texCoord = UV0;
}

버텍스 셰이더는 각 정점의 위치를 클립 공간으로 변환합니다. ModelViewMat은 월드 변환, ProjMat은 원근 투영입니다.

프래그먼트 셰이더 (.fsh)

#version 150
 
in vec2 texCoord;
 
out vec4 fragColor;
 
uniform float GameTime;
 
void main() {
    // 시간에 따라 RGB 채널이 사인파로 변화
    float r = sin(GameTime * 3.14159) * 0.5 + 0.5;
    float g = cos(GameTime * 3.14159) * 0.5 + 0.5;
    float b = 0.5;
 
    fragColor = vec4(r, g, b, 1.0);
}

GameTime은 NeoForge가 자동으로 주입하는 유니폼입니다. 틱마다 증가하므로 시간 기반 애니메이션에 활용할 수 있습니다.


커스텀 셰이더 등록

Java 코드에서 셰이더를 로드하고 사용하는 방법입니다.

// 셰이더 레퍼런스 선언
public static ShaderProgram SPARKLE_SHADER;
 
// 클라이언트 초기화 이벤트에서 등록
@SubscribeEvent
public static void onRegisterShaders(RegisterShadersEvent event) {
    event.registerShader(
        new ShaderProgram(
            Identifier.fromNamespaceAndPath("examplemod", "sparkle"),
            DefaultVertexFormat.POSITION_TEX
        ),
        shader -> SPARKLE_SHADER = shader
    );
}
 
// 렌더링 시 사용
RenderSystem.setShader(() -> SPARKLE_SHADER);

RegisterShadersEvent는 클라이언트 이벤트 버스에서만 발생합니다. 서버 사이드에서 호출하면 크래시가 납니다.

// 반드시 클라이언트 이벤트 버스에 등록
NeoForge.EVENT_BUS.addListener(
    EventPriority.NORMAL,
    false,
    RegisterShadersEvent.class,
    MyClientEvents::onRegisterShaders
);

셰이더 파일 구조

셰이더 파일은 assets/examplemod/shaders/core/ 아래에 같은 이름의 .json, .vsh, .fsh 세 파일이 함께 있어야 NeoForge가 인식합니다. 위 등록 코드의 sparkle 셰이더라면 다음과 같습니다.

src/main/resources/assets/examplemod/shaders/core/
├── sparkle.json   # 파이프라인 메타데이터 (VertexFormat·uniform·샘플러 선언)
├── sparkle.vsh    # 버텍스 셰이더 (GLSL)
└── sparkle.fsh    # 프래그먼트 셰이더 (GLSL)

셰이더 메타데이터 검증

JSON 메타데이터에서 uniforms 배열의 타입과 count가 GLSL 선언과 일치해야 합니다. 불일치하면 빌드는 통과하지만 런타임에 셰이더 로드 실패 오류가 발생합니다.


디버깅 팁

셰이더 컴파일 오류: 게임 로그에 SPIR-V compilation failed 메시지가 출력됩니다. 줄 번호와 오류 내용이 함께 나오므로 GLSL 문법 오류를 빠르게 찾을 수 있습니다.

유니폼 불일치: Uniform 'GameTime' not found in shader 같은 경고가 나오면 JSON 메타데이터와 GLSL 선언을 비교하세요.

렌더링 순서 문제: translucent 오브젝트가 잘못 겹쳐 보이면 RenderType.translucent()를 사용하고 있는지, 렌더링 레이어가 올바른지 확인하세요.

검증 레이어 활성화: 개발 중에는 JVM 인수에 -Dvulkan.validation=true를 추가하면 Vulkan 검증 레이어가 활성화됩니다. 성능은 떨어지지만 오류 메시지가 훨씬 상세해집니다.


정리

개념역할
VkInstanceVulkan 컨텍스트, 게임 시작 시 초기화
VkDevice논리적 GPU, 드라이버 추상화
VkSwapChain프레임 버퍼, 이중/삼중 버퍼링
VkCommandBuffer렌더 명령 큐, CPU/GPU 병렬화
RenderType.solid()불투명 파이프라인, 가장 빠름
RenderType.translucent()알파 블렌딩, 순서 정렬 필요
RenderType.cutout()알파 임계값, 식물 블록용
.vsh / .fshGLSL 셰이더 소스
.json셰이더 메타데이터 (유니폼, 어트리뷰트)
SPIR-VVulkan이 실제로 실행하는 중간 표현

NeoForge 추상화 덕분에 Vulkan의 복잡한 초기화 코드를 직접 작성할 필요는 없습니다. 하지만 파이프라인 흐름을 이해하면 렌더링 버그를 훨씬 빠르게 추적할 수 있습니다.

자주 발생하는 문제와 해결법

NeoForge 26.1.2 모딩 개발 중 자주 마주치는 12가지 오류의 증상, 원인, 검증된 해결 방법을 정리합니다.

On this page

왜 Vulkan인가Vulkan 백엔드 구조VkInstanceVkDeviceVkSwapChainVkCommandBuffer전체 파이프라인 흐름RenderType의 Vulkan 매핑solid (불투명 파이프라인)translucent (알파 블렌딩 파이프라인)cutout (알파 임계값 파이프라인)GLSL에서 SPIR-V 컴파일파일 구조메타데이터 파일 (JSON)버텍스 셰이더 (.vsh)프래그먼트 셰이더 (.fsh)커스텀 셰이더 등록셰이더 파일 구조셰이더 메타데이터 검증디버깅 팁정리
NeoForge 26.1 Docs

NeoForge 26.1 모딩 개발 문서 사이트

GitHubDiscord

문서

  • 문서
  • 노트

GitHub

  • GitHub
  • Discord

© 2026 NeoForge 26.1 Docs. 콘텐츠는 MIT 라이선스로 제공됩니다.

Built with Next.js · Tailwind CSS · shadcn/ui