NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
BlockEntity 기초 — CounterBlockEntityBlockEntityRenderer 구현Entity 등록과 AttributeSupplierEntityRenderer와 EntityModelAI Goal 시스템CustomPacketPayload — 클라이언트/서버 패킷 통신AttachmentTypes — 엔티티·블록에 데이터 부착WorldGen Feature 기초 — 광물 생성
고급 시스템

BlockEntityRenderer 구현

NeoForge 26 Vulkan 백엔드 기준 BlockEntityRenderer를 작성해 CounterBlockEntity 위에 회전하는 아이콘을 렌더링합니다. PoseStack 변환·partialTick 보간·안전한 MultiBufferSource 사용법을 다룹니다.

BlockEntity를 저장 레이어에서 끌어내어 화면에 표현하는 건 BlockEntityRenderer의 역할입니다. 이 챕터에서는 앞서 만든 CounterBlockEntity 위에 다이아몬드 아이콘이 Y축으로 회전하는 렌더러를 작성합니다.

🎮 NeoForge 26 렌더링 백엔드

NeoForge 26의 렌더 백엔드는 OpenGL 기반이며, Vulkan은 선택적 옵션입니다. 본 코스 검증 환경(NVIDIA GeForce RTX 3070, 드라이버 595.79)에서는 F3에 OpenGL 3.3.0 NVIDIA 595.79로 표시됩니다.

  • BlockEntityRenderer의 render 메소드는 백엔드 추상화 위에서 동작합니다.
  • PoseStack, MultiBufferSource 등은 백엔드 독립적 API입니다.
  • 직접 GL 호출(GL11.glRotate 등) → 백엔드 업데이트 시 미정의 동작 또는 크래시
  • 항상 NeoForge가 제공하는 추상화 API를 사용하세요.

CounterRenderer 클래스 작성

NeoForge 26.1.2에서 BlockEntityRenderer는 2-제네릭 인터페이스 BlockEntityRenderer<T extends BlockEntity, S extends BlockEntityRenderState>로 바뀌었습니다. 구 render(be, partialTick, poseStack, buffer, light, overlay) 단일 메소드는 삭제되고, 렌더 로직이 다음 3단계로 분리됩니다.

  • createRenderState() — 프레임마다 재사용할 빈 렌더 상태 스냅샷을 생성합니다.
  • extractRenderState(...) — BlockEntity 상태를 스냅샷으로 추출합니다(회전각·스케일·아이템 해석).
  • submit(...) — 스냅샷만으로 렌더 노드를 제출합니다(BlockEntity 직접 접근 금지).

아이템은 Minecraft.getInstance().getItemRenderer().renderStatic(...) 대신 ItemModelResolver로 ItemStackRenderState를 해석한 뒤 제출합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/client/renderer/CounterRenderer.java
package com.example.examplemod.client.renderer;
 
import com.example.examplemod.blockentity.CounterBlockEntity;
import com.example.examplemod.client.renderer.state.CounterRenderState;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
import net.minecraft.client.renderer.item.ItemModelResolver;
import net.minecraft.client.renderer.state.level.CameraRenderState;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.Nullable;
 
public class CounterRenderer implements BlockEntityRenderer<CounterBlockEntity, CounterRenderState> {
 
    private final ItemModelResolver itemModelResolver;
 
    public CounterRenderer(BlockEntityRendererProvider.Context context) {
        // context 에서 아이템 모델 해석기를 획득 (다이아몬드 아이콘 렌더에 사용)
        this.itemModelResolver = context.itemModelResolver();
    }
 
    // 프레임마다 재사용할 빈 RenderState 를 생성합니다. (필수)
    @Override
    public CounterRenderState createRenderState() {
        return new CounterRenderState();
    }
 
    // 블록엔티티 상태를 RenderState 스냅샷으로 추출합니다.
    @Override
    public void extractRenderState(
            CounterBlockEntity be,
            CounterRenderState state,
            float partialTick,
            Vec3 cameraPosition,
            ModelFeatureRenderer.@Nullable CrumblingOverlay breakProgress) {
        BlockEntityRenderer.super.extractRenderState(be, state, partialTick, cameraPosition, breakProgress);
 
        Level level = be.getLevel();
        // 시간 기반 Y축 회전 — partialTick 으로 보간해 끊김 없음
        long time = level != null ? level.getGameTime() : 0L;
        state.spinDegrees = (time + partialTick) * 2.0f;
 
        // 카운트가 많을수록 살짝 크게 (최대 1.5×)
        state.scale = 0.5f + Math.min(be.getCount() * 0.05f, 1.0f);
 
        // 다이아몬드 아이콘을 미리 해석 (Vulkan 호환 추상화 API)
        // owner 인자는 @Nullable ItemOwner — BlockEntity 는 ItemOwner 가 아니므로 null 전달.
        ItemStack stack = new ItemStack(Items.DIAMOND);
        this.itemModelResolver.updateForTopItem(
                state.item,
                stack,
                ItemDisplayContext.GROUND,
                level,
                null,
                0);
    }
 
    // 추출된 스냅샷만으로 렌더 노드를 제출합니다(블록엔티티 직접 접근 금지).
    @Override
    public void submit(
            CounterRenderState state,
            PoseStack poseStack,
            SubmitNodeCollector submitNodeCollector,
            CameraRenderState camera) {
        poseStack.pushPose();
 
        // 블록 위 중앙 위로 이동
        poseStack.translate(0.5, 1.5, 0.5);
 
        // 시간 기반 Y축 회전
        poseStack.mulPose(Axis.YP.rotationDegrees(state.spinDegrees));
 
        // 카운트 기반 스케일
        poseStack.scale(state.scale, state.scale, state.scale);
 
        // 아이템 렌더 노드 제출
        state.item.submit(
                poseStack,
                submitNodeCollector,
                state.lightCoords,
                OverlayTexture.NO_OVERLAY,
                0);
 
        poseStack.popPose();
    }
 
    @Override
    public boolean shouldRenderOffScreen() {
        return true; // 카메라 밖에서도 렌더
    }
}

CounterRenderState는 추출된 스냅샷을 담는 클래스입니다. BlockEntityRenderState를 상속하고, 회전각·스케일과 미리 해석한 아이템 상태를 보관합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/client/renderer/state/CounterRenderState.java
package com.example.examplemod.client.renderer.state;
 
import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
import net.minecraft.client.renderer.item.ItemStackRenderState;
 
public class CounterRenderState extends BlockEntityRenderState {
    /** 표시할 아이템(다이아몬드)의 사전 해석된 렌더 상태. */
    public final ItemStackRenderState item = new ItemStackRenderState();
    /** Y축 회전 각도(도). extractRenderState 에서 게임시간+부분틱으로 계산. */
    public float spinDegrees;
    /** 카운트 기반 스케일(0.5~1.5). */
    public float scale = 0.5f;
}

PoseStack 변환 이해하기

PoseStack은 행렬 스택입니다. OpenGL의 glPushMatrix/glPopMatrix에 대응하는 백엔드 독립적 추상화 API입니다.

메소드역할예시
pushPose()현재 변환 저장렌더 시작 시 호출
popPose()저장한 변환 복원렌더 종료 시 반드시 호출
translate(x, y, z)위치 이동블록 중심으로 이동
scale(x, y, z)크기 조절아이템을 작게 표시
mulPose(Quaternionf)회전 적용Axis.YP.rotationDegrees(angle)

push/pop은 반드시 쌍을 이루어야 합니다. pop 없이 끝나면 이후 렌더링 전체가 틀어집니다.

poseStack.pushPose();          // ← 여기서 변환 저장
poseStack.translate(0.5, 1.5, 0.5);
poseStack.mulPose(Axis.YP.rotationDegrees(state.spinDegrees));
// ... 렌더 노드 제출 ...
poseStack.popPose();           // ← 반드시 쌍으로

partialTick — 부드러운 애니메이션의 핵심

extractRenderState에 넘어오는 partialTick은 현재 게임 틱과 다음 틱 사이의 보간값입니다.

  • 범위: 0.0f ~ 1.0f
  • 0.0f = 현재 틱 시작
  • 1.0f = 다음 틱 직전

게임 틱은 초당 20회 갱신됩니다. partialTick 없이 getGameTime()만 쓰면 60FPS 환경에서도 초당 20번 뚝뚝 끊겨 보입니다.

// ❌ partialTick 미사용 — 60FPS에서도 20FPS처럼 보임
state.spinDegrees = be.getLevel().getGameTime() * 2.0f;
 
// ✅ partialTick으로 보간 — 부드러운 회전
long time = be.getLevel().getGameTime();
state.spinDegrees = (time + partialTick) * 2.0f;

렌더러 등록 — EntityRenderersEvent.RegisterRenderers

렌더러는 클라이언트 전용 이벤트에서 등록합니다. value = Dist.CLIENT로 제한합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/client/ClientEventHandler.java
import com.example.examplemod.ExampleMod;
import com.example.examplemod.client.renderer.CounterRenderer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
 
@EventBusSubscriber(modid = ExampleMod.MODID, value = Dist.CLIENT)
public class ClientEventHandler {
 
    @SubscribeEvent
    public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
        event.registerBlockEntityRenderer(
                ExampleMod.COUNTER_BE.get(),
                CounterRenderer::new);
    }
}

주의 사항:

  • MOD 버스 이벤트입니다. GAME 버스(NeoForge.EVENT_BUS)에 등록하면 절대 호출되지 않습니다.
  • 26.1.2에서는 @EventBusSubscriber의 bus 속성과 EventBusSubscriber.Bus enum이 삭제됐습니다. modid만 지정하면 MOD 버스에 등록되고, 클라이언트 전용이면 value = Dist.CLIENT만 추가합니다.
  • Dist.CLIENT 제한을 빠뜨리면 전용 서버에서 클라이언트 전용 클래스를 참조해 NoClassDefFoundError 크래시가 납니다.
  • COUNTER_BE는 05-advanced-systems/00-block-entity 챕터에서 DeferredRegister로 등록한 BlockEntityType 홀더입니다.

안티패턴 — GL11.* 직접 호출 금지

⚠️ GL11.* 직접 호출 금지

// ❌ 백엔드 무관 미정의 동작 (비호환 API)
@Override
public void render(...) {
    GL11.glRotatef(45, 0, 1, 0);   // OpenGL 직접 호출 금지
    GL11.glColor3f(1.0f, 0.5f, 0.0f); // 미정의 동작
}
 
// ✅ PoseStack + MultiBufferSource (백엔드 독립적)
poseStack.mulPose(Axis.YP.rotationDegrees(45));

NeoForge 26의 모든 렌더 코드는 백엔드 독립적 추상화 API만 사용합니다. GL11.* 직접 호출은 OpenGL 백엔드에서도 렌더링 오류 또는 크래시를 유발하므로 절대 사용하지 마세요.


성능 고려 사항

extractRenderState와 submit은 매 프레임 호출됩니다. 프레임 드롭을 막으려면:

규칙이유
추출/제출 안에서 new ItemStack(...) 반복 생성 금지GC 압박 → 프레임 드롭
추출/제출 안에서 NBT 직접 읽기 금지I/O 비용 → 프레임 드롭
무거운 계산은 BlockEntity 틱에서 캐시렌더는 읽기만
shouldRenderOffScreen으로 가시성 최적화필요한 경우에만 true
// ✅ 아이템 스택은 상수로 캐시해 매 프레임 new 를 피함
private static final ItemStack DIAMOND_STACK = new ItemStack(Items.DIAMOND);
 
@Override
public void extractRenderState(CounterBlockEntity be, CounterRenderState state, float partialTick,
                               Vec3 cameraPosition, ModelFeatureRenderer.@Nullable CrumblingOverlay breakProgress) {
    // ...
    this.itemModelResolver.updateForTopItem(
        state.item,
        DIAMOND_STACK, // 매 프레임 new 안 함
        ItemDisplayContext.GROUND,
        be.getLevel(),
        null,
        0);
}

정리

  • BlockEntityRenderer<T, S>를 구현하고 createRenderState·extractRenderState·submit 3단계로 렌더링합니다.
  • poseStack.pushPose() / poseStack.popPose()는 반드시 쌍으로 사용합니다.
  • partialTick으로 보간해 부드러운 애니메이션을 만들 수 있습니다.
  • 등록은 EntityRenderersEvent.RegisterRenderers — MOD 버스, CLIENT only.
  • GL11.* 직접 호출은 백엔드 무관 미정의 동작이므로 절대 사용하지 않습니다.

다음 챕터에서는 엔티티 렌더러(EntityRenderer)를 다룹니다.

BlockEntity 기초 — CounterBlockEntity

DeferredRegister로 BlockEntityType을 등록하고, BlockEntity 클래스에서 NBT 직렬화로 데이터를 저장·복원하는 전체 과정을 학습합니다.

Entity 등록과 AttributeSupplier

DeferredRegister로 커스텀 Mob 엔티티 타입을 등록하고, EntityAttributeCreationEvent로 AttributeSupplier를 안전하게 연결하는 방법을 배웁니다.

On this page

CounterRenderer 클래스 작성PoseStack 변환 이해하기partialTick — 부드러운 애니메이션의 핵심렌더러 등록 — EntityRenderersEvent.RegisterRenderers안티패턴 — GL11.* 직접 호출 금지성능 고려 사항정리
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