,T>`
337 | 原版未使用的地方,`forge`大多进行了`wrapper`,并传入一个`EmptyModelData.INSTANCE`
338 | ```java
339 | public interface IModelData
340 | {
341 | /**
342 | * Check if this data has a property, even if the value is {@code null}. Can be
343 | * used by code that intends to fill in data for a render pipeline, such as the
344 | * forge animation system.
345 | *
346 | * IMPORTANT: {@link #getData(ModelProperty)} can return {@code null}
347 | * even if this method returns {@code true}.
348 | *
349 | * @param prop The property to check for inclusion in this model data
350 | * @return {@code true} if this data has the given property, even if no value is present
351 | */
352 | boolean hasProperty(ModelProperty> prop);
353 |
354 | @Nullable
355 | T getData(ModelProperty prop);
356 |
357 | @Nullable
358 | T setData(ModelProperty prop, T data);
359 | }
360 | ```
361 | `CTM`这种链接纹理,依靠的就是这个机制
362 |
363 | 和他一样很重要的是`ModelDataManager`,可以理解为带拥有缓存,刷新等机制的`Map`
364 | 这是配合`BlockEntity`使用的
365 |
366 | `forge`通过`IForgeBlockEntity`为`BlockEntiy`添加了
367 | `requestModelDataUpdate()`
368 | `default @Nonnull IModelData getModelData()`
369 |
370 | ## Coloring
371 |
372 | 与物品一样,方块也可以染色,原理也一致,只不过接口变了而已
373 | ```java
374 | @OnlyIn(Dist.CLIENT)
375 | public interface BlockColor {
376 | int getColor(BlockState pState, @Nullable BlockAndTintGetter pLevel, @Nullable BlockPos pPos, int pTintIndex);
377 | }
378 | ```
379 |
380 | 但是方块与物品不同,不存在所谓的`BlockStack`,`BlockState`也无法支持任意颜色的方块存在
381 | 因此,我们需要另外的载体,来存储方块所需的数据
382 |
383 | ### example
384 |
385 | 给出的例子是,利用上一章制作的`Colorful Chalk`内的数据,在其右键我们的`Colorful Block`的时候
386 | 设置它所拥有的`BlockEntity`内的字段
387 | 当我们的方块所对应的`getColor`被调用时,从`BlockEntity`中获得颜色信息
388 |
389 |
390 |
391 | #### **ColorfulBlockByBlockEntity**
392 |
393 | ```kotlin-s
394 | class ColorfulBlockByBlockEntity : Block(Properties.of(Material.STONE)), EntityBlock {
395 |
396 | companion object {
397 | @JvmStatic
398 | fun registerColorHandle(event: ColorHandlerEvent.Block) {
399 | event.blockColors.register(
400 | { pState, pLevel, pPos, pTintIndex ->
401 | if (pLevel != null && pPos != null) {
402 | val blockEntity = pLevel.getBlockEntity(pPos) as? ColorfulBlockEntity
403 | //当方块被破坏后,由于需要渲染方块被破坏的粒子,此处会被调用
404 | //但是由于坐标所处的`BlockEntity`已经无法获取,所以会出错,只能使用`as?`
405 | return@register blockEntity?.color ?: 0xffffff
406 | }
407 | 0xffffff
408 | }, AllRegisters.colorfulBlockByBlockEntity.get()
409 | )
410 | }
411 | }
412 |
413 | override fun newBlockEntity(pPos: BlockPos, pState: BlockState): BlockEntity = ColorfulBlockEntity(pPos, pState)
414 |
415 | override fun use(
416 | pState: BlockState,
417 | pLevel: Level,
418 | pPos: BlockPos,
419 | pPlayer: Player,
420 | pHand: InteractionHand,
421 | pHit: BlockHitResult
422 | ): InteractionResult {
423 | if (pHand != InteractionHand.MAIN_HAND) {
424 | return super.use(pState, pLevel, pPos, pPlayer, pHand, pHit)
425 | }
426 | val itemStack = pPlayer.getItemInHand(pHand)
427 | val item = itemStack.item as? ColorfulChalk
428 | val blockEntity = pLevel.getBlockEntity(pPos) as ColorfulBlockEntity
429 | if (item != null) {
430 | if (!pLevel.isClientSide) {
431 | val color = item.getColor(itemStack)
432 | blockEntity.color = color
433 | return InteractionResult.SUCCESS
434 | }
435 | } else {
436 | val color = Integer.toHexString(blockEntity.color)
437 | if (pLevel.isClientSide) {
438 | pPlayer.sendMessage(TextComponent("client:entity color:$color"), Util.NIL_UUID)
439 | } else {
440 | pPlayer.sendMessage(TextComponent("server:entity color:$color"), Util.NIL_UUID)
441 | }
442 | }
443 | return super.use(pState, pLevel, pPos, pPlayer, pHand, pHit)
444 | }
445 | }
446 | ```
447 |
448 | ```java-s
449 | public class ColorfulBlockByBlockEntity extends Block implements EntityBlock {
450 |
451 | public ColorfulBlockByBlockEntity() {
452 | super(new Properties.of(Material.STONE));
453 | }
454 |
455 | public static void registerColorHandle(ColorHandlerEvent.Block event) {
456 | event.blockColors.register((pState, pLevel, pPos, pTintIndex) -> {
457 | if (pLevel != null && pPos != null) {
458 | final var blockEntity = ((ColorfulBlockEntity)(pLevel)).getBlockEntity(pPos);
459 | //当方块被破坏后,由于需要渲染方块被破坏的粒子,此处会被调用
460 | //但是由于坐标所处的`BlockEntity`已经无法获取,所以会出错,需要额外判断
461 | if(blockEntity != null){
462 | return blockEntity.color;
463 | }
464 | }
465 | return 0xffffff;
466 | }, AllRegisters.colorfulBlockByBlockEntity.get()
467 | );
468 | }
469 |
470 | @Override
471 | public BlockEntity newBlockEntity(BlockPos pPos,BlockState pState) {
472 | return new ColorfulBlockEntity(pPos, pState);
473 | }
474 |
475 | @Override
476 | public InteractionResult use(BlockState pState,Level pLevel,BlockPos pPos,Player pPlayer,InteractionHand pHand,BlockHitResult pHit){
477 | if (pHand != InteractionHand.MAIN_HAND) {
478 | return super.use(pState, pLevel, pPos, pPlayer, pHand, pHit);
479 | }
480 | final var itemStack = pPlayer.getItemInHand(pHand);
481 | if(itemStack.item instanceof ColorfulChalk item) {
482 | final var blockEntity = ((ColorfulBlockEntity)(pLevel)).getBlockEntity(pPos);
483 | if (item != null) {
484 | if (!pLevel.isClientSide) {
485 | final var color = item.getColor(itemStack);
486 | blockEntity.color = color;
487 | return InteractionResult.SUCCESS;
488 | }
489 | } else {
490 | final var color = Integer.toHexString(blockEntity.color);
491 | if (pLevel.isClientSide) {
492 | pPlayer.sendMessage(TextComponent("client:entity color:$color"), Util.NIL_UUID);
493 | } else {
494 | pPlayer.sendMessage(TextComponent("server:entity color:$color"), Util.NIL_UUID);
495 | }
496 | }
497 | }
498 | return super.use(pState, pLevel, pPos, pPlayer, pHand, pHit);
499 | }
500 | }
501 | ```
502 |
503 | #### **ColorfulBlockEntity**
504 |
505 | ```kotlin-s
506 | class ColorfulBlockEntity(pos: BlockPos, state: BlockState) :
507 | BlockEntity(AllRegisters.colorfulBlockEntityType.get(), pos, state) {
508 | var color: Int = 0xffffff
509 | set(value) = if (value in 0..0xffffff) {
510 | if (value != field) {
511 | field = value
512 | if (level?.isClientSide == true) {
513 | Minecraft.getInstance().levelRenderer.setBlocksDirty(
514 | worldPosition.x, worldPosition.y, worldPosition.z, worldPosition.x, worldPosition.y, worldPosition.z
515 | )
516 | } else {
517 | level?.sendBlockUpdated(worldPosition, blockState, blockState, 1)
518 | }
519 | Unit
520 | } else {
521 | }
522 | } else {
523 | throw AssertionError("color:${Integer.toHexString(value)} not range in 0 to 0xffffff")
524 | }
525 |
526 | override fun getUpdatePacket(): Packet? =
527 | ClientboundBlockEntityDataPacket.create(this)
528 |
529 | override fun getUpdateTag(): CompoundTag = CompoundTag().apply { putInt("color", color) }
530 |
531 | override fun handleUpdateTag(tag: CompoundTag?) {
532 | tag?.getInt("color")?.apply { color = this }
533 | }
534 |
535 | override fun onDataPacket(net: Connection?, pkt: ClientboundBlockEntityDataPacket?) {
536 | pkt?.tag?.getInt("color")?.apply { color = this }
537 | }
538 | }
539 | ```
540 |
541 | ```java-s
542 | class ColorfulBlockEntity extends BlockEntity{
543 |
544 | public ColorfulBlockEntity(BlockPos pos, BlockState state){
545 | super(AllRegisters.colorfulBlockEntityType.get(), pos, state);
546 | }
547 |
548 | private int color = 0xffffff;
549 |
550 | public int getColor() {
551 | return color;
552 | }
553 |
554 | public void setColor(int color) {
555 | if(color < 0 && color > 0xffffff)
556 | throw new AssertionError("color:" + Integer.toHexString(value) + "} not range in 0 to 0xffffff");
557 | if(this.color != color) {
558 | this.color = color;
559 | if(level == null) {
560 | return;
561 | }
562 | if(level.isClientSide) {
563 | Minecraft.getInstance().levelRenderer.setBlocksDirty(
564 | worldPosition.x, worldPosition.y, worldPosition.z, worldPosition.x, worldPosition.y, worldPosition.z
565 | );
566 | }else {
567 | level.sendBlockUpdated(worldPosition, blockState, blockState, 1);
568 | }
569 | }
570 | }
571 |
572 | @Override
573 | public Packet getUpdatePacket() {
574 | return ClientboundBlockEntityDataPacket.create(this);
575 | }
576 |
577 | @Override
578 | public CompoundTag getUpdateTag() {
579 | final var tag = CompoundTag();
580 | tag.putInt("color",color);
581 | return tag;
582 | }
583 |
584 | @Override
585 | public void handleUpdateTag(CompoundTag tag) {
586 | this.color = tag.getInt("color"); //可能需要考虑tag不存在的情况
587 | }
588 |
589 | @Override
590 | public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt){
591 | final var color = pkt.tag.getInt("color"); //两个参数都可能为空
592 | this.color = color;
593 | }
594 | }
595 | ```
596 |
597 |
598 | #### **Register**
599 |
600 | ```kotlin-s
601 | private val BLOCK = DeferredRegister.create(ForgeRegistries.BLOCKS, Cobalt.MOD_ID)
602 | private val BLOCKENTITY_TYPE = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, Cobalt.MOD_ID)
603 |
604 | val colorfulBlockByBlockEntity = BLOCK.register("colorful_chalk_by_block_entity") { ColorfulBlockByBlockEntity() }
605 |
606 | val colorfulBlockByBlockEntityItem = ITEM.register("colorful_chalk_by_block_entity") {
607 | BlockItem(colorfulBlockByBlockEntity.get(), Item.Properties().tab(creativeTab))
608 | }
609 |
610 | val colorfulBlockEntityType = BLOCKENTITY_TYPE.register("colorful_block") {
611 | BlockEntityType.Builder.of({ pos, state ->
612 | ColorfulBlockEntity(pos, state)
613 | }, colorfulBlockByBlockEntity.get()).build(Util.fetchChoiceType(References.BLOCK_ENTITY, "colorful_block"))
614 | }
615 | ```
616 |
617 | ```java-s
618 | DeferredRegister BLOCK = DeferredRegister.create(ForgeRegistries.BLOCKS, Cobalt.MOD_ID);
619 | DeferredRegister> = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, Cobalt.MOD_ID);
620 |
621 | RegistryObject colorfulBlockByBlockEntity
622 | = BLOCK.register("colorful_chalk_by_block_entity",ColorfulBlockByBlockEntity::new);
623 |
624 | RegistryObject colorfulBlockByBlockEntityItem = ITEM.register("colorful_chalk_by_block_entity",
625 | () -> new BlockItem(colorfulBlockByBlockEntity.get(), new Item.Properties().tab(creativeTab))
626 | );
627 |
628 | RegistryObject> colorfulBlockEntityType
629 | = BLOCKENTITY_TYPE.register("colorful_block") , () -> BlockEntityType.Builder.of(ColorfulBlockEntity::new,
630 | colorfulBlockByBlockEntity.get()).build(Util.fetchChoiceType(References.BLOCK_ENTITY, "colorful_block")));
631 | ```
632 |
633 | #### **Block Json Model**
634 |
635 | ```json
636 | {
637 | "parent": "block/block",
638 | "textures": {
639 | "down":"minecraft:block/iron_block",
640 | "up":"minecraft:block/iron_block",
641 | "north":"minecraft:block/iron_block",
642 | "south":"minecraft:block/iron_block",
643 | "west":"minecraft:block/iron_block",
644 | "east":"minecraft:block/iron_block"
645 | },
646 | "elements": [
647 | { "from": [ 0, 0, 0 ],
648 | "to": [ 16, 16, 16 ],
649 | "faces": {
650 | "down": { "texture": "#down", "cullface": "down" ,"tintindex": 0 },
651 | "up": { "texture": "#up", "cullface": "up" ,"tintindex": 0 },
652 | "north": { "texture": "#north", "cullface": "north" ,"tintindex": 0 },
653 | "south": { "texture": "#south", "cullface": "south" ,"tintindex": 0 },
654 | "west": { "texture": "#west", "cullface": "west" ,"tintindex": 0 },
655 | "east": { "texture": "#east", "cullface": "east","tintindex": 0 }
656 | }
657 | }
658 | ]
659 | }
660 | ```
661 |
662 |
663 |
664 |
665 | >[!note]
666 | > 这里的JSON模型相当于原版的贴方块
667 | > 一定要使得**_tintindex_**不为默认值-1
668 |
669 |
670 | >[!warning]
671 | > 注意我们调用的`LevelRender#setBlocksDirty`
672 | > 否则方块的数据不会**_刷新_**
673 | > 会被阻拦在`LevelRender#compileChunks`内的`ChunkRenderDispatcher.RenderChunk#isDirty`
674 | > 详见[RenderChunk的Cache问题](render/misc.md#renderchunk)
675 |
676 | 效果如下
677 | 
678 |
679 |
680 | ## BlockEntityRender
681 |
682 | 游戏中,总有那么些东西,看上去不像是普通的模型能过做到的,比如附魔台,那本书的动画,各个mod的机器的动画,透明箱子渲染的其拥有的物品
683 | 这一般是用`BlockEntityRender`实现的
684 |
685 | 在实现`BlockEntityRender#render`前,我们需要一系列操作
686 | `BlockEntityRender`需要`BlockEntity`配合使用
687 |
688 | 注册方块,方块需要实现`EntityBlock`接口
689 | 实现抽象方法`newBlockEntity`,返回`BlockEntiy`实例
690 | 需要注册`BlockEntityType`,并与你的方块进行绑定
691 | 订阅事件`EntityRenderersEvent.RegisterRenderers`,调用事件内`registerBlockEntityRenderer`进行绑定
692 |
693 | >[!note]
694 | > 为什么我渲染出的物品都黑漆漆的?
695 | > 查看你的`pPackedLight`参数,如果一直是0,可以通过给方块添加`noOcclusion`或者非完整`VoxelShape`解决
696 | > **_如做修改,请尝试更新光照,敲掉重放或者放一个光源都可以_**
697 |
698 | 而最为重要的`render`函数的实现,则会在单独的[一章](render/renderInLevel.md)中单独介绍
--------------------------------------------------------------------------------
/render/coordinateSystem.md:
--------------------------------------------------------------------------------
1 | # CoordinateSystem
2 |
3 | ---
4 |
5 | >[!note]
6 | > 本章所阐述的坐标系统,指代在渲染时使用的坐标系统
7 | > 并非为用于实体方位的坐标系
8 |
9 | ## Review
10 |
11 | 在介绍mc的渲染坐标系前,首先请了解`MVP`变换,你可以在这里找到它们的介绍
12 |
13 | * [LearnOpenGL](https://learnopengl.com/Getting-started/Coordinate-Systems)
14 | * [LearnOpenGLCN](https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/)
15 |
16 | 
17 |
18 | 1. `Local Space(本地/局部空间)`,可理解为建模时使用的坐标
19 | 2. `World Space(世界空间)`,在游戏内,一个模型可出现在多处,此时采用的坐标
20 | 3. `View Space(视口空间)`,摄像机所观察到的空间
21 | 4. `Clip Space(剪切空间)`,具有透视投影/正交投影性质的空间
22 | 5. `Normalized Device Coordinate(NDC Space)`,各分量除以w,所有坐标都处于-1到1内,左下为[-1,1],右上为[1,1]
23 | 6. `Screen Space`,`glViewPort`所定义的空间
24 |
25 | 箭头上标注的矩阵即为将上一个空间变换到下一个空间的矩阵
26 |
27 | >[!note]
28 | > 前三个空间并非是OpenGL定义的
29 | > `Vertex Shader`的内置变量`gl_Position`输出的为`Clip Space`
30 | > `Fragment Shader`的内置变量`gl_FragCoord`较为复杂,首先来自`NDC空间`
31 | > xy分量受到[Fragment shader coordinate origin](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)#Fragment_shader_coordinate_origin)和glViewPort的影响
32 | > z分量受到`glDepthRange`影响
33 | > w分量为`Clip Space` w分量的倒数
34 |
35 | 对于mc游戏内使用的坐标,我们在本文章中称之为方块坐标
36 |
37 | ## In World
38 |
39 | 为了将坐标映射到`Clip Space`,mc仍采用上述流程,只不过模型矩阵与视图矩阵合并为同一个矩阵,后文称之为`MV矩阵`
40 | 对于投影矩阵,mc采用的是`透视投影矩阵`,可以在`GameRender#getProjectionMatrix`找到具体代码
41 | 且该矩阵始终不为单位矩阵
42 |
43 | 设置中的bobView是通过投影矩阵实现的
44 |
45 | ### Render Chunk
46 |
47 | >[!attention]
48 | > 在批量渲染区块的过程中,MV矩阵不为单位矩阵
49 | > 渲染的坐标中心为摄像机坐标中心,即以摄像机坐标为原点
50 |
51 | 以`rendertype_solid.vsh`为例,近保留坐标变换内容
52 |
53 | ```glsl
54 | in vec3 Position;
55 | in vec3 Normal;
56 |
57 | uniform sampler2D Sampler2;
58 |
59 | uniform mat4 ModelViewMat;
60 | uniform mat4 ProjMat;
61 | uniform vec3 ChunkOffset;
62 |
63 | void main() {
64 | vec3 pos = Position + ChunkOffset;
65 | gl_Position = ProjMat * ModelViewMat * vec4(pos, 1.0);
66 |
67 | vertexDistance = fog_distance(ModelViewMat, pos, FogShape);
68 | normal = ProjMat * ModelViewMat * vec4(Normal, 0.0);
69 | }
70 | ```
71 |
72 | `Postion`为BlockPosition & 0xFF,即坐标数据的后四位,即表示自身相对于自身chunk原点所在的位置
73 | `ChunkOffset`是批量渲染区块时一个特有的uniform变量,其值为`RenderChunkDispatcher$RenderChunk.origin - camPos`
74 | 每个`RenderChunkDispatcher$RenderChunk`代表一个16x16x16范围
75 | 根据上文所述的以摄像机中心为原点,在此我们可以通过简单的`pos + camPos`得到该顶点的方块坐标
76 | `Camera.getPosition()`可直接拿到摄像机的方块坐标
77 | 此时的MV矩阵仅受到摄像机方向的影响
78 |
79 | 对于流体渲染也是同理,因此没有任何modder会直接调用`LiquidBlockRenderer`来渲染流体
80 | 因为原版使用的流体渲染会直接抹除坐标的部分信息,而在非chunk渲染种,被抹除的信息不会被正确补全/修正
81 |
82 | ### Other
83 |
84 | 对于非区块渲染,`MV矩阵`始终为单位矩阵,无法提供任何的坐标信息,且不存在`ChunkOffset`的等价表示
85 | 因此mc使用`PoseStack`,对提交的顶点在cpu内进行`逐顶点预乘`
86 |
87 | 结合上文的描述,以摄像机为原点。因此我们常常能在`RenderLevelLastEvent/RenderLevelStageEvent`见到这种操作
88 | ```java
89 | var poseStack = new PoseStack(); //仅表示目前仍然是单位矩阵
90 | poseStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
91 | poseStack.translate(blockPos.x,blockPos.y,blockPos.z);
92 | ```
93 |
94 | ## GUI
95 |
96 | 在gui体系内,mc的渲染坐标系便较为统一,在此我们还会介绍`SDF`在此的使用并让其支持原版的`PoseStack`系统
97 |
98 | 首先我们要介绍的是mc引入的一个叫做`GuiScale`的变量,可以通过`Minecraft.getInstance().window.guiScale`拿到
99 | 同时在window内有三对字段储存了窗口的宽高
100 |
101 | | Name | Meaning |
102 | |----------------------|----------------------------------|
103 | | width | actual length, measured in pixel |
104 | | height | actual length, measured in pixel |
105 | | framebufferWidth | actual length, measured in pixel |
106 | | framebufferHeight | actual length, measured in pixel |
107 | | guiScaledWidth | length / guiScale |
108 | | guiScaledHeight | length / guiScale |
109 | | getWidth() | actual length |
110 | | getHeight() | actual length |
111 | | getGuiScaledWidth() | length / guiScale |
112 | | getGuiScaledHeight() | length / guiScale |
113 |
114 | 对于gui内渲染,mc重新定义了一个空间,在本文,我们称之为`gui空间`
115 | 该空间以左上角为原点,向右为x轴正方向,向下为y轴正方向,即左上角为(0,0),右下角为(guiScaledWidth,guiScaledHeight)
116 | 同时,我们为了讲述方便,定义一个`未规范化屏幕空间`,左上角为(0,0),右下角为(actual width,actual height)
117 | 并且定义`屏幕空间`为左下角(-1,-1),右上角(1,1),中心点为(0,0)
118 |
119 | 在gui内,mc使用的投影矩阵为正交投影矩阵,定义空间与gui空间一致,且近平面为1000,远平面为3000
120 | 在forge环境下,有提供`guiLayers`,每有一层的guiLayers,在3000的基础上增加2000
121 |
122 | 原版定义的所有部件,使用的所有空间都为gui空间,包括x,y,width,height
123 | 在部件内拿到鼠标对于的坐标空间也是gui空间
124 | 可以在GameRender#render内找到类似代码
125 |
126 | ```java
127 | var mouseX = (mouseHandler.xpos() * getWindow().getGuiScaledWidth() / getWindow().getScreenWidth());
128 | var mouseY = (mouseHandler.ypos() * getWindow().getGuiScaledHeight() / getWindow().getScreenHeight());
129 | ```
130 |
131 | 如果想在gui中启用`scissor test(剪切测试)`,请通过`guiScale`进行修正,类似这样
132 | ```java
133 | RenderSystem.enableScissor(
134 | (int) (x * scale),
135 | (int) (minecraft.getWindow().getHeight() - (y + height) * scale),
136 | (int) (width * scale),
137 | (int) (height * scale));
138 | ```
139 |
140 | ## SDF
141 |
142 | 可以直接传输二维的屏幕坐标空间,给出如下screen.vsh
143 | 可以通过修改是否定义宏`SUPPORT_POSE_STACK`来开关对其支持
144 | ```glsl
145 | #version 150
146 |
147 | #define SUPPORT_POSE_STACK
148 |
149 | in vec3 Position;// base on normalized screen postion
150 |
151 | out vec2 screenPos;// before modified by gui scale
152 | out vec2 guiPos;// modified by gui scale
153 | out vec2 uv;// normalized into [-1,1]
154 |
155 | uniform float GuiScale;
156 | uniform vec2 ScreenSize;
157 |
158 | #ifdef SUPPORT_POSE_STACK
159 | uniform mat4 PoseStack;
160 | uniform mat4 ProjMat;
161 | #endif
162 |
163 | void main() {
164 | gl_Position = vec4(Position.xy, 0.0, 1.0);
165 | vec2 normalizedPos = gl_Position.xy * 0.5 + 0.5;
166 | screenPos = ScreenSize * vec2(normalizedPos.x, 1-normalizedPos.y);
167 | guiPos = screenPos / GuiScale;
168 | uv = gl_Position.xy;
169 | #ifdef SUPPORT_POSE_STACK
170 | gl_Position = vec4(((ProjMat) * PoseStack * vec4(guiPos, 0.0, 1.0)).xy, 0.0, 1.0);
171 | #endif
172 | }
173 | ```
174 | 仅需传输四个顶点坐标即可,类似于
175 | ```java
176 | var builder = Tesselator.getInstance().getBuilder();
177 |
178 | builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION);
179 | builder.vertex(-1.0, 1.0, 0.0).endVertex();
180 | builder.vertex(-1.0, -1.0, 0.0).endVertex();
181 | builder.vertex(1.0, -1.0, 0.0).endVertex();
182 | builder.vertex(1.0, 1.0, 0.0).endVertex();
183 | builder.end();
184 |
185 | BufferUploader._endInternal(builder);//for <119
186 | BufferUploader.draw(bufferbuilder.end());//for >= 119 //changed at 22w16a
187 | ```
188 |
189 | Example:
190 | 
191 |
192 | ## Utility
193 |
194 | - [实用顶点处理函数](https://github.com/onnowhere/core_shaders/blob/master/.shader_utils/vsh_util.glsl)
195 |
--------------------------------------------------------------------------------
/render/itemModel.md:
--------------------------------------------------------------------------------
1 | # ItemModel物品模型
2 |
3 | ---
4 |
5 | ## ModelResourceLocation
6 |
7 | 在正式开始前,我们需要介绍一下`ModelResourceLocation`,继承自`ResourceLocation`,与之相比多了一个名为`variant`的字段
8 | 与`ResourceLocation`一样,同样拥有`namespace`和`path`字段
9 | 在这里,前者表示模型所处的`命名空间`,即`modID`,而后者则对应所属物品/方块的`registryName`
10 | 而`variant`对于物品,则为`inventory`
11 | 对于方块,则描述了其`BlockState`,若没有BlockState,则为空字符串
12 | `toString`方法为`:#`
13 |
14 | 想要拿到`BlockState`对应的`ModelResourceLocation`可以通过`BlockModelShaper#stateToModelLocation`
15 | 物品则可通过`ModelResourceLocation(- .registryName, "inventory")`
16 |
17 | ## Normal Model
18 |
19 | 具体可以参见[wiki](https://minecraft.fandom.com/zh/wiki/%E6%A8%A1%E5%9E%8B#.E7.89.A9.E5.93.81.E6.A8.A1.E5.9E.8B)
20 |
21 | ### Use Block Model
22 |
23 | **_方块对应的物品默认是没有材质的_**
24 | 如果你的物品想要使用方块的模型,例如`BlockItem`的物品模型
25 | 可以直接让`parent`指向方块对应的模型,格式:`:block/`,`<>`内为根据实际填写的字段
26 |
27 | ### Layer Model
28 |
29 | mc自带的一种生成模型的方式,一多层的`Layer`叠加,为物品生成模型
30 | 可以查看`forge`对原版的扩展,在`ItemLayerModel`内,扩展了原版仅支持4个`layer`至无限
31 |
32 | ### 3D Json Model
33 |
34 | 资源文件结构
35 |
36 | ```treeview
37 | resources/
38 | `-- assets/
39 | `-- cobalt/
40 | |-- models/
41 | | `-- item/
42 | | |-- weather_indicator.json
43 | | |-- weather_indicator_empty.json
44 | `-- textures/
45 | `-- weather_indicator/
46 | `-- weather_indicator.png
47 | ```
48 |
49 | 注册物品
50 | ```kotlin-s
51 | private val ITEM = DeferredRegister.create(ForgeRegistries.ITEMS, Cobalt.MOD_ID)
52 | private val whetherIndicator = ITEM.register("weather_indicator") { Item(Item.Properties().tab(creativeTab)) }
53 | ```
54 |
55 | ```java-s
56 | private DeferredRegister
- ITEM = DeferredRegister.create(ForgeRegistries.ITEMS, Cobalt.MOD_ID);
57 | private RegistryObject
- whetherIndicator = ITEM.register("weather_indicator", () -> new Item(new Item.Properties().tab(creativeTab)));
58 | ```
59 |
60 | 上述文件结构中`weather_indicator.json`尚未编写,其他json模型,都由`blockBench`生成
61 | ```json
62 | {
63 | "parent": "cobalt:item/weather_indicator_empty"
64 | }
65 | ```
66 | 这样子即可引用`json模型`,效果如下
67 |
68 | 
69 |
70 | ## Item Property Override
71 |
72 | 天气指示器,仅有一种样式肯定是不够的,为此,我们需要借助如下机制
73 | 原版提供了一种名为`overrides`的机制,可以通过一定的上下文,从有限数目的模型中指定一个进行渲染
74 |
75 | 调用`ItemProperties.register(Item pItem, ResourceLocation pName, ItemPropertyFunction pProperty)`
76 | 第一个参数`pItem`即需要绑定的物品
77 | 第二个参数`pName`指的是`overrides`的名称,原版的有[这些](https://minecraft.fandom.com/zh/wiki/%E6%A8%A1%E5%9E%8B#.E7.89.A9.E5.93.81.E6.A0.87.E7.AD.BE.E8.B0.93.E8.AF.8D)
78 | 第三个参数就是给定上下文,返回模型的地方了
79 |
80 | ```java
81 | @Deprecated
82 | @OnlyIn(Dist.CLIENT)
83 | public interface ItemPropertyFunction {
84 | float call(ItemStack pStack, @Nullable ClientLevel pLevel, @Nullable LivingEntity pEntity, int pSeed);
85 | }
86 |
87 | @OnlyIn(Dist.CLIENT)
88 | public interface ClampedItemPropertyFunction extends ItemPropertyFunction {
89 | /** @deprecated */
90 | @Deprecated
91 | default float call(ItemStack pStack, @Nullable ClientLevel pLevel, @Nullable LivingEntity pEntity, int pSeed) {
92 | return Mth.clamp(this.unclampedCall(pStack, pLevel, pEntity, pSeed), 0.0F, 1.0F);
93 | }
94 |
95 | float unclampedCall(ItemStack pStack, @Nullable ClientLevel pLevel, @Nullable LivingEntity pEntity, int pSeed);
96 | }
97 | ```
98 | 我们应该使用下面那个函数式接口
99 |
100 | 第三个参数pSeed,部分传入为`0`,部分为`ItemEntity的ID`
101 | 理论上也可以自己随意使用
102 |
103 | 代码
104 | ```kotlin-s
105 | private val whetherIndicator = ITEM.register("weather_indicator"){
106 | object :Item(Properties().tab(creativeTab)){
107 | override fun isFoil(pStack: ItemStack): Boolean {
108 | return if (Thread.currentThread().threadGroup == SidedThreadGroups.SERVER||Minecraft.getInstance().level?.isThundering == true){
109 | true
110 | } else {
111 | super.isFoil(pStack)
112 | }
113 | }
114 | }
115 | }
116 |
117 | fun setItemOverride(event: FMLClientSetupEvent) {
118 | ItemProperties.register(whetherIndicator.get(), ResourceLocation(Cobalt.MOD_ID, "weather"))
119 | { itemStack, _, livingEntity, seed ->
120 | val clientLevel = Minecraft.getInstance().level
121 | if (clientLevel == null) {
122 | 0f
123 | } else {
124 | if (clientLevel.isRaining) {
125 | 1f
126 | } else if (clientLevel.dayTime < 11000) {
127 | 2f
128 | } else {
129 | 3f
130 | }
131 | }
132 | }
133 | }
134 | ```
135 |
136 | ```java-s
137 | private Item whetherIndicator = ITEM.register("weather_indicator", () -> new Item(new Properties().tab(creativeTab)) {
138 | @Override
139 | public bool isFoil(ItemStack pStack) {
140 | final var level = Minecraft.getInstance().level
141 | if (Thread.currentThread().threadGroup == SidedThreadGroups.SERVER || (level != null && level.isThundering)) {
142 | return true;
143 | } else {
144 | return super.isFoil(pStack);
145 | }
146 | }
147 | });
148 |
149 | public static void setItemOverride(FMLClientSetupEvent event) {
150 | ItemProperties.register(whetherIndicator.get(), new ResourceLocation(Cobalt.MOD_ID, "weather"),
151 | (itemStack, __, livingEntity, seed) -> {
152 | final var clientLevel = Minecraft.getInstance().level;
153 | if (clientLevel == null){
154 | return 0f;
155 | } else {
156 | if (clientLevel.isRaining) {
157 | return 1f;
158 | } else if (clientLevel.dayTime < 11000) {
159 | return 2f;
160 | } else {
161 | return 3f;
162 | }
163 | }
164 | }
165 | );
166 | }
167 | ```
168 |
169 | 资源文件结构
170 |
171 | ```treeview
172 | resources/
173 | `-- assets/
174 | `-- cobalt/
175 | |-- models/
176 | | `-- item/
177 | | |-- weather_indicator.json
178 | | |-- weather_indicator_cloud.json
179 | | |-- weather_indicator_empty.json
180 | | `-- weather_indicator_sun.json
181 | `-- textures/
182 | `-- weather_indicator/
183 | |-- cloud1.png
184 | |-- cloud2.png
185 | |-- moon1.png
186 | |-- moon2.png
187 | |-- sun1.png
188 | |-- sun2.png
189 | `-- weather_indicator.png
190 | ```
191 |
192 | 效果如下
193 | 
194 |
195 | 逻辑如下
196 | 
197 |
198 | ---
199 |
200 | ## colouring
201 |
202 | 这一小结,我们讲制作一支支持染色的粉笔
203 |
204 | ### principle
205 |
206 | `ItemRenderer`类内
207 |
208 | ```java
209 | public void renderQuadList(PoseStack pMatrixStack, VertexConsumer pBuffer, List pQuads, ItemStack pItemStack, int pCombinedLight, int pCombinedOverlay) {
210 | boolean flag = !pItemStack.isEmpty();
211 | PoseStack.Pose posestack$pose = pMatrixStack.last();
212 |
213 | for(BakedQuad bakedquad : pQuads) {
214 | int i = -1;
215 | if (flag && bakedquad.isTinted()) {
216 | i = this.itemColors.getColor(pItemStack, bakedquad.getTintIndex()); //!
217 | }
218 |
219 | float f = (float)(i >> 16 & 255) / 255.0F;
220 | float f1 = (float)(i >> 8 & 255) / 255.0F;
221 | float f2 = (float)(i & 255) / 255.0F;
222 | pBuffer.putBulkData(posestack$pose, bakedquad, f, f1, f2, pCombinedLight, pCombinedOverlay, true);
223 | }
224 | }
225 | ```
226 |
227 | 可以看到,`getColor`的返回值会被分割为三个参数,传递给`VertexConsumer#putBulkData`
228 | 而这三个参数会直接乘以将要提交的顶点的颜色
229 |
230 | ```java
231 | default void putBulkData(PoseStack.Pose pose, BakedQuad bakedQuad, float[] baseBrightness, float red, float green, float blue, float alpha, int[] lightmap, int packedOverlay, boolean readExistingColor) {
232 | int[] aint = bakedQuad.getVertices();
233 | Vec3i faceNormal = bakedQuad.getDirection().getNormal();
234 | Vector3f normal = new Vector3f((float)faceNormal.getX(), (float)faceNormal.getY(), (float)faceNormal.getZ());
235 | Matrix4f matrix4f = pose.pose();
236 | normal.transform(pose.normal());
237 | int intSize = DefaultVertexFormat.BLOCK.getIntegerSize();
238 | int vertexCount = aint.length / intSize;
239 |
240 | try (MemoryStack memorystack = MemoryStack.stackPush()) {
241 | ByteBuffer bytebuffer = memorystack.malloc(DefaultVertexFormat.BLOCK.getVertexSize());
242 | IntBuffer intbuffer = bytebuffer.asIntBuffer();
243 |
244 | for(int v = 0; v < vertexCount; ++v) {
245 | ((Buffer)intbuffer).clear();
246 | intbuffer.put(aint, v * 8, 8);
247 | float f = bytebuffer.getFloat(0);
248 | float f1 = bytebuffer.getFloat(4);
249 | float f2 = bytebuffer.getFloat(8);
250 | float cr;
251 | float cg;
252 | float cb;
253 | float ca;
254 | if (readExistingColor) {
255 | float r = (float)(bytebuffer.get(12) & 255) / 255.0F;
256 | float g = (float)(bytebuffer.get(13) & 255) / 255.0F;
257 | float b = (float)(bytebuffer.get(14) & 255) / 255.0F;
258 | float a = (float)(bytebuffer.get(15) & 255) / 255.0F;
259 | cr = r * baseBrightness[v] * red; //!
260 | cg = g * baseBrightness[v] * green; //!
261 | cb = b * baseBrightness[v] * blue; //!
262 | ca = a * alpha;
263 | } else {
264 | cr = baseBrightness[v] * red; //!
265 | cg = baseBrightness[v] * green; //!
266 | cb = baseBrightness[v] * blue; //!
267 | ca = alpha;
268 | }
269 |
270 | int lightmapCoord = applyBakedLighting(lightmap[v], bytebuffer);
271 | float f9 = bytebuffer.getFloat(16);
272 | float f10 = bytebuffer.getFloat(20);
273 | Vector4f pos = new Vector4f(f, f1, f2, 1.0F);
274 | pos.transform(matrix4f);
275 | applyBakedNormals(normal, bytebuffer, pose.normal());
276 | ((VertexConsumer)this).vertex(pos.x(), pos.y(), pos.z(), cr, cg, cb, ca, f9, f10, packedOverlay, lightmapCoord, normal.x(), normal.y(), normal.z());
277 | //!
278 | }
279 | }
280 | }
281 | ```
282 |
283 | ### finite
284 |
285 | ```java
286 | @OnlyIn(Dist.CLIENT)
287 | public interface ItemColor {
288 | int getColor(ItemStack pStack, int pTintIndex);
289 | }
290 | ```
291 | 利用此接口,返回值为`rgb`,`pTintIndex`为`json`模型内参数
292 |
293 | 注册通过`ItemColors.register(ItemColor pItemColor, ItemLike... pItems)`
294 |
295 |
296 | #### **item register**
297 |
298 | ```kotlin-s
299 | val redChalk = ITEM.register("red_chalk") { Item(Item.Properties().tab(creativeTab)) }
300 | val greenChalk = ITEM.register("green_chalk") { Item(Item.Properties().tab(creativeTab)) }
301 | val blueChalk = ITEM.register("blue_chalk") { Item(Item.Properties().tab(creativeTab)) }
302 | ```
303 |
304 | ```java-s
305 | RegistryObject
- redChalk = ITEM.register("red_chalk", () -> new Item(new Item.Properties().tab(creativeTab)));
306 | RegistryObject
- greenChalk = ITEM.register("green_chalk", () -> new Item(new Item.Properties().tab(creativeTab)));
307 | RegistryObject
- blueChalk = ITEM.register("blue_chalk", () -> new Item(new Item.Properties().tab(creativeTab)));
308 | ```
309 |
310 | #### **ItemColor register**
311 |
312 | ```kotlin-s
313 | @JvmStatic
314 | fun registerColorHandle(event: ColorHandlerEvent.Item) {
315 | event.itemColors.register({ pStack, pTintIndex ->
316 | when (pStack.item) {
317 | redChalk.get() -> MaterialColor.COLOR_RED
318 | greenChalk.get() -> MaterialColor.COLOR_GREEN
319 | blueChalk.get() -> MaterialColor.COLOR_BLUE
320 | else -> MaterialColor.COLOR_BLACK
321 | }.col
322 | }, redChalk.get(), greenChalk.get(), blueChalk.get())
323 | }
324 | ```
325 |
326 | ```java-s
327 | public static void registerColorHandle(ColorHandlerEvent.Item event) {
328 | event.itemColors.register((pStack, pTintIndex) -> {
329 | if(pStack.item == redChalk.get())
330 | return MaterialColor.COLOR_RED.col;
331 | if(pStack.item == greenChalk.get())
332 | return MaterialColor.COLOR_GREEN.col;
333 | if(pStack.item == blueChalk.get())
334 | return MaterialColor.COLOR_BLUE.col;
335 | return MaterialColor.COLOR_BLACK.col;
336 | }, redChalk.get(), greenChalk.get(), blueChalk.get());
337 | }
338 | ```
339 |
340 |
341 |
342 | 就可以看到
343 | 
344 |
345 | ### infinite
346 |
347 | 有限颜色的粉笔肯定是不行的
348 | 从代码原理我们可以看出,染色的原理几乎就是将提交的顶点数据的颜色部分,乘以我们返回的`RGB`值
349 | 所以,我们可以不通过`TintIndex`,而通过`stackItem`的`nbt`数据获取信息
350 |
351 |
352 | #### **colorful chalk item**
353 |
354 | ```kotlin-s
355 | class ColorfulChalk : Item(Properties().tab(creativeTab)) {
356 | fun setColor(itemStack: ItemStack, color: Int) {
357 | val nbt = IntTag.valueOf(color)
358 | itemStack.addTagElement("color", nbt)
359 | }
360 |
361 | fun getColor(itemStack: ItemStack): Int {
362 | val nbt = itemStack.tag?.get("color") as? IntTag
363 | return nbt?.asInt ?: 0xffffff
364 | }
365 | }
366 |
367 | val colorfulChalk = ITEM.register("colorful_chalk"){ColorfulChalk()}
368 | ```
369 |
370 | ```java-s
371 | class ColorfulChalk extends Item {
372 |
373 | public ColorfulChalk() {
374 | super(new Properties().tab(creativeTab));
375 | }
376 |
377 | public void setColor(ItemStack itemStack, int color) {
378 | final var nbt = IntTag.valueOf(color);
379 | itemStack.addTagElement("color", nbt);
380 | }
381 |
382 | public int getColor(ItemStack itemStack) {
383 | final var tag = itemStack.tag;
384 | if(tag != null && tag.get("color") instanceof IntTag colorTag) {
385 | return colorTag.asInt;
386 | }else {
387 | return 0xffffff;
388 | }
389 | }
390 | }
391 |
392 | RegistryObject colorfulChalk = ITEM.register("colorful_chalk", ColorfulChalk::new);
393 | ```
394 |
395 | #### **ItemColor register**
396 |
397 | ```kotlin-s
398 | @JvmStatic
399 | fun registerColorHandle(event: ColorHandlerEvent.Item) {
400 | event.itemColors.register({pStack,_ ->
401 | (pStack.item as ColorfulChalk).getColor(pStack)
402 | }, colorfulChalk.get())
403 | }
404 | ```
405 |
406 | ```java-s
407 | public static void registerColorHandle(ColorHandlerEvent.Item event) {
408 | event.itemColors.register((pStack,__) -> ((ColorfulChalk)(pStack.item)).getColor(pStack), colorfulChalk.get());
409 | }
410 | ```
411 |
412 | ### **Command**
413 |
414 | ```kotlin-s
415 | @JvmStatic
416 | fun registerCommand(event: RegisterCommandsEvent) {
417 | event.dispatcher.register(LiteralArgumentBuilder.literal("setColor").then(
418 | Commands.argument("color",HexArgumentType(0,0xffffff))
419 | .executes { ctx ->
420 | val color = ctx.getArgument("color", Int::class.java)
421 | val source = ctx.source
422 | val entity = source.entity
423 | if (entity is Player) {
424 | val itemStack = entity.mainHandItem
425 | if (itemStack.item is ColorfulChalk) {
426 | (itemStack.item as ColorfulChalk).setColor(itemStack, color)
427 | source.sendSuccess(TextComponent("successfully set color"), true)
428 | } else {
429 | source.sendFailure(TextComponent("main hand isn't holding colorfulChalk"))
430 | }
431 | }else{
432 | source.sendFailure(TextComponent("sender is not a player"))
433 | }
434 | 0
435 | }
436 | ))
437 | }
438 | ```
439 |
440 | ```java-s
441 | public static void registerCommand(RegisterCommandsEvent event) {
442 | event.dispatcher.register(LiteralArgumentBuilder.literal("setColor").then(
443 | Commands.argument("color",new HexArgumentType(0,0xffffff))
444 | .executes( ctx ->
445 | final var color = ctx.getArgument("color", Int::class.java);
446 | final var source = ctx.source;
447 | final var entity = source.entity;
448 | if (entity instanceof Player player) {
449 | final var itemStack = player.mainHandItem;
450 | if (itemStack.item instanceof ColorfulChalk colorfulChalkItem) {
451 | colorfulChalkItem.setColor(itemStack, color);
452 | source.sendSuccess(new TextComponent("successfully set color"), true);
453 | } else {
454 | source.sendFailure(new TextComponent("main hand isn't holding colorfulChalk"));
455 | }
456 | }else{
457 | source.sendFailure(new TextComponent("sender is not a player"));
458 | }
459 | return 0;
460 | )
461 | ));
462 | }
463 | ```
464 |
465 | ### **HexArgumentType**
466 |
467 | ```kotlin-s
468 | class HexArgumentType(private val minimum: Int = Int.MIN_VALUE, private val maximum: Int = Int.MAX_VALUE) :
469 | ArgumentType {
470 |
471 | companion object {
472 | private val example = listOf("0xffffff", "0xff00ff")
473 | private val hexSynaxErrorType = DynamicCommandExceptionType { value ->
474 | LiteralMessage("hex number must begin witch 0x instead of $value")
475 | }
476 | private val readerExpectedStartOf0x = SimpleCommandExceptionType(LiteralMessage("expected start with 0x"))
477 | private val noHexInputType = SimpleCommandExceptionType(LiteralMessage("please enter number"))
478 | }
479 |
480 | @Throws(CommandSyntaxException::class)
481 | override fun parse(reader: StringReader): Int {
482 | var cursor = reader.cursor
483 | try {
484 | val first = reader.read()
485 | val second = reader.read()
486 | if (first != '0' || second != 'x') {
487 | reader.cursor = cursor
488 | throw hexSynaxErrorType.createWithContext(reader, "$first$second")
489 | }
490 | } catch (e: Exception) {
491 | throw readerExpectedStartOf0x.create()
492 | }
493 | cursor += 2
494 | val result :String
495 | try {
496 | result = reader.readString()
497 | }catch (e:Exception){
498 | throw noHexInputType.create()
499 | }
500 | val resultNum = Integer.parseInt(result,16)
501 | if (resultNum < minimum) {
502 | reader.cursor = cursor
503 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooLow().createWithContext(reader, result, minimum)
504 | }
505 | if (resultNum > maximum) {
506 | reader.cursor = cursor
507 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh().createWithContext(reader, result, maximum)
508 | }
509 | return resultNum
510 | }
511 |
512 | override fun equals(other: Any?): Boolean {
513 | if (this === other) return true
514 | if (other !is IntegerArgumentType) return false
515 | val that = other
516 | return maximum == that.maximum && minimum == that.minimum
517 | }
518 |
519 | override fun hashCode(): Int {
520 | return 31 * minimum + maximum
521 | }
522 |
523 | override fun toString(): String {
524 | return if (minimum == Int.MIN_VALUE && maximum == Int.MAX_VALUE) {
525 | "integer()"
526 | } else if (maximum == Int.MAX_VALUE) {
527 | "integer($minimum)"
528 | } else {
529 | "integer($minimum, $maximum)"
530 | }
531 | }
532 |
533 | override fun getExamples(): MutableCollection = example
534 |
535 | }
536 | ```
537 |
538 | ```java-s
539 | class HexArgumentType extends ArgumentType {
540 | private int minimum = Integer.MAX_VALUE;
541 | private int maximum = Integer.MAX_VALUE;
542 |
543 | private static List example = List.of("0xffffff", "0xff00ff");
544 | private static DynamicCommandExceptionType hexSynaxErrorType = new DynamicCommandExceptionType ( value ->
545 | new LiteralMessage("hex number must begin witch 0x instead of " + value)
546 | );
547 | private static SimpleCommandExceptionType readerExpectedStartOf0x = new SimpleCommandExceptionType(new LiteralMessage("expected start with 0x"));
548 | private static SimpleCommandExceptionType noHexInputType = new SimpleCommandExceptionType(new LiteralMessage("please enter number"));
549 |
550 | @Override
551 | public int parse(StringReader reader) throws CommandSyntaxException {
552 | var cursor = reader.cursor;
553 | try {
554 | final var first = reader.read();
555 | final var second = reader.read();
556 | if (first != '0' || second != 'x') {
557 | reader.cursor = cursor;
558 | throw hexSynaxErrorType.createWithContext(reader, first + "" +second);
559 | }
560 | } catch (Exception e) {
561 | throw readerExpectedStartOf0x.create();
562 | }
563 | cursor += 2;
564 | String result;
565 | try {
566 | result = reader.readString();
567 | }catch (Exception e){
568 | throw noHexInputType.create();
569 | }
570 | final var resultNum = Integer.parseInt(result,16);
571 | if (resultNum < minimum) {
572 | reader.cursor = cursor;
573 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooLow().createWithContext(reader, result, minimum);
574 | }
575 | if (resultNum > maximum) {
576 | reader.cursor = cursor;
577 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh().createWithContext(reader, result, maximum);
578 | }
579 | return resultNum;
580 | }
581 |
582 | @Override
583 | public bool equals(Object other) {
584 | if (this == other) return true;
585 | if (!(other instanceof IntegerArgumentType)) return false;
586 | return maximum == other.maximum && minimum == other.minimum;
587 | }
588 |
589 | @Override
590 | public int hashCode() {
591 | return 31 * minimum + maximum;
592 | }
593 |
594 | @Override
595 | public String toString() {
596 | if (minimum == Int.MIN_VALUE && maximum == Int.MAX_VALUE) {
597 | return "integer()";
598 | } else if (maximum == Int.MAX_VALUE) {
599 | return "integer(" + minimum + ")";
600 | } else {
601 | return "integer(" + minimum + ", " + maximum + ")";
602 | }
603 | }
604 |
605 | @Override
606 | public Collection getExamples() {
607 | return example;
608 | }
609 |
610 | }
611 | ```
612 |
613 |
614 |
615 | 再编写一个命令,用于给粉笔设置颜色
616 | 就可以得到这样的效果
617 | 
618 |
619 | ## Overrides
620 |
621 | 本节的`Overrides`与上文的`Item Property Overrides`不同,指的是`ItemOverrides`类
622 | 而上文的`Item Property Overrides`只是`ItemOverrides`的默认实现
623 |
624 | `ItemOverrides`中有这样一个方法
625 | `public BakedModel resolve(BakedModel pModel, ItemStack pStack, @Nullable ClientLevel pLevel, @Nullable LivingEntity pEntity, int pSeed)`
626 | 通过这个方法,我们就能在不同的时候返回不同的`BkaedModel`,不在限制在预先定义的范围内
627 |
628 | 这里给出两种办法,一种通过`ModelBakedEvent`直接替换并利用代理模式,包上一层
629 |
630 | ```kotlin-s
631 | fun setBakedModel(event: ModelBakeEvent){
632 | val modelResourceLocation = ModelResourceLocation(AllRegisters.drawableChalk.get().registryName,"inventory")
633 | val model = event.modelRegistry[modelResourceLocation]
634 | event.modelRegistry[modelResourceLocation] = object : BakedModelWrapper(model) {
635 | override fun getOverrides(): ItemOverrides = OverrideItemOverrides()
636 | }
637 | }
638 | ```
639 |
640 | ```java-s
641 | public void setBakedModel(ModelBakeEvent event){
642 | final var modelResourceLocation = new ModelResourceLocation(AllRegisters.drawableChalk.get().registryName,"inventory");
643 | final var model = event.getModelRegistry().get(modelResourceLocation);
644 | event.modelRegistry.put(modelResourceLocation , new BakedModelWrapper(model) {
645 | @Override public ItemOverrides getOverrides() { return new OverrideItemOverrides();}
646 | });
647 | }
648 | ```
649 |
650 |
651 | 如果你不嫌麻烦的话,可以这样
652 |
653 |
654 | #### **OverrideModelLoader**
655 |
656 | ```kotlin-s
657 | class OverrideModelLoader : IModelLoader {
658 | companion object {
659 | @JvmStatic
660 | fun registerModelLoader(event: ModelRegistryEvent) {
661 | ModelLoaderRegistry.registerLoader(
662 | ResourceLocation(Cobalt.MOD_ID, "override_loader"),
663 | OverrideModelLoader()
664 | )
665 | }
666 | }
667 |
668 | override fun onResourceManagerReload(pResourceManager: ResourceManager) {
669 |
670 | }
671 |
672 | override fun read(
673 | deserializationContext: JsonDeserializationContext,
674 | modelContents: JsonObject
675 | ): OverrideModelGeometry {
676 | modelContents.remove("loader")
677 | val model = deserializationContext.deserialize(modelContents, BlockModel::class.java)
678 | return OverrideModelGeometry(model)
679 | }
680 |
681 | }
682 | ```
683 |
684 | ```java-s
685 | class OverrideModelLoader extends IModelLoader {
686 |
687 | public static void registerModelLoader(ModelRegistryEvent event) {
688 | ModelLoaderRegistry.registerLoader(
689 | new ResourceLocation(Cobalt.MOD_ID, "override_loader"),
690 | new OverrideModelLoader()
691 | );
692 | }
693 |
694 | @Override
695 | public void onResourceManagerReload(ResourceManager pResourceManager) {}
696 |
697 | @Override
698 | public OverrideModelGeometry read(JsonDeserializationContext deserializationContext, JsonObject modelContents) {
699 | modelContents.remove("loader");
700 | final var model = deserializationContext.deserialize(modelContents, BlockModel.class);
701 | return new OverrideModelGeometry(model);
702 | }
703 |
704 | }
705 | ```
706 |
707 | #### **OverrideModelGeometry**
708 |
709 | ```kotlin-s
710 | class OverrideModelGeometry(val delegate: BlockModel) : IModelGeometry {
711 | override fun bake(
712 | owner: IModelConfiguration?,
713 | bakery: ModelBakery?,
714 | spriteGetter: Function?,
715 | modelTransform: ModelState?,
716 | overrides: ItemOverrides?,
717 | modelLocation: ResourceLocation?
718 | ): BakedModel {
719 |
720 | val delegateModel = delegate.bake(
721 | bakery, delegate, spriteGetter, modelTransform, modelLocation, delegate.guiLight.lightLikeBlock()
722 | )
723 | return OverrideWrappedBakedModel(delegateModel, OverrideItemOverrides())
724 | }
725 |
726 | override fun getTextures(
727 | owner: IModelConfiguration?,
728 | modelGetter: Function?,
729 | missingTextureErrors: MutableSet>?
730 | ): MutableCollection {
731 | return delegate.getMaterials(modelGetter, missingTextureErrors)
732 | }
733 |
734 | }
735 | ```
736 |
737 | ```java-s
738 | class OverrideModelGeometry extends IModelGeometry {
739 |
740 | BlockModel delegate;
741 |
742 | OverrideModelGeometry(BlockModel delegate) {
743 | this.delegate = delegate;
744 | }
745 |
746 | @Override
747 | public BakedModel bake(IModelConfiguration owner,ModelBakery bakery,Function spriteGetter,
748 | ModelState modelTransform,ItemOverrides overrides,ResourceLocation modelLocation) {
749 | final var delegateModel = delegate.bake(
750 | bakery, delegate, spriteGetter, modelTransform, modelLocation, delegate.guiLight.lightLikeBlock()
751 | );
752 | return new OverrideWrappedBakedModel(delegateModel, new OverrideItemOverrides());
753 | }
754 |
755 | @Override
756 | public MutableCollection getTextures(IModelConfiguration owner,Function modelGetter,
757 | MutableSet> missingTextureErrors){
758 | return delegate.getMaterials(modelGetter, missingTextureErrors);
759 | }
760 |
761 | }
762 | ```
763 |
764 | #### **OverrideWrappedBakedModel**
765 |
766 | ```kotlin-s
767 | class OverrideWrappedBakedModel(originalModel: BakedModel, private val overrides: OverrideItemOverrides) :
768 | BakedModelWrapper(originalModel) {
769 | override fun getOverrides(): ItemOverrides = overrides
770 | }
771 | ```
772 |
773 | ```java-s
774 | class OverrideWrappedBakedModel extends BakedModelWrapper {
775 |
776 | private final OverrideItemOverrides overrides;
777 |
778 | public OverrideWrappedBakedModel(originalModel BakedModel, OverrideItemOverrides overrides){
779 | super(originModel);
780 | this.overrides = overrides;
781 | }
782 |
783 | @override
784 | public ItemOverrides getOverrides() { return overrides;}
785 | }
786 | ```
787 |
788 | #### **OverrideItemOverrides**
789 |
790 | ```kotlin-s
791 | class OverrideItemOverrides : ItemOverrides() {
792 |
793 | override fun resolve(
794 | pModel: BakedModel,
795 | pStack: ItemStack,
796 | pLevel: ClientLevel?,
797 | pEntity: LivingEntity?,
798 | pSeed: Int
799 | ): BakedModel? {
800 | val item = pStack.item as DrawableChalk
801 | val blockState = item.getBlockState(pStack)
802 | return if (blockState!=null){
803 | val modelManager = Minecraft.getInstance().modelManager
804 | val location = BlockModelShaper.stateToModelLocation(blockState)
805 | return modelManager.getModel(location)
806 | }else{
807 | pModel
808 | }
809 | }
810 | }
811 | ```
812 |
813 | ```java-s
814 | class OverrideItemOverrides extends ItemOverrides {
815 |
816 | @Override
817 | public BakedModel resolve(BakedModel pModel,ItemStack pStack,ClientLevel pLevel,LivingEntity pEntity,int pSeed) {
818 | final var item = (DrawableChalk) pStack.item;
819 | final var blockState = item.getBlockState(pStack);
820 | if (blockState != null){
821 | final var modelManager = Minecraft.getInstance().modelManager;
822 | final var location = BlockModelShaper.stateToModelLocation(blockState);
823 | return modelManager.getModel(location);
824 | }else{
825 | return pModel;
826 | }
827 | }
828 | }
829 | ```
830 |
831 | #### **drawable_chalk.json**
832 |
833 | ```json
834 | {
835 | "loader": "cobalt:override_loader",
836 | "parent": "item/generated",
837 | "textures": {
838 | "layer0": "cobalt:chalk"
839 | }
840 | }
841 | ```
842 |
843 |
844 |
845 | 效果如下
846 | 
847 |
848 | >[!note]
849 | > 如果你像修正草方块的颜色
850 | > 应该查询原版的实现方式,位置在`BlockColors`内类
851 | > 其实现调用了`BiomeColors#getAverageGrassColor`
852 |
853 | ## BlockEntityWithoutLevelRenderer
854 |
855 | 如果你需要更加动态的渲染物品或者物品的渲染需要和使用了`BlockEntityRender`的方块渲染效果一致
856 | 那么你需要的是名为`BlockEntityWithoutLevelRenderer`,曾叫做`ItemStackTileEntityRenderer`
857 | 以代码的方式进行渲染,做到你想要的一切
858 |
859 | 首先要让MC知道你的物品模型需要`BlockEntityWithoutLevelRenderer`,这需要你的`BakedModel.isCustomRenderer`返回`true`
860 |
861 | 因为在`ItemRender#render`内,会以此作为判断
862 |
863 | 而要实现这一目标,给出两种办法
864 |
865 | 一种是让你的`json`模型,直接或间接继承自`builtin/entity`,原因如下
866 |
867 | 在`ModelBakery#loadBlockModel`中,如果你的物品模型,继承自`builtin/entity`
868 | 你的模型就会被读取为一个名为`BLOCK_ENTITY_MARKER`的`BlockModel/UnbakedModel`
869 | 在`BlockModel#bakeVanilla`,模型就会被`bake`为`BuiltInModel`,它的`isCustomRender()`返回就为`true`
870 |
871 | 另一种就是在`ModelBakeEvent`中进行替换,同上文替换`overrides`一致
872 |
873 | 当然你也可以和上文一样,直接定义一个`IModelLoader`走一个模型加载的全套流程
874 |
875 | 这样,只要给你的物品复写`public void initializeClient(Consumer consumer)`
876 | 给传入的`consumer`传入一个复写了`BlockEntityWithoutLevelRenderer getItemStackRenderer()`的`IItemRenderProperties`即可
877 | 不然则会默认返回`BlockEntityWithoutLevelRenderer(blockEntityRenderDispatcher,/*EntityModelSet*/ entityModels)`
878 |
879 | 通过以上操作,物品在渲染时候就会调用你传入的`BlockEntityWithoutLevelRender#renderByItem`
880 |
881 | 示例如下
882 |
883 | ```kotlin-s
884 | override fun initializeClient(consumer: Consumer) {
885 | consumer.accept(object : IItemRenderProperties {
886 | override fun getItemStackRenderer(): BlockEntityWithoutLevelRenderer {
887 | return object : BlockEntityWithoutLevelRenderer(
888 | Minecraft.getInstance().blockEntityRenderDispatcher, Minecraft.getInstance().entityModels
889 | ) {
890 | override fun renderByItem(
891 | pStack: ItemStack,
892 | pTransformType: TransformType,
893 | pPoseStack: PoseStack,
894 | pBuffer: MultiBufferSource,
895 | pPackedLight: Int,
896 | pPackedOverlay: Int
897 | ) {
898 | //do anything you want to do
899 | }
900 | }
901 | }
902 | })
903 | }
904 | ```
905 |
906 | ```java-s
907 | @Override
908 | public void initializeClient(Consumer consumer) {
909 | consumer.accept(new IItemRenderProperties() {
910 | @Override
911 | public BlockEntityWithoutLevelRenderer getItemStackRenderer() {
912 | return new BlockEntityWithoutLevelRenderer(
913 | Minecraft.getInstance().blockEntityRenderDispatcher, Minecraft.getInstance().entityModels
914 | ) {
915 | @Override
916 | public void renderByItem(ItemStack pStack,TransformType pTransformType,PoseStack pPoseStack,
917 | MultiBufferSource pBuffer,Int pPackedLight,Int pPackedOverlay
918 | ) {
919 | //do anything you want to do
920 | }
921 | };
922 | }
923 | });
924 | }
925 | ```
--------------------------------------------------------------------------------
/render/misc.md:
--------------------------------------------------------------------------------
1 | # misc
2 |
3 | 用于放置尚未分类的杂项
4 | 单项长度不定
5 |
6 | ---
7 |
8 | ## RenderChunk的缓存问题
9 |
10 | `ChunkRenderDispatcher.RenderChunk`存在一个缓存机制,仅在内部变量`dirty`被设置后才会更新
11 | 如果不依赖`BlockState`变化想要使用`BlockColor`会遇到无法即使更新的情况
12 | 对单个方块进行`dirty`标记的方法为`LevelRender#setBlockDirty`如下
13 |
14 | ```java
15 | public void setBlockDirty(BlockPos pPos, BlockState pOldState, BlockState pNewState) {
16 | if (this.minecraft.getModelManager().requiresRender(pOldState, pNewState)) {
17 | this.setBlocksDirty(pPos.getX(), pPos.getY(), pPos.getZ(), pPos.getX(), pPos.getY(), pPos.getZ());
18 | }
19 | }
20 | ```
21 | 可以看到判断新旧`BlockState`所需的模型是否相等,在这里我们按照它的调用方式调用
22 | `setBlocksDirty(int pMinX, int pMinY, int pMinZ, int pMaxX, int pMaxY, int pMaxZ)`
23 | 即可标记`dirty`
24 |
25 | 同理的还有`dirty` `section`的方法
26 |
27 | ## ChunkOffset和&15
28 |
29 | 有时你会看到BlockPos的各个坐标在被提交前,分别都与 15进行了按位&操作
30 | 这是用于每个section的独立渲染,它们会被`ChunkOffset` `uniform`进行修正,其类型为`vec3`
31 | 仅在mc按section渲染时被设置,其余时候各分量为0
32 |
33 | ## PoseStack
34 |
35 | `translate(double pX, double pY, double pZ)`平移
36 | `scale(float pX, float pY, float pZ)`缩放
37 | `mulPose(Quaternion pQuaternion)`用于旋转,其中的四元数可通过
38 | `Quaternion.fromXYZ/YXZ/XYZ(flaot,float,flaot)`,注意为弧度制
39 | 或者`Vector3f`下的`XN` `XP` `YN` `YP` `ZN` `ZP`,`N/P`为`negative/positive`
40 | 下有方法`rotation/rotationDegrees(float)`,前者为弧度制,后者为角度制
41 |
42 | 内部有一个`Deque`用于存储存储数据,`push`压入,`pop`弹出,`last`拿到队列顶部元素
43 | 每个`PoseStack.Pose`,内有`Matrix4f`的`pose`,和`Matrix3f`的`normal`
44 |
45 | ## Selected Text
46 |
47 | glLogicOp使用GL_OR_REVERSE
48 |
49 | ## Fog in liquid
50 |
51 | 直接使用`EntityViewRenderEvent.FogColors`
52 |
53 | ## RenderShape
54 |
55 | 枚举类
56 |
57 | | name | usage |
58 | |----------------------|-------------------------|
59 | | INVISIBLE | skip render |
60 | | ENTITYBLOCK_ANIMATED | skip batch chunk render |
61 | | MODEL | render all |
62 |
63 | > [!note]
64 | > `BaseEntityBlock`覆写后方块默认从MODE变为ENTITYBLOCK_ANIMATED
65 | > 若继承自此类且不需要跳过批量区块渲染则需要手动覆写,参考附魔台
66 |
--------------------------------------------------------------------------------
/render/overlayTexture.md:
--------------------------------------------------------------------------------
1 | # overlyTexture
2 |
3 | ---
4 |
5 | 
6 |
7 | ## source
8 |
9 | 这张称之为OverlayTexture的材质不存在于材质包中,由代码生成
10 | 如下为具体代码,来自`net.minecraft.client.renderer.texture.OverlayTexture`的构造方法
11 | ```java
12 | for(int i = 0; i < 16; ++i) {
13 | for(int j = 0; j < 16; ++j) {
14 | if (i < 8) {
15 | nativeimage.setPixelRGBA(j, i, -1308622593);
16 | } else {
17 | int k = (int)((1.0F - (float)j / 15.0F * 0.75F) * 255.0F);
18 | nativeimage.setPixelRGBA(/*pX:*/ j , /*pY:*/ i , pAbgrColor:k << 24 | 16777215);
19 | }
20 | }
21 | }
22 | ```
23 | 下半部分
24 | 有符号数`-1308622593`转化为无符号数`2986344703`
25 | 如果你想自己转化的话,可以随便找门语言
26 | ```cpp
27 | std::cout << (unsigned int) (int) (-1308622593);
28 | ```
29 | ```kotlin
30 | println((-1308622593).toUInt())
31 | ```
32 | 再次转化为16进制`b20000ff`
33 | 从高位至地位(从左到右)代表alpha blue green red,对应rgba `ff 00 00 00 b2`
34 | 上半部分
35 | 有符号数`16777215`转化为16进制`ffffff`
36 | `k`左移24位,为二进制数8位,代表alpha,对应颜色rgba `ff ff ff k`
37 |
38 | ## principle & usage
39 |
40 | ---
41 | 这个材质有什么用呢,在说明这之前,我们先来回一下我们在函数参数中见到有关OverlayTexture的地方
42 |
43 | ```java
44 | @OnlyIn(Dist.CLIENT)
45 | public interface BlockEntityRenderer {
46 | void render(T pBlockEntity, float pPartialTick, PoseStack pPoseStack,
47 | MultiBufferSource pBufferSource, int pPackedLight, int pPackedOverlay);
48 |
49 | ......
50 | }
51 | ```
52 | 可以看到那个名为pPackedOverlay,或者在mcp mapping被称之为combinedOverlayIn的参数
53 | 如果你查看它的Call Hierarchy,也就是调用结构,可以发现,传入的参数几乎都是一个名为
54 | `OverlayTexture.NO_OVERLAY`的东西,查找其定义是`OverlayTexture.pack(/*pU:*/0,/*pV:*/10)`
55 | 而`pack`函数的定义又是
56 | ```java
57 | public static int pack(int pU, int pV) {
58 | return pU | pV << 16;
59 | }
60 | ```
61 | 可以发现,这个函数就是将v左移16位,把两个使用时一定范围较小的变量,合并到一个范围较大的变量中
62 |
63 | 在`com.mojang.blaze3d.vertex.VertexConsumer`中能发现
64 | ```java
65 | default VertexConsumer overlayCoords(int pOverlayUV) {
66 | return this.overlayCoords(pOverlayUV & '\uffff', pOverlayUV >> 16 & '\uffff');
67 | }
68 | ```
69 | 这里的字符`'\uffff'`应该是反编译错误?,查看其字节码
70 | ```
71 | // access flags 0x1
72 | public default overlayCoords(I)Lcom/mojang/blaze3d/vertex/VertexConsumer;
73 | L0
74 | LINENUMBER 59 L0
75 | ALOAD 0
76 | ILOAD 1
77 | LDC 65535
78 | IAND
79 | ILOAD 1
80 | BIPUSH 16
81 | ISHR
82 | LDC 65535
83 | IAND
84 | INVOKEINTERFACE com/mojang/blaze3d/vertex/VertexConsumer.overlayCoords (II)Lcom/mojang/blaze3d/vertex/VertexConsumer; (itf)
85 | ARETURN
86 | L1
87 | LOCALVARIABLE this Lcom/mojang/blaze3d/vertex/VertexConsumer; L0 L1 0
88 | LOCALVARIABLE pOverlayUV I L0 L1 1
89 | MAXSTACK = 4
90 | MAXLOCALS = 2
91 | ```
92 | 可以发现是`65535`,即`0xffff`
93 | 该函数的处理参数与`OverlayTexture#pack`作用相反
94 |
95 | ---
96 |
97 | 查看函数`overlayCoords`调用的方法会追踪到抽象函数`VertexConsumer uv2(int pU, int pV)`
98 | 查看其所有实现,除了`SheetedDecalTextureGenerator`的方法将参数用于设置`lightCoords`
99 | 其他实现都直接或间接的将参数没有经过处理,直接put进了`nio`的`ByteBuffer`
100 | 没有任何地方对这个参数进行`normalize/标准化/归一化`,那么这个参数是如何使用的呢
101 |
102 | 虽然在`VertexFormatElement`的静态内部枚举`Usage`和`DefaultVertexFormat`类内我们都找不到相关信息
103 | 但是在mc的`core shader` `rendertype_entity_solid.vsh`内
104 | ```glsl
105 | #version 150
106 |
107 | #moj_import
108 | #moj_import
109 |
110 | in vec3 Position;
111 | in vec4 Color;
112 | in vec2 UV0;
113 | in ivec2 UV1;
114 | in ivec2 UV2;
115 | in vec3 Normal;
116 |
117 | uniform sampler2D Sampler1;
118 | uniform sampler2D Sampler2;
119 |
120 | uniform mat4 ModelViewMat;
121 | uniform mat4 ProjMat;
122 | uniform mat3 IViewRotMat;
123 | uniform int FogShape;
124 |
125 | uniform vec3 Light0_Direction;
126 | uniform vec3 Light1_Direction;
127 |
128 | out float vertexDistance;
129 | out vec4 vertexColor;
130 | out vec4 lightMapColor;
131 | out vec4 overlayColor;
132 | out vec2 texCoord0;
133 | out vec4 normal;
134 |
135 | void main() {
136 | gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
137 |
138 | vertexDistance = fog_distance(ModelViewMat, IViewRotMat * Position, FogShape);
139 | vertexColor = minecraft_mix_light(Light0_Direction, Light1_Direction, Normal, Color);
140 | lightMapColor = texelFetch(Sampler2, UV2 / 16, 0);
141 | overlayColor = texelFetch(Sampler1, UV1, 0);
142 | texCoord0 = UV0;
143 | normal = ProjMat * ModelViewMat * vec4(Normal, 0.0);
144 | }
145 | ```
146 | 可以看到,overlayColor来自于Sampler1的UV1处
147 | 而函数`texelFetch`的采样坐标正是不需要归一化的
148 | 因此传入的参数即为对应材质的`UV坐标`
149 |
150 | `texelFetch`函数可以参考[docs.gl](https://docs.gl/sl4/texelFetch)和
151 | [stackOverFlow上的一处回答](https://stackoverflow.com/a/45613787/15315647)
152 |
153 | 题外话
154 | `#moj_import`是由类`GlslPreprocessor`处理的
155 | `UVO`可以发现是`材质坐标`
156 | `UV2`可以发现是`lightMap坐标`
157 |
158 | 再次查看与之同名不同类型的片段着色器`rendertype_entity_solid.fsh`
159 | ```glsl
160 | #version 150
161 |
162 | #moj_import
163 |
164 | uniform sampler2D Sampler0;
165 |
166 | uniform vec4 ColorModulator;
167 | uniform float FogStart;
168 | uniform float FogEnd;
169 | uniform vec4 FogColor;
170 |
171 | in float vertexDistance;
172 | in vec4 vertexColor;
173 | in vec4 lightMapColor;
174 | in vec4 overlayColor;
175 | in vec2 texCoord0;
176 | in vec4 normal;
177 |
178 | out vec4 fragColor;
179 |
180 | void main() {
181 | vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator;
182 | color.rgb = mix(overlayColor.rgb, color.rgb, overlayColor.a);
183 | color *= lightMapColor;
184 | fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor);
185 | }
186 | ```
187 | 这句`color.rgb = mix(overlayColor.rgb, color.rgb, overlayColor.a);`
188 | 可以发现最终输出的color的rgb分量为自身rgb分量分别乘以overlayColor的rgb分量
189 | 并且与其己归一化的(1-alpha)分量和alpha为加权系数求合
190 | [mix](https://docs.gl/sl4/mix)函数的说明可以在这里看到
191 |
192 | ---
193 |
194 | 这时回过头来看类`OverlayTexture`的这两个函数
195 | ```java
196 | public static int pack(float pU, boolean pHurt) {
197 | return pack(u(pU), v(pHurt));
198 | }
199 |
200 | public static int v(boolean pHurt) {
201 | return pHurt ? 3 : 10;
202 | }
203 | ```
204 | 受伤对应的v坐标正是对应OverlayTexture下方的红色,即我们在游戏中看到的
205 | 生物受伤时的红色效果
206 |
207 | 等等,我记得生物受伤时的效果是会闪烁的吧?
208 | 没错
209 | 查看类`LivingEntityRenderer`的`render`函数,有这样两行
210 | ```java
211 | int i = getOverlayCoords(pEntity, this.getWhiteOverlayProgress(pEntity, pPartialTicks));
212 | this.model.renderToBuffer(pMatrixStack, vertexconsumer, pPackedLight, i, 1.0F, 1.0F, 1.0F, flag1 ? 0.15F : 1.0F);
213 | ```
214 | 而`getOverlayCoords`函数为
215 | ```java
216 | public static int getOverlayCoords(LivingEntity pLivingEntity, float pU) {
217 | return OverlayTexture.pack(OverlayTexture.u(pU), OverlayTexture.v(pLivingEntity.hurtTime > 0 || pLivingEntity.deathTime > 0));
218 | }
219 | ```
220 | 又有`OverlayTexture#u`
221 | ```java
222 | public static int u(float pU) {
223 | return (int)(pU * 15.0F);
224 | }
225 | ```
226 | `OverlayTexture`的U坐标正对应alpha的插值权重,而`pPartialTicks`又为处于0~1的插值参量
227 | 因此动画便是线性变化的
228 | 错!
229 | 然而仅有白色的部分alpha是变化的,所以生物受伤/死亡时,表面红色程度固定
230 | 那么TNT实体在点然后表面闪烁的白色?
231 | 错!
232 | `TntMinecartRenderer`类内
233 | ```java
234 | public static void renderWhiteSolidBlock(BlockState pBlockState, PoseStack pMatrixStack, MultiBufferSource pRenderTypeBuffer, int pCombinedLight, boolean pDoFullBright) {
235 | int i;
236 | if (pDoFullBright) {
237 | i = OverlayTexture.pack(OverlayTexture.u(1.0F), 10);
238 | } else {
239 | i = OverlayTexture.NO_OVERLAY;
240 | }
241 |
242 | Minecraft.getInstance().getBlockRenderer().renderSingleBlock(pBlockState, pMatrixStack, pRenderTypeBuffer, pCombinedLight, i);
243 | }
244 | ```
245 | ```java
246 | int i = pEntity.getFuse();
247 | if ((float)i - pPartialTicks + 1.0F < 10.0F) {
248 | float f = 1.0F - ((float)i - pPartialTicks + 1.0F) / 10.0F;
249 | f = Mth.clamp(f, 0.0F, 1.0F);
250 | f *= f;
251 | f *= f;
252 | float f1 = 1.0F + f * 0.3F;
253 | pMatrixStack.scale(f1, f1, f1);
254 | }
255 | pdoFullBolckLight=i / 5 % 2 == 0;
256 | ```
257 | 还是固定程度的的....
258 |
259 | 至少看完以后你知道了怎样实现这样一个效果
260 |
261 | ## example
262 |
263 | 比如
264 | 
265 |
266 | 不太恰当的示例代码
267 | ```java
268 | /**
269 | * make fired tnt twinkle
270 | */
271 | @Mixin(TntMinecartRenderer.class)
272 | abstract class MixinTNTMinecartRenderer {
273 | @ModifyVariable(
274 | method = "renderWhiteSolidBlock",
275 | at = @At(
276 | value = "LOAD",
277 | opcode = Opcodes.ILOAD
278 | ),
279 | ordinal = 1
280 | )
281 | private static int interpolatedOverlay(int value) {
282 | return OverlayTexture.pack(OverlayTexture.u(getU()), 10);
283 | }
284 |
285 | private static float getU() {
286 | var time = (float) (System.currentTimeMillis() % 1000 / 1000.0);
287 | if (time>0.5){
288 | return 1-time;
289 | }else {
290 | return time;
291 | }
292 | }
293 |
294 | }
295 | ```
--------------------------------------------------------------------------------
/render/overview.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | ---
4 |
5 | 在`net.minecraft.client.Minecraft#runTick`下,仅列出部分逻辑
6 | ```mmd
7 | flowchart TB
8 | begin[Minecraft#runTick]-->gameRender
9 | subgraph gameRender[GameRender#render]
10 | direction TB
11 | viewPoer[set view port] -->
12 | renderLevel[GameRender#renderLevel] -->
13 | tryTakeScreen[try take screenshoot]
14 | end
15 | gameRender --> toast[render toasts]
16 | toast --> postRender[post render]
17 |
18 | ```
--------------------------------------------------------------------------------
/render/renderInLevel.md:
--------------------------------------------------------------------------------
1 | # renderInLevel
2 |
3 | ---
4 |
5 | >[!note]
6 | > 如果你绘制出的对象一直黑漆漆的,且使用了`pPackedLight`
7 | > 请检查其是否一直为0
8 | > 可以通过给方块添加`noOcclusion`或者非完整`VoxelShape`解决
9 | > **_如做修改,请尝试更新光照,敲掉重放或者放一个光源都可以_**
10 |
11 |
12 | ## Block
13 |
14 | ```kotlin-s
15 | val blockRenderer = Minecraft.getInstance().blockRenderer
16 | val blockstate = Blocks.ANVIL.defaultBlockState()
17 | blockRenderer.renderSingleBlock(blockstate,pPoseStack, pBufferSource, pPackedLight
18 | , pPackedOverlay,EmptyModelData.INSTANCE)
19 | ```
20 |
21 | ```java-s
22 | final var blockRenderer = Minecraft.getInstance().blockRenderer;
23 | final var blockstate = Blocks.ANVIL.defaultBlockState();
24 | blockRenderer.renderSingleBlock(blockstate,pPoseStack, pBufferSource, pPackedLight
25 | , pPackedOverlay,EmptyModelData.INSTANCE);
26 | ```
27 |
28 | 
29 |
30 | 不进行交互的话,只有f3能看出这其实不是个铁砧
31 |
32 | ### BakedModel
33 |
34 | ```kotlin-s
35 | val blockRenderer = Minecraft.getInstance().blockRenderer
36 | val blockstate = Blocks.ANVIL.defaultBlockState()
37 | val model = blockRenderer.getBlockModel(blockstate)
38 | blockRenderer.modelRenderer.renderModel(pPoseStack.last(),
39 | pBufferSource.getBuffer(RenderType.solid()),
40 | blockstate, model,
41 | /*r*/1.0f,/*g*/1.0f,/*b*/1.0f,
42 | pPackedLight, pPackedOverlay,
43 | EmptyModelData.INSTANCE
44 | )
45 | ```
46 |
47 | ```java-s
48 | final var blockRenderer = Minecraft.getInstance().blockRenderer;
49 | final var blockstate = Blocks.ANVIL.defaultBlockState();
50 | final var model = blockRenderer.getBlockModel(blockstate);
51 | blockRenderer.modelRenderer.renderModel(pPoseStack.last(),
52 | pBufferSource.getBuffer(RenderType.solid()),
53 | blockstate, model,
54 | /*r*/1.0f,/*g*/1.0f,/*b*/1.0f,
55 | pPackedLight, pPackedOverlay,
56 | EmptyModelData.INSTANCE
57 | );
58 | ```
59 |
60 | `renderType`可从`ItemBlockRenderTypes#getRenderType`获取
61 | `r,g,b`参量仅在`BakedQuad`为`isTinted`时候被利用
62 |
63 | ### BlockRenderDispatcher#renderBatched & ModelRender#tesselateBlock
64 |
65 | 均用于原版用于绘制世界上的方块,其中有光照获取,并且内部有是否计算`AO(Ambient Occlusion/环境光遮蔽)`的分支
66 |
67 | ## Fluid
68 |
69 | 从`Minecraft.getInstance().blockRenderer.getBlockModel(Blocks.WATER.defaultBlockState())`
70 | 拿到的模型为空模型,原版没有类似于上文`renderSingleFluid`的方法,仅有
71 | `BlockRnederDispatcher#renderLiquid`,用于绘制世界上的流体,需要`Level`和`BlockPos`用于判断面是否需要渲染
72 | 如果想自己绘制,可以参考其内部调用`LiquidBlockRender#tesselate`
73 | 或者查看下文
74 |
75 | ## Item
76 |
77 | ```kotlin-s
78 | val itemRenderer = Minecraft.getInstance().itemRenderer
79 | val itemStack = ItemStack(Items.IRON_PICKAXE) // need cache
80 | val model = itemRenderer.getModel(itemStack,pBlockEntity.level,null,0)
81 | itemRenderer.render(
82 | itemStack,ItemTransforms.TransformType.GROUND,/*left hand*/false,pPoseStack,
83 | pBufferSource,pPackedLight,pPackedOverlay,model
84 | )
85 | ```
86 |
87 | ```java-s
88 | final var itemRenderer = Minecraft.getInstance().itemRenderer;
89 | final var itemStack = ItemStack(Items.IRON_PICKAXE); // need cache
90 | final var model = itemRenderer.getModel(itemStack,pBlockEntity.level,null,0);
91 | itemRenderer.render(
92 | itemStack,ItemTransforms.TransformType.GROUND,/*left hand*/false,pPoseStack,
93 | pBufferSource,pPackedLight,pPackedOverlay,model
94 | );
95 | ```
96 |
97 | 
98 |
99 | 凭借此图,我们也得以一窥在`BlockEntityRender`内的`PoseStack`的位置和朝向如何
100 |
101 | ## Custom
102 |
103 | 给出一个利用`RenderType`自行提交顶点绘制流体的例子
104 |
105 | ```kotlin-s
106 | val renderType = RenderType.translucent()
107 | val buffer = pBufferSource.getBuffer(renderType)
108 | val pos = pBlockEntity.blockPos
109 | val atlas = Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(Fluids.WATER.attributes.stillTexture)
110 | val color = Fluids.WATER.attributes.color
111 | val r = color shr 16 and 255
112 | val g = color shr 8 and 255
113 | val b = color and 255
114 | val alpha = color shr 24 and 255
115 | val matrix = pPoseStack.last().pose()
116 | buffer.vertex(matrix,0f,0f,0f)
117 | .color(r,g,b,alpha)
118 | .uv(atlas.u0,atlas.v0)
119 | .overlayCoords(OverlayTexture.NO_OVERLAY)
120 | .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex()
121 |
122 | buffer.vertex(matrix,0f,1f,1f)
123 | .color(r,g,b,alpha)
124 | .uv(atlas.u0,atlas.v1)
125 | .overlayCoords(OverlayTexture.NO_OVERLAY)
126 | .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex()
127 |
128 | buffer.vertex(matrix,1f,1f,1f)
129 | .color(r,g,b,alpha)
130 | .uv(atlas.u1,atlas.v1)
131 | .overlayCoords(OverlayTexture.NO_OVERLAY)
132 | .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex()
133 |
134 | buffer.vertex(matrix,1f,0f,0f)
135 | .color(r,g,b,alpha)
136 | .uv(atlas.u1,atlas.v0)
137 | .overlayCoords(OverlayTexture.NO_OVERLAY)
138 | .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex()
139 | ```
140 |
141 | ```java-s
142 | final var renderType = RenderType.translucent();
143 | final var buffer = pBufferSource.getBuffer(renderType);
144 | final var pos = pBlockEntity.blockPos;
145 | final var atlas = Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(Fluids.WATER.attributes.stillTexture);
146 | final var color = Fluids.WATER.attributes.color;
147 | final var r = color >> 16 & 255;
148 | final var g = color >> 8 & 255;
149 | final var b = color & 255;
150 | final var alpha = color >> 24 & 255;
151 | final var matrix = pPoseStack.last().pose();
152 | buffer.vertex(matrix,0f,0f,0f)
153 | .color(r,g,b,alpha)
154 | .uv(atlas.u0,atlas.v0)
155 | .overlayCoords(OverlayTexture.NO_OVERLAY)
156 | .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();
157 |
158 | buffer.vertex(matrix,0f,1f,1f)
159 | .color(r,g,b,alpha)
160 | .uv(atlas.u0,atlas.v1)
161 | .overlayCoords(OverlayTexture.NO_OVERLAY)
162 | .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();
163 |
164 | buffer.vertex(matrix,1f,1f,1f)
165 | .color(r,g,b,alpha)
166 | .uv(atlas.u1,atlas.v1)
167 | .overlayCoords(OverlayTexture.NO_OVERLAY)
168 | .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();
169 |
170 | buffer.vertex(matrix,1f,0f,0f)
171 | .color(r,g,b,alpha)
172 | .uv(atlas.u1,atlas.v0)
173 | .overlayCoords(OverlayTexture.NO_OVERLAY)
174 | .uv2(pPackedLight).normal(0.0F, 1.0F, 0.0F).endVertex();
175 | ```
176 |
177 | 
178 |
179 | >[!note]
180 | > 如果流体贴图没用正确渲染
181 | > 查看背面是否渲染?如果是改变顶点提交的顺序
182 | > 是否是画质是否是`Fabulous(极佳)`,如果是,可以将renderType切换到
183 | > `Sheets.translucentCullBlockSheet()`
184 |
185 | ## Model
186 |
187 | `net.minecraft.client.model.Model`内,直接调用`renderToBuffer`即可
--------------------------------------------------------------------------------
/render/renderType.md:
--------------------------------------------------------------------------------
1 | # RenderType
2 |
3 | ---
4 |
5 | `RenderType`可以说是mc渲染中最为重要的一部分,而它实际上是一些列对于`OpenGL context`操作的合集
6 |
7 | ## structure
8 |
9 | ```mmd
10 | flowchart RL
11 | BooleanStateShard
12 | CompositeRenderType
13 | CullStateShard
14 | DepthTestStateShard
15 | EmptyTextureStateShard
16 | LayeringStateShard
17 | LightmapStateShard
18 | LineStateShard
19 | MultiTextureStateShard
20 | OffsetTexturingStateShard
21 | OutputStateShard
22 | OverlayStateShard
23 | RenderType
24 | ShaderStateShard
25 | TextureStateShard
26 | TexturingStateShard
27 | TransparencyStateShard
28 | WriteMaskStateShard
29 | RenderStateShard
30 |
31 | BooleanStateShard --> RenderStateShard
32 | CompositeRenderType --> RenderType
33 | CullStateShard --> BooleanStateShard
34 | DepthTestStateShard --> RenderStateShard
35 | EmptyTextureStateShard --> RenderStateShard
36 | LayeringStateShard --> RenderStateShard
37 | LightmapStateShard --> BooleanStateShard
38 | LineStateShard --> RenderStateShard
39 | MultiTextureStateShard --> EmptyTextureStateShard
40 | OffsetTexturingStateShard --> TexturingStateShard
41 | OutputStateShard --> RenderStateShard
42 | OverlayStateShard --> BooleanStateShard
43 | RenderType --> RenderStateShard
44 | ShaderStateShard --> RenderStateShard
45 | TextureStateShard --> EmptyTextureStateShard
46 | TexturingStateShard --> RenderStateShard
47 | TransparencyStateShard --> RenderStateShard
48 | WriteMaskStateShard --> RenderStateShard
49 | ```
50 |
51 | 处于继承树顶层的`RenderStateShard`拥有三个字段,`String name`,`Runnable setupState`,`Runnable clearState`
52 | 正如起名,`setupState`,`clearState`分别在`renderType`配合调用`drawCall`前后被调用,用于便利的控制`opengl context`
53 | 其他的派生类只是对所需改变上下文所需字段的特化
54 |
55 | ## overview table
56 |
57 | | class/instance name | name | extra/comment |
58 | |----------------------------|--------------------------|---------------------------------------------------------------------------------------------------|
59 | | **DepthTestStateShard** | **depth_test** | **String functionName** |
60 | | NO_DEPTH_TEST | | functionName:always |
61 | | EQUAL_DEPTH_TEST | | functionName:== |
62 | | LEQUAL_DEPTH_TEST | | functionName:<= |
63 | | **LineStateShard** | **line_width** | **OptionalDouble width** |
64 | | DEFAULT_LINE | | width:1.0 |
65 | | **ShaderStateShard** | **shader** | **Optional> shader** |
66 | | **TransparencyStateShard** | | |
67 | | NO_TRANSPARENCY | no_transparency | |
68 | | ADDITIVE_TRANSPARENCY | additive_transparency | blendFunc(SRC.ONE,DEST.ONE) |
69 | | LIGHTNING_TRANSPARENCY | lightning_transparency | blendFunc(SRC.SRC_ALPHA,DEST.ONE) |
70 | | GLINT_TRANSPARENCY | glint_transparency | blendFuncSeparate
SRC.SRC_COLOR,DEST.ONE
SRC.ZERO,DEST.ONE |
71 | | CRUMBLING_TRANSPARENCY | crumbling_transparency | blendFuncSeparate
DEST.DST_COLOR,DEST.PME
SRC.ONE,DEST.ZERO |
72 | | TRANSLUCENT_TRANSPARENCY | translucent_transparency | blendFuncSeparate
SRC.SRC_ALPHA,DEST.ONE_MINUS_SRC_ALPHA
SRC.ONE,DEST.ONE_MINUS_SRC_ALPHA |
73 | | **WriteMaskStateShard** | **write_mask_state** | **boolean writeColor
boolean writeDepth** |
74 | | COLOR_DEPTH_WRITE | | writeColor:true
writeDepth:true |
75 | | COLOR_WRITE | | writeColor:true
writeDepth:false |
76 | | DEPTH_WRITE | | writeColor:false
writeDepth:true |
77 | | **OutputStateShard** | | |
78 | | MAIN_TARGET | main_target | getMainRenderTarget() |
79 | | OUTLINE_TARGET | outline_target | levelRenderer.entityTarget() |
80 | | TRANSLUCENT_TARGET | translucent_target | levelRenderer.getTranslucentTarget() |
81 | | PARTICLES_TARGET | particles_target | levelRenderer.getParticlesTarget() |
82 | | WEATHER_TARGET | weather_target | levelRenderer.getWeatherTarget() |
83 | | CLOUDS_TARGET | clouds_target | levelRenderer.getCloudsTarget() |
84 | | ITEM_ENTITY_TARGET | item_entity_target | levelRenderer.getItemEntityTarget() |
85 | | **LayeringStateShard** | | |
86 | | NO_LAYERING | no_layering | |
87 | | POLYGON_OFFSET_LAYERING | polygon_offset_layering | polygonOffset(factor:-1.0F,units:-10.0F) |
88 | | VIEW_OFFSET_Z_LAYERING | view_offset_z_layering | scale(x:0.99975586F,y:0.99975586F,z:0.99975586F) |
89 | | **EmptyTextureStateShard** | **texture** | **Optional cutoutTexture()** |
90 | | NO_TEXTURE | | |
91 | | **MultiTextureStateShard** | | |
92 | | **TextureStateShard** | | **Optional texture
boolean blur
boolean mipmap** |
93 | | BLOCK_SHEET_MIPPED | | texture:TextureAtlas.LOCATION_BLOCKS
blur:false
mipmap:true |
94 | | BLOCK_SHEET | | texture:TextureAtlas.LOCATION_BLOCKS
blur:false
mipmap:false |
95 | | **TexturingStateShard** | | |
96 | | DEFAULT_TEXTURING | default_texturing | |
97 | | GLINT_TEXTURING | glint_texturing | setupGlintTexturing(8.0F); |
98 | | ENTITY_GLINT_TEXTURING | entity_glint_texturing | setupGlintTexturing(0.16F); |
99 | | OffsetTexturingStateShard | offset_texturing | |
100 | | **BooleanStateShard** | | **bool enabled** |
101 | | **CullStateShard** | **cull** | **bool useCull** |
102 | | CULL | | useCull:true |
103 | | NO_CULL | | useCull:false |
104 | | **LightmapStateShard** | **lightmap** | **bool useLightMap** |
105 | | LIGHTMAP | | useLightMap:true |
106 | | NO_LIGHTMAP | | useLightMap:false |
107 | | **OverlayStateShard** | **overlay** | **bool useLightmap** |
108 | | OVERLAY | overlay | useLightmap:true |
109 | | NO_OVERLAY | overlay | useLightmap:false |
110 |
111 | `CompositeState`正是每种`RenderStateShard`合集,mj还提供了`CompositeStateBuilder`用`Builder模式`来构造对象
112 | 而`RenderType`则是`VertexFormat`,`bufferSize`,`CompositeState`的合集
113 |
114 | ## example
115 |
116 | 利用`RenderType`简化上次的代码
117 |
118 | ```kotlin-s
119 | @Suppress("unused")
120 | @Mod.EventBusSubscriber(Dist.CLIENT)
121 | object VertexFillByRenderType {
122 |
123 | private class RenderTypeHolder : RenderType("any", DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS, 256, false, false, {}, {}) {
124 | companion object {
125 | @Suppress("INACCESSIBLE_TYPE")
126 | val renderType: RenderType = create(
127 | "posColorRenderType", DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS, 256, false, false,
128 | CompositeState.builder()
129 | .setShaderState(POSITION_COLOR_SHADER)
130 | .setCullState(NO_CULL)
131 | .setDepthTestState(NO_DEPTH_TEST)
132 | .setTransparencyState(TRANSLUCENT_TRANSPARENCY)
133 | .createCompositeState(false)
134 | )
135 | }
136 | }
137 |
138 | @SubscribeEvent
139 | @JvmStatic
140 | fun renderLevelLastEvent(event: RenderLevelLastEvent) {
141 | if (Minecraft.getInstance().player!!.mainHandItem.item != Items.ANVIL) {
142 | return
143 | }
144 | val bufferSource = Minecraft.getInstance().renderBuffers().bufferSource()
145 | val buffer = bufferSource.getBuffer(RenderTypeHolder.renderType)
146 | dataFill(event,buffer,Blocks.ANVIL)
147 | RenderSystem.disableDepthTest()
148 | bufferSource.endBatch(RenderTypeHolder.renderType)
149 | }
150 | }
151 | ```
152 |
153 | ```java-s
154 | @SuppressWarnings("unused")
155 | @Mod.EventBusSubscriber(Dist.CLIENT)
156 | class VertexFillByRenderType {
157 |
158 | private class RenderTypeHolder extends RenderType {
159 |
160 | public RenderTypeHolder() {
161 | throw new RuntimeException("never should run to there");
162 | }
163 |
164 | @SuppressWarnings("INACCESSIBLE_TYPE")
165 | public static RenderType renderType = create(
166 | "posColorRenderType", DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS, 256, false, false,
167 | CompositeState.builder()
168 | .setShaderState(POSITION_COLOR_SHADER)
169 | .setCullState(NO_CULL)
170 | .setDepthTestState(NO_DEPTH_TEST)
171 | .setTransparencyState(TRANSLUCENT_TRANSPARENCY)
172 | .createCompositeState(false)
173 | );
174 | }
175 |
176 | @SubscribeEvent
177 | public static void renderLevelLastEvent(RenderLevelLastEvent event) {
178 | if (Minecraft.getInstance().player.mainHandItem.item != Items.ANVIL) {
179 | return;
180 | }
181 | final var bufferSource = Minecraft.getInstance().renderBuffers().bufferSource();
182 | final var buffer = bufferSource.getBuffer(RenderTypeHolder.renderType);
183 | dataFill(event,buffer,Blocks.ANVIL);
184 | RenderSystem.disableDepthTest();
185 | bufferSource.endBatch(RenderTypeHolder.renderType);
186 | }
187 | }
188 | ```
189 |
190 | >[!note]
191 | > 这里我们使用一个`RenderTypeHolder`
192 | > 是因为许多需要使用的字段访问级别仅为`protected`
193 | > 通过继承父类来暴露`protected`
194 | > 所以`holder`并不会被构造
195 |
196 | 可以看到调用处,还是简洁了不少
197 | 请无视最后的`RenderSystem.disableDepthTest()`,为什么有这个我折叠了,正常是不需要的
198 |
199 |
200 | 为什么呢
201 |
202 | ```java
203 | public static class DepthTestStateShard extends RenderStateShard {
204 | private final String functionName;
205 |
206 | public DepthTestStateShard(String pFunctionName, int pDepthFunc) {
207 | super("depth_test",/*setupState*/ () -> {
208 | if (pDepthFunc != GL_ALWAYS) {
209 | RenderSystem.enableDepthTest();
210 | RenderSystem.depthFunc(pDepthFunc);
211 | }
212 |
213 | }, /*clearState*/ () -> {
214 | if (pDepthFunc != GL_ALWAYS) {
215 | RenderSystem.disableDepthTest();
216 | RenderSystem.depthFunc(GL_LEQUAL);
217 | }
218 | });
219 | this.functionName = pFunctionName;
220 | }
221 |
222 | public String toString() {
223 | return this.name + "[" + this.functionName + "]";
224 | }
225 | }
226 |
227 | protected static final RenderStateShard.DepthTestStateShard NO_DEPTH_TEST
228 | = new RenderStateShard.DepthTestStateShard("always", GL_ALWAYS);
229 |
230 | ```
231 | 可以看到,对于`NO_DEPTH_TEST`,实际上就是...什么都不做
232 | 这就导致`DisableDepthTest`的调用,完全取决于使用`RenderType`或者在手动调用`enable`后再次`disable`
233 | 然后在笔者所处的环境中...mj没有配对的调用`disable`,只能手动添加
234 |
235 |
236 |
237 | ## bufferSource & batch
238 |
239 | 从调用的函数名`endBatch`暗示了`RenderType`配合`BufferSource`其实是用于批量渲染的
240 |
241 | ```java
242 | @OnlyIn(Dist.CLIENT)
243 | public interface MultiBufferSource {
244 | static MultiBufferSource.BufferSource immediate(BufferBuilder pBuilder) {
245 | return immediateWithBuffers(ImmutableMap.of(), pBuilder);
246 | }
247 |
248 | static MultiBufferSource.BufferSource immediateWithBuffers(Map pMapBuilders, BufferBuilder pBuilder) {
249 | return new MultiBufferSource.BufferSource(pBuilder, pMapBuilders);
250 | }
251 |
252 | VertexConsumer getBuffer(RenderType pRenderType);
253 |
254 | @OnlyIn(Dist.CLIENT)
255 | public static class BufferSource implements MultiBufferSource {
256 | protected final BufferBuilder builder;
257 | protected final Map fixedBuffers;
258 | protected Optional lastState = Optional.empty();
259 | protected final Set startedBuffers = Sets.newHashSet();
260 |
261 | protected BufferSource(BufferBuilder pBuilder, Map pFixedBuffers) {
262 | this.builder = pBuilder;
263 | this.fixedBuffers = pFixedBuffers;
264 | }
265 |
266 | public VertexConsumer getBuffer(RenderType pRenderType) {
267 | Optional optional = pRenderType.asOptional();
268 | BufferBuilder bufferbuilder = this.getBuilderRaw(pRenderType);
269 | if (!Objects.equals(this.lastState, optional)) {
270 | if (this.lastState.isPresent()) {
271 | RenderType rendertype = this.lastState.get();
272 | if (!this.fixedBuffers.containsKey(rendertype)) {
273 | this.endBatch(rendertype);
274 | }
275 | }
276 |
277 | if (this.startedBuffers.add(bufferbuilder)) {
278 | bufferbuilder.begin(pRenderType.mode(), pRenderType.format());
279 | }
280 |
281 | this.lastState = optional;
282 | }
283 |
284 | return bufferbuilder;
285 | }
286 |
287 | private BufferBuilder getBuilderRaw(RenderType pRenderType) {
288 | return this.fixedBuffers.getOrDefault(pRenderType, this.builder);
289 | }
290 | }
291 | }
292 | ```
293 |
294 | 可以发现,如果我们传入的`renderType`包含在`pMapBuilders/fixedBuffer`内,那么每次拿到的`BufferBuilder`
295 | 便是该`renderType`独占的,达到`batch`的效果
296 | 否则,将会共享`pBuilder`,并且还会直接`endBatch`上一次对应的`renderType`和`bufferBuilder`避免污染
297 |
298 |
299 | fixedBuffer
300 |
301 | ```java
302 | private final SortedMap fixedBuffers = Util.make(new Object2ObjectLinkedOpenHashMap<>(), (map) -> {
303 | map.put(Sheets.solidBlockSheet(), this.fixedBufferPack.builder(RenderType.solid()));
304 | map.put(Sheets.cutoutBlockSheet(), this.fixedBufferPack.builder(RenderType.cutout()));
305 | map.put(Sheets.bannerSheet(), this.fixedBufferPack.builder(RenderType.cutoutMipped()));
306 | map.put(Sheets.translucentCullBlockSheet(), this.fixedBufferPack.builder(RenderType.translucent()));
307 | put(map, Sheets.shieldSheet());
308 | put(map, Sheets.bedSheet());
309 | put(map, Sheets.shulkerBoxSheet());
310 | put(map, Sheets.signSheet());
311 | put(map, Sheets.chestSheet());
312 | put(map, RenderType.translucentNoCrumbling());
313 | put(map, RenderType.armorGlint());
314 | put(map, RenderType.armorEntityGlint());
315 | put(map, RenderType.glint());
316 | put(map, RenderType.glintDirect());
317 | put(map, RenderType.glintTranslucent());
318 | put(map, RenderType.entityGlint());
319 | put(map, RenderType.entityGlintDirect());
320 | put(map, RenderType.waterMask());
321 | ModelBakery.DESTROY_TYPES.forEach((item) -> {
322 | put(map, item);
323 | });
324 | });
325 | ```
326 |
327 |
328 |
329 | 至于`endBatch`则会调用`RenderType`内的如下方法
330 | `setupState`->`BufferUploader.end(buffer)`->`clearState`
331 |
332 | ```java
333 | public void end(BufferBuilder pBuffer, int pCameraX, int pCameraY, int pCameraZ) {
334 | if (pBuffer.building()) {
335 | if (this.sortOnUpload) {
336 | pBuffer.setQuadSortOrigin((float)pCameraX, (float)pCameraY, (float)pCameraZ);
337 | }
338 |
339 | pBuffer.end();
340 | this.setupRenderState();
341 | BufferUploader.end(pBuffer);
342 | this.clearRenderState();
343 | }
344 | }
345 | ```
346 |
347 | ## blockEntityRender
348 |
349 | 大致过程如下,摘自`LevelRender#renderLevel`
350 |
351 |
352 | #### **BlockEntity in frustum**
353 | ```java
354 | for(LevelRenderer.RenderChunkInfo levelrenderer$renderchunkinfo : this.renderChunksInFrustum) {
355 | List list = levelrenderer$renderchunkinfo.chunk.getCompiledChunk().getRenderableBlockEntities();
356 | if (!list.isEmpty()) {
357 | for(BlockEntity blockentity1 : list) {
358 | if(!frustum.isVisible(blockentity1.getRenderBoundingBox())) continue;
359 | BlockPos blockpos4 = blockentity1.getBlockPos();
360 | MultiBufferSource multibuffersource1 = multibuffersource$buffersource;
361 | pPoseStack.pushPose();
362 | pPoseStack.translate((double)blockpos4.getX() - d0, (double)blockpos4.getY() - d1, (double)blockpos4.getZ() - d2);
363 | SortedSet sortedset = this.destructionProgress.get(blockpos4.asLong());
364 | if (sortedset != null && !sortedset.isEmpty()) {
365 | int j1 = sortedset.last().getProgress();
366 | if (j1 >= 0) {
367 | PoseStack.Pose posestack$pose1 = pPoseStack.last();
368 | VertexConsumer vertexconsumer = new SheetedDecalTextureGenerator(this.renderBuffers.crumblingBufferSource().getBuffer(ModelBakery.DESTROY_TYPES.get(j1)), posestack$pose1.pose(), posestack$pose1.normal());
369 | multibuffersource1 = (p_194349_) -> {
370 | VertexConsumer vertexconsumer3 = multibuffersource$buffersource.getBuffer(p_194349_);
371 | return p_194349_.affectsCrumbling() ? VertexMultiConsumer.create(vertexconsumer, vertexconsumer3) : vertexconsumer3;
372 | };
373 | }
374 | }
375 |
376 | this.blockEntityRenderDispatcher.render(blockentity1, pPartialTick, pPoseStack, multibuffersource1); //!!!
377 | pPoseStack.popPose();
378 | }
379 | }
380 | }
381 | ```
382 | #### **Global BlockEntity**
383 | ```java
384 | synchronized(this.globalBlockEntities) {
385 | for(BlockEntity blockentity : this.globalBlockEntities) {
386 | if(!frustum.isVisible(blockentity.getRenderBoundingBox())) continue;
387 | BlockPos blockpos3 = blockentity.getBlockPos();
388 | pPoseStack.pushPose();
389 | pPoseStack.translate((double)blockpos3.getX() - d0, (double)blockpos3.getY() - d1, (double)blockpos3.getZ() - d2);
390 | this.blockEntityRenderDispatcher.render(blockentity, pPartialTick, pPoseStack, multibuffersource$buffersource);
391 | //!! 这里就会调用我们写的BlockEntityRender内的render方法
392 | pPoseStack.popPose();
393 | }
394 | }
395 | ```
396 | #### **batch render**
397 | ```java
398 | this.checkPoseStack(pPoseStack);
399 | multibuffersource$buffersource.endBatch(RenderType.solid()); //?
400 | multibuffersource$buffersource.endBatch(RenderType.endPortal());
401 | multibuffersource$buffersource.endBatch(RenderType.endGateway());
402 | multibuffersource$buffersource.endBatch(Sheets.solidBlockSheet());
403 | multibuffersource$buffersource.endBatch(Sheets.cutoutBlockSheet());
404 | multibuffersource$buffersource.endBatch(Sheets.bedSheet());
405 | multibuffersource$buffersource.endBatch(Sheets.shulkerBoxSheet());
406 | multibuffersource$buffersource.endBatch(Sheets.signSheet());
407 | multibuffersource$buffersource.endBatch(Sheets.chestSheet());
408 | ```
409 |
410 | 一种`RenderType`被`endBatch`应该仅代表在此之后,本帧不会在使用??
411 |
412 |
413 |
414 | ## normal block
415 |
416 | ---
417 |
418 | ### special? not special
419 |
420 | 相信各位都见过
421 |
422 |
423 | #### **TheGreyGhost**
424 | 
425 | 来自[TheGreyGhost](https://greyminecraftcoder.blogspot.com/2020/04/block-rendering-1144.html)
426 | #### **3T**
427 | 
428 |
429 |
430 | 并且配合`ItemBlockRenderTypes#setRenderLayer`或者层叫做`RenderTypeLookup#setRenderLayer`的方法为流体/方块设置`RenderLayer`?
431 | 但是,这里的参数确实是`RenderType`啊,这几个并没有什么特殊的啊
432 | 确实如此,但真正特殊的其实在于它们被渲染的代码块
433 | 大多数时候,我们只关心于`entity`.`blockEntity`,`gui`的渲染,它们的数量与遍布每个角落的渲染方式与之相比平平无奇的方块少的多的多
434 | 面对这种较大的数量级,mj对于它们采用了特殊的方式
435 |
436 |
437 | >[!tip]
438 | > 原版对其自身方块的设置位于类`ItemBlockRenderTypes`内
439 |
440 | `RenderType`类内
441 | ```java
442 | public static List chunkBufferLayers() {
443 | return ImmutableList.of(solid(), cutoutMipped(), cutout(), translucent(), tripwire());
444 | }
445 | ```
446 |
447 | 关于tripwire
448 |
449 | 好像在以前是没有的
450 | 在尚未有`RenderType`的的1.12.2,前面四个都放在一个叫做`BlockRenderLayer`的枚举类中
451 | 此时,tripwire方块的的renderLayer为`BlockRenderLayer.TRANSLUCENT;`
452 | 在1.16.5,mcp表这个方法叫做`getBlockRenderTypes`就存在`tripwire`
453 | 而forge的`multiLayerModel`中最早提早有相关信息的在[这里](https://github.com/MinecraftForge/MinecraftForge/blob/ce3d8b40cf37924caf1708cdde6842ae6fdcee31/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java#L247)
454 | 里面就已经包含了有关内容,但那次提交所处时间位于1.16.4与1.16.5之间
455 | 在1.18.1,对`translucent`和`tripwire`进行对比,可以发现除了`bufferSize`,`outputState`,`vsh`有非常小的差别外,一模一样
456 |
457 |
458 |
459 | 可以看到,它们与区块的渲染有密切相关
460 |
461 | ### concrete
462 |
463 | 仅列出与提交数据有关的部分
464 |
465 | ```java
466 | profilerfiller.popPush("terrain_setup");
467 | this.setupRender(pCamera, frustum, pHasCapturedFrustrum, this.minecraft.player.isSpectator());
468 | profilerfiller.popPush("compilechunks");
469 | this.compileChunks(pCamera);
470 | profilerfiller.popPush("terrain");
471 | this.renderChunkLayer(RenderType.solid(), pPoseStack, cameraX, camerY, cameraZ, pProjectionMatrix);
472 | this.minecraft.getModelManager().getAtlas(TextureAtlas.LOCATION_BLOCKS).setBlurMipmap(false, this.minecraft.options.mipmapLevels > 0); // FORGE: fix flickering leaves when mods mess up the blurMipmap settings
473 | this.renderChunkLayer(RenderType.cutoutMipped(), pPoseStack, cameraX, camerY, cameraZ, pProjectionMatrix);
474 | this.minecraft.getModelManager().getAtlas(TextureAtlas.LOCATION_BLOCKS).restoreLastBlurMipmap();
475 | this.renderChunkLayer(RenderType.cutout(), pPoseStack, cameraX, camerY, cameraZ, pProjectionMatrix);
476 | ```
477 |
478 | `this.renderChunkLayer(RenderType.solid(), pPoseStack, d0, d1, d2, pProjectionMatrix);`
479 | 这是最终调用`drawCall`的部分
480 |
481 | 最关键的一步在于`this.compileChunks(pCamera);`
482 |
483 |
484 | 相关内容,可不看
485 |
486 | 罗列一车与渲染有关的chunk类
487 | `static`前缀表明这是一个静态内部类,构造时,无需外部类,内部不含外部类引用
488 |
489 | | class name | field/method | description |
490 | |--------------------------------------------|---------------------------------------------------------------|-----------------------------------------|
491 | | RenderRegionCache | Long2ObjectMap chunkInfoCache | long:ChunkPos.asLong() |
492 | | static RenderRegionCache.ChunkInfo | LevelChunk chunk | map entry? |
493 | | | RenderChunk renderChunk | |
494 | | RenderChunk | Map blockEntities | |
495 | | | List> sections | |
496 | | | boolean debug | |
497 | | | LevelChunk wrapped | |
498 | | | BlockEntity getBlockEntity(BlockPos) | |
499 | | | BlockState getBlockState(BlockPos) | |
500 | | RenderChunkDispatcher.RenderChunk | static final int SIZE = 16 | |
501 | | | final int index | |
502 | | | AtomicInteger initialCompilationCancelCount | |
503 | | | ChunkRenderDispatcher.RenderChunk.RebuildTask lastRebuildTask | |
504 | | | ChunkRenderDispatcher.RenderChunk.ResortTransparencyTask | |
505 | | | Set globalBlockEntities | |
506 | | | Map buffers | |
507 | | static RenderChunkDispatcher.CompiledChunk | ChunkRenderDispatcher.CompiledChunk UNCOMPILED | |
508 | | | Set hasBlocks | |
509 | | | Set hasLayer | |
510 | | | boolean isCompletelyEmpty | |
511 | | | List renderableBlockEntities | |
512 | | | BufferBuilder.SortState transparencyState | |
513 | | static LevelRender.RenderInfoMap | LevelRenderer.RenderChunkInfo[] infos | ChunkRenderDispatcher.RenderChunk.index |
514 | | static LevelRender.RenderChunkStorage | LevelRenderer.RenderInfoMap renderInfoMap | |
515 | | | LinkedHashSet renderChunks | |
516 | | static LevelRender.RenderChunkInfo | ChunkRenderDispatcher.RenderChunk chunk | |
517 | | | byte sourceDirections | |
518 | | | byte directions | |
519 | | | int step | |
520 |
521 | ```java
522 | private void compileChunks(Camera camera) {
523 | this.minecraft.getProfiler().push("populate_chunks_to_compile");
524 | RenderRegionCache renderregioncache = new RenderRegionCache();
525 | BlockPos blockpos = camera.getBlockPosition();
526 | List list = Lists.newArrayList();
527 |
528 | for(LevelRenderer.RenderChunkInfo levelrenderer$renderchunkinfo : this.renderChunksInFrustum) {
529 | ChunkRenderDispatcher.RenderChunk chunkrenderdispatcher$renderchunk = levelrenderer$renderchunkinfo.chunk;
530 | ChunkPos chunkpos = new ChunkPos(chunkrenderdispatcher$renderchunk.getOrigin());
531 | if (chunkrenderdispatcher$renderchunk.isDirty() && this.level.getChunk(chunkpos.x, chunkpos.z).isClientLightReady()) {
532 | boolean flag = false;
533 | if (this.minecraft.options.prioritizeChunkUpdates != PrioritizeChunkUpdates.NEARBY) {
534 | if (this.minecraft.options.prioritizeChunkUpdates == PrioritizeChunkUpdates.PLAYER_AFFECTED) {
535 | flag = chunkrenderdispatcher$renderchunk.isDirtyFromPlayer();
536 | }
537 | } else {
538 | BlockPos blockpos1 = chunkrenderdispatcher$renderchunk.getOrigin().offset(8, 8, 8);
539 | flag = !net.minecraftforge.common.ForgeConfig.CLIENT.alwaysSetupTerrainOffThread.get() && (blockpos1.distSqr(blockpos) < 768.0D || chunkrenderdispatcher$renderchunk.isDirtyFromPlayer()); // the target is the else block below, so invert the forge addition to get there early
540 | }
541 |
542 | if (flag) {
543 | this.minecraft.getProfiler().push("build_near_sync");
544 | this.chunkRenderDispatcher.rebuildChunkSync(chunkrenderdispatcher$renderchunk, renderregioncache);
545 | chunkrenderdispatcher$renderchunk.setNotDirty();
546 | this.minecraft.getProfiler().pop();
547 | } else {
548 | list.add(chunkrenderdispatcher$renderchunk);
549 | }
550 | }
551 | }
552 |
553 | this.minecraft.getProfiler().popPush("upload");
554 | this.chunkRenderDispatcher.uploadAllPendingUploads();
555 | this.minecraft.getProfiler().popPush("schedule_async_compile");
556 |
557 | for(ChunkRenderDispatcher.RenderChunk chunkrenderdispatcher$renderchunk1 : list) {
558 | chunkrenderdispatcher$renderchunk1.rebuildChunkAsync(this.chunkRenderDispatcher, renderregioncache);
559 | chunkrenderdispatcher$renderchunk1.setNotDirty();
560 | }
561 |
562 | this.minecraft.getProfiler().pop();
563 | }
564 | ```
565 |
566 | `this.renderChunksInFrustum`的设置在`setupRender`内调用的`applyFrustum`
567 |
568 |
569 |
570 |
571 | 其大致内容,是根据当前区块渲染的策略,同步或异步的将`ChunkRenderDispatcher.RenderChunk`进行build的操作
572 | 最终它们都会调用到`ChunkCompileTask#doTask`
573 | 它有两个实现,一个是`RebuildTask`另一个是`ResortTransparencyTask`,抛去其所有线程调度逻辑
574 |
575 | 对于前者,最核心的的代码在于
576 |
577 | ```java
578 | ChunkRenderDispatcher.CompiledChunk compiledChunk = new ChunkRenderDispatcher.CompiledChunk();
579 | Set set = this.compile(cameraX, cameraY, cameraZ, compiledChunk, pBuffers);
580 | RenderChunk.this.updateGlobalBlockEntities(set);
581 |
582 | List> list = Lists.newArrayList();
583 | compiledChunk.hasLayer.forEach((item : RenderType) -> {
584 | list.add(ChunkRenderDispatcher.this.uploadChunkLayer(pBuffers.builder(item), RenderChunk.this.getBuffer(item)));
585 | });
586 | ```
587 |
588 | 在`compile`函数内,会对范围内所有的`BlockPos`逐个判定
589 | 并且根据对应坐标内的方块/流体/BlockEntity做出不同的渲染操作
590 | 调用`BlockRenderDispatcher#renderBatched/renderLiquid`,分别对应了`ModelBlockRenderer`与`LiquidBlockRenderer`内的函数
591 | 同时设置传入的`CompiledChunk`的一系列参数
592 | 而返回的`Set`则被同步进`ChunkRenderDispatcher.globalBlockEntities`
593 | 同时,如果内部有`translucent`的方块,则会设置`QuadSortOrigin`
594 |
595 | 而后面的foreach则分别将`BufferBuilder`内的数据上传
596 |
597 | 对于后者,它仅在调用`renderChunkLayer`传入的`RenderType`为`translucent`时被立刻执行
598 | 用于重新设置`BufferBuilder`的`QuadSortOrigin`
--------------------------------------------------------------------------------
/render/shader.md:
--------------------------------------------------------------------------------
1 | # Shader
2 |
3 | ---
4 |
5 | 对应于`ShaderInstance`与`EffectInstance`
6 | 前者即为`core shader`,后者为`Post Process Shader`
7 | 对于两者的具体内容,可以参见
8 |
9 | - [vanilla shader书写](https://docs.google.com/document/d/15TOAOVLgSNEoHGzpNlkez5cryH3hFF3awXL5Py81EMk/edit)
10 | - [core shader在游戏内用于渲染何物](https://github.com/ShockMicro/Minecraft-Shaders/wiki/Core-Shaders)
11 | - [vanilla uniforms](https://github.com/ShockMicro/Minecraft-Shaders/wiki/Uniforms)
12 |
13 | ---
14 |
15 | ## Register
16 |
17 | 对于forge,可以使用`RegisterShadersEvent`
18 | 对于fabric,fabric-api没有提供等价产物,可自行注入到在原版加载处后
19 |
20 | ## GlslPreprocessor
21 |
22 | 用于处理#moj_import<>并插入正确的[宏](https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)##line_directive)使报错的行号信息正确
23 |
24 | > [!note]
25 | > 如果对shader源码在运行时进行了修改,并防御性的进行编译测试
26 | > 由于GlslPreprocessor在ShaderInstance的匿名实现类,会记录已经处理过的#moj_import<>
27 | > 在测试后若仍使用同一个对象,切记清空import记录
28 |
29 | ## Respect Mod's name space
30 |
31 | 对于forge,`ShaderInstance`在经过forge patch后存在一个使用`Resource Loaction`的构造函数,可以使用mod的name space
32 | 同时,在forge这个[commit](https://github.com/MinecraftForge/MinecraftForge/commit/82026d11a4a0fde3fe524567552030a881648574)后#mj_import也支持了mod的name space
33 |
34 | fabric无
35 |
36 | ## New VertexFormatElement.Usage
37 |
38 | **_mc对`glVertexAttribPointer`竟然是以枚举类实现的
39 | 夏虫不可语冰!!!_**
40 | 而这玩意儿还是一个该死的非公开静态内部类
41 | 请使用AT/AW修改其访问修饰符并使用mixin来访问枚举类的构造函数来使得我们破除这个限制
42 |
43 | ```java
44 | import com.mojang.blaze3d.vertex.VertexFormatElement;
45 | import org.spongepowered.asm.mixin.Mixin;
46 | import org.spongepowered.asm.mixin.gen.Invoker;
47 | import org.spongepowered.asm.mixin.injection.Coerce;
48 |
49 | @Mixin(VertexFormatElement.Usage.class)
50 | public interface VertexFormatElementUsageAccessor{
51 | @Invoker("")
52 | static VertexFormatElement.Usage constructor(String enumName, int enumIndex, String name, VertexFormatElement.Usage.SetupState steup, @Coerce VertexFormatElement.Usage.ClearState clear){
53 | return null;
54 | }
55 | }
56 | ```
57 |
58 | ## New VertexFormat
59 | shader json内的attributes数组,其名与在构造VertexFormat时使用的Map的key的String一致
60 |
61 | ## Correspond BufferBuilder
62 | 为了方便使用,我们还需要一个与其配套使用的BufferBuilder子类
63 |
64 | 给出与原版实现风格一致的实现
65 | ```java
66 | public BufferBuilder progress(float progress){
67 | var vertexformatelement = your_VertexFormatElement.Usage
68 | if (this.currentElement().getUsage() != vertexformatelement){
69 | return this;
70 | }else if(vertexformatelement.getType() == your_VertexFormatElement.Type && vertexformatelement.getCount() == your_num)
71 | this.putXXX(); //fill data, maybe multi line
72 | this.nextElement();
73 | return this;
74 | }else {
75 | throw new IllegalStateException();
76 | }
77 | }
78 | ```
79 |
80 | # Example
81 |
82 | [逐顶点版本的ColorModulator](https://github.com/USS-Shenzhou/MadParticle/blob/master/src/main/java/cn/ussshenzhou/madparticle/particle/MadParticleShader.java)
83 | [消融效果实现](https://github.com/Low-Drag-MC/ShimmerFire/blob/main/src/main/java/com/lowdragmc/shimmerfire/client/renderer/MimicDissolveRender.java)
84 |
85 | 最终效果:
86 | 
87 |
88 | [可参考的消融原理讲解视频](https://www.bilibili.com/video/BV1ue4y147SX/)
--------------------------------------------------------------------------------
/render/vertexLife.md:
--------------------------------------------------------------------------------
1 | # Vertex's Lifetime
2 |
3 | ---
4 |
5 | 在本节,我们将追溯顶点从产生到`draw call`的全部流程
6 |
7 | ## flow
8 |
9 | 在开始之前,我们梳理一下OpenGL中顶点的流程
10 |
11 | ```mmd
12 | flowchart TB
13 | raw[raw data in any collection] --> upload
14 | subgraph upload[upload to graphic memory]
15 | direction LR
16 | subgraph vertexObject[Vertex Object]
17 | direction TB
18 | vertexArray[Object Buffer:attribute]
19 | indexArray[Object Buufer:index]
20 | end
21 | attribute[attribute setting]
22 | end
23 | upload --> any[...]
24 | any --> drawCall
25 | ```
26 |
27 | ## bufferBuilder
28 |
29 | mojang对整个过程进行了封装,这里的容器被封装为`BufferBuilder`
30 | 这里列出了`public`的函数,并且子类未展示父类函数,去除了所有`Balk`版本,去除了过长的
31 | `vertex(x, y, z, r, g, b, a, texU, texV, overlayUV, lightmapUV, normalX, normalY, normalZ) void`
32 | 一个函数若同时拥有float/double和int类型的参数的重载,则表明其值范围不同
33 | float/double表明参数需要`标准化/归一化`必须处于0-1,而int一般表明其值位于0-255
34 | 接口`VertexConsumer`还继承自`IForgeVertexConsumer`,里面也是`balk`版本的函数
35 |
36 | ```mmd
37 | classDiagram
38 | direction LR
39 | class BufferBuilder {
40 | + BufferBuilder(capacity)
41 | + building() boolean
42 | + begin(Mode, VertexFormat) void
43 | + getSortState() SortState
44 | + popNextBuffer() Pair~DrawStateAndByteBuffer~
45 | + clear() void
46 | + discard() void
47 | + end() void
48 | + setQuadSortOrigin(sortX, sortY, sortZ) void
49 | + getVertexFormat() VertexFormat
50 | + restoreSortState(SortState) void
51 | }
52 | class BufferVertexConsumer {
53 | <>
54 | + nextElement() void
55 | + normalIntValue(num) byte
56 | + uvShort(u, sv, index) VertexConsumer
57 | + putShort(int, short) void
58 | + currentElement() VertexFormatElement
59 | + putByte(int, byte) void
60 | + putFloat(int, float) void
61 | }
62 | class DefaultedVertexConsumer {
63 | + unsetDefaultColor() void
64 | }
65 | class VertexConsumer {
66 | <>
67 | + uv2(u, v) VertexConsumer
68 | + defaultColor(defaultR, defaultG, defaultB, defaultA) void
69 | + overlayCoords(u, v) VertexConsumer
70 | + overlayCoords(overlayUV) VertexConsumer
71 | + color(colorARGB) VertexConsumer
72 | + uv2(LightmapUV) VertexConsumer
73 | + unsetDefaultColor() void
74 | + uv(u, v) VertexConsumer
75 | + vertex(x, y, z) VertexConsumer
76 | + color(r, g, b, a) VertexConsumer
77 | + normal(x, y, z) VertexConsumer
78 | + endVertex() void
79 | + normal(Matrix3f, x, y, z) VertexConsumer
80 | + vertex(Matrix4f, x, y, z) VertexConsumer
81 | }
82 |
83 | BufferBuilder ..> BufferVertexConsumer
84 | BufferBuilder --> DefaultedVertexConsumer
85 | BufferVertexConsumer --> VertexConsumer
86 | DefaultedVertexConsumer ..> VertexConsumer
87 |
88 | ```
89 | 注意 popNextBuffer()的返回值应是Pair,上文的错误是由于mermaid不支持所致
90 | 在此[issue](https://github.com/mermaid-js/mermaid/issues/3287#issuecomment-1468536297)被解决后即可改善
91 |
92 | 可以发现位于继承树顶层的`VertexConsumer`定义了存放了不同功能的顶点数据
93 | `BufferVertexConsumer`则具体定义了将具体类型的数据存放
94 | 而`BufferBuilder`则是具体的实现者
95 | 这里实现了对于`Render Type` `translucent`的顶点顺序排序
96 | `setQuadSortOrigin` `getSortState`等还透露了关于`Render Type`中`translucent`的特殊处理
97 |
98 | 那么我们该如何获取我们想要的`VertexBuilder`呢?
99 |
100 | 在这节,我们先不引入`RenderType`
101 | 直接通过`Tesselator.getInstance().getBuilder()`或者直接使用`BufferBuilder`的构造函数
102 | 在提交数据时候,前者通过`Tesselator#end`,后者需要`BufferBuilder#end`和`BufferUploader.end(buffer)`
103 |
104 | ## example
105 |
106 |
107 | #### **by buffer**
108 |
109 | ```kotlin-s
110 | @Suppress("unused")
111 | @EventBusSubscriber(Dist.CLIENT)
112 | object VertexFillByBuffer {
113 |
114 | private val buffer = BufferBuilder(/*pCapacity*/ 256)
115 |
116 | @SubscribeEvent
117 | @JvmStatic
118 | fun renderLevelLastEvent(event: RenderLevelLastEvent) {
119 | if (Minecraft.getInstance().player!!.mainHandItem.item != Items.DIAMOND_BLOCK) {
120 | return
121 | }
122 | buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
123 | RenderSystem.setShader(GameRenderer::getPositionColorShader)
124 | RenderSystem.disableDepthTest()
125 | RenderSystem.enableBlend()
126 | RenderSystem.defaultBlendFunc()
127 | dataFill(event, buffer, Blocks.DIAMOND_BLOCK)
128 | buffer.end()
129 | BufferUploader.end(buffer)
130 | RenderSystem.enableDepthTest()
131 | RenderSystem.disableBlend()
132 | }
133 |
134 | }
135 | ```
136 |
137 | ```java-s
138 | @SuppressWarnings("unused")
139 | @EventBusSubscriber(Dist.CLIENT)
140 | class VertexFillByBuffer {
141 |
142 | private static BufferBuilder buffer = new BufferBuilder(/*pCapacity*/ 256);
143 |
144 | @SubscribeEvent
145 | public static void renderLevelLastEvent(RenderLevelLastEvent event) {
146 | if (Minecraft.getInstance().player.mainHandItem.item != Items.DIAMOND_BLOCK) {
147 | return;
148 | }
149 | buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
150 | RenderSystem.setShader(GameRenderer::getPositionColorShader);
151 | RenderSystem.disableDepthTest();
152 | RenderSystem.enableBlend();
153 | RenderSystem.defaultBlendFunc();
154 | dataFill(event, buffer, Blocks.DIAMOND_BLOCK);
155 | buffer.end();
156 | BufferUploader.end(buffer);
157 | RenderSystem.enableDepthTest();
158 | RenderSystem.disableBlend();
159 | }
160 |
161 | }
162 | ```
163 |
164 | #### **by tesselator**
165 |
166 | ```kotlin-s
167 | @Suppress("unused")
168 | @EventBusSubscriber(Dist.CLIENT)
169 | object VertexFillByTesselator {
170 | @SubscribeEvent
171 | @JvmStatic
172 | fun renderLevelLastEvent(event: RenderLevelLastEvent) {
173 | if (Minecraft.getInstance().player!!.mainHandItem.item != Items.IRON_BLOCK) {
174 | return
175 | }
176 | val tesselator = Tesselator.getInstance()
177 | val buffer = tesselator.builder
178 | buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
179 | RenderSystem.setShader(GameRenderer::getPositionColorShader)
180 | RenderSystem.disableDepthTest()
181 | RenderSystem.enableBlend()
182 | RenderSystem.defaultBlendFunc()
183 | dataFill(event, buffer, Blocks.IRON_BLOCK)
184 | tesselator.end()
185 | RenderSystem.enableDepthTest()
186 | RenderSystem.disableBlend()
187 | }
188 | }
189 | ```
190 |
191 | ```java-s
192 | @SuppressWarnings("unused")
193 | @EventBusSubscriber(Dist.CLIENT)
194 | class VertexFillByTesselator {
195 | @SubscribeEvent
196 | public static void renderLevelLastEvent(RenderLevelLastEvent event) {
197 | if (Minecraft.getInstance().player.mainHandItem.item != Items.IRON_BLOCK) {
198 | return;
199 | }
200 | final var tesselator = Tesselator.getInstance();
201 | final var buffer = tesselator.builder;
202 | buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
203 | RenderSystem.setShader(GameRenderer::getPositionColorShader);
204 | RenderSystem.disableDepthTest();
205 | RenderSystem.enableBlend();
206 | RenderSystem.defaultBlendFunc();
207 | dataFill(event, buffer, Blocks.IRON_BLOCK);
208 | tesselator.end();
209 | RenderSystem.enableDepthTest();
210 | RenderSystem.disableBlend();
211 | }
212 | }
213 | ```
214 |
215 | #### **dataFill fun**
216 |
217 | ```kotlin-s
218 | fun dataFill(event: RenderLevelLastEvent, buffer: VertexConsumer, block:Block) {
219 | val stack = event.poseStack
220 | val cameraPos = Minecraft.getInstance().gameRenderer.mainCamera.position
221 | stack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z)
222 | val playerPos = Minecraft.getInstance().player!!.blockPosition()
223 | val x = playerPos.x
224 | val y = playerPos.y
225 | val z = playerPos.z
226 | val pos = BlockPos.MutableBlockPos()
227 | for (dx in (x - 15)..(x + 15)) {
228 | pos.x = dx
229 | for (dy in (y - 15)..(y + 15)) {
230 | pos.y = dy
231 | for (dz in (z - 15)..(z + 15)) {
232 | pos.z = dz
233 | val blockState = Minecraft.getInstance().level!!.getBlockState(pos)
234 | if (blockState.block == block) {
235 | stack.pushPose()
236 | stack.translate(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble())
237 | val lastPose = stack.last().pose()
238 |
239 | buffer.vertex(lastPose, 0f, 0f, 0f).color(1f, 0f, 0f, 0.75f).endVertex()
240 | buffer.vertex(lastPose, 0f, 1f, 0f).color(0f, 1f, 0f, 0.75f).endVertex()
241 | buffer.vertex(lastPose, 1f, 1f, 0f).color(1f, 1f, 1f, 0.75f).endVertex()
242 | buffer.vertex(lastPose, 1f, 0f, 0f).color(1f, 1f, 1f, 0.75f).endVertex()
243 |
244 | stack.popPose()
245 | }
246 | }
247 | }
248 | }
249 | }
250 | ```
251 |
252 | ```java-s
253 | public static void dataFill(RenderLevelLastEvent event, VertexConsumer buffer, Block block) {
254 | final var stack = event.poseStack;
255 | final var cameraPos = Minecraft.getInstance().gameRenderer.mainCamera.position;
256 | stack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
257 | var playerPos = Minecraft.getInstance().player.blockPosition();
258 | final var x = playerPos.x;
259 | final var y = playerPos.y;
260 | final var z = playerPos.z;
261 | final var pos = BlockPos.MutableBlockPos();
262 | for (var dx = x - 15; dx < x + 15 ; dx++) {
263 | pos.x = dx;
264 | for (var dy = y - 15; dy< y + 15 ; dy++) {
265 | pos.y = dy;
266 | for (var dz = z - 15;dz
287 |
288 |
289 | ## VertexFormat.Mode
290 |
291 | 可以看到,首先我们首先调用了`begin(VertexFormat.Mode pMode, VertexFormat pFormat)`
292 | 如下是`VertexFormat.Mode`的部分定义
293 |
294 | | Name | glMode | primitiveLength | primitiveStride | indexCount(vertices) |
295 | |------------------|-------------------|-----------------|-----------------|----------------------|
296 | | LINES | GL_TRIANGLES | 2 | 2 | vertices/4*6 |
297 | | LINE_STRIP | GL_TRIANGLE_STRIP | 2 | 1 | vertices |
298 | | DEBUG_LINES | GL_LINES | 2 | 2 | vertices |
299 | | DEBUG_LINE_STRIP | GL_LINE_STRIP | 2 | 1 | vertices |
300 | | TRIANGLES | GL_TRIANGLES | 3 | 3 | vertices |
301 | | TRIANGLE_STRIP | GL_TRIANGLE_STRIP | 3 | 1 | vertices |
302 | | TRIANGLE_FAN | GL_TRIANGLE_FAN | 3 | 1 | vertices |
303 | | QUADS | GL_TRIANGLES | 4 | 4 | vertices/4*6 |
304 |
305 | 可以看到它与
306 | `void glDrawArrays(@NativeType("GLenum") int mode, @NativeType("GLint") int first, @NativeType("GLsizei") int count);`
307 | 和
308 | `void glDrawElements(@NativeType("GLenum") int mode, @NativeType("void const *") ShortBuffer indices)`
309 | 中的`mode`有关有关
310 | `glMode`即绘制的`图元模式`
311 | `primitiveLength`,在mc尚未使用,猜测单个图元所需顶点数目
312 | `primitiveStride`,只在顶点排序时有用到
313 | `int indexCount(int vertices)`是个函数,从顶点个数计算索引个数
314 | 很神奇的是`LINES`使用的图元模式也是三角形,而计算索引个数的方式与`QUADS`一致
315 | 在`BufferBuilder#endVertex`内
316 |
317 | ```java
318 | public void endVertex() {
319 | if (this.elementIndex != 0) {
320 | throw new IllegalStateException("Not filled all elements of the vertex");
321 | } else {
322 | ++this.vertices;
323 | this.ensureVertexCapacity();
324 | if (this.mode == VertexFormat.Mode.LINES || this.mode == VertexFormat.Mode.LINE_STRIP) {
325 | int i = this.format.getVertexSize();
326 | this.buffer.position(this.nextElementByte);
327 | ByteBuffer bytebuffer = this.buffer.duplicate();
328 | bytebuffer.position(this.nextElementByte - i).limit(this.nextElementByte);
329 | this.buffer.put(bytebuffer);
330 | this.nextElementByte += i;
331 | ++this.vertices;
332 | this.ensureVertexCapacity();
333 | }
334 |
335 | }
336 | }
337 | ```
338 |
339 | 顶点数据被`duplicate`了一份,所以绘制时仍只需传入两个顶点数据,具体可以查看`GLX#_renderCrosshair`
340 |
341 | ## VertexFormatElement.Type
342 |
343 | 在看`VertexFormat`前,我们先查看枚举`VertexFormatElement.Type`
344 |
345 | | name | size | name | glType |
346 | |--------|------|----------------|-------------------|
347 | | FLOAT | 4 | Float | GL_FLOAT |
348 | | UBYTE | 1 | Unsigned Byte | GL_UNSIGNED_BYTE |
349 | | BYTE | 1 | Byte | GL_BYTE |
350 | | USHORT | 2 | Unsigned Short | GL_UNSIGNED_SHORT |
351 | | SHORT | 2 | Short | GL_SHORT |
352 | | UINT | 4 | Int | GL_UNSIGNED_INT |
353 | | INT | 4 | Int | GL_INT |
354 |
355 | 总之就是映射了类型名称,类型位宽,gl枚举
356 |
357 | 枚举`VertexFormatElement.Usage`
358 | 每个枚举包含三个字段`String name`,`SetupState setupState`,`ClearState clearState`
359 | mc已定义了如下
360 |
361 | | name | setup | clear |
362 | |--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|
363 | | Position | enableVertexAttribArray(stateIndex) vertexAttribPointer(index,size,type,normalized false,stride,pointer) | disableVertexAttribArray(index) |
364 | | Normal | enableVertexAttribArray(stateIndex) vertexAttribPointer(index,size,type,normalized true,stride,pointer) | disableVertexAttribArray(index) |
365 | | Vertex Color | enableVertexAttribArray(stateIndex) vertexAttribPointer(index,size,type,normalized true,stride,pointer) | disableVertexAttribArray(index) |
366 | | UV | enableVertexAttribArray(stateIndex)
float:vertexAttribPointer(index,size,type,normalized false,stride,pointer)
int:vertexAttribIPointer(index,size,type,stride,pointer) | disableVertexAttribArray(index) |
367 | | Padding | | |
368 | | Generic | enableVertexAttribArray(stateIndex) vertexAttribPointer(index,size,type,normalized true,stride,pointer) | disableVertexAttribArray(index) |
369 |
370 | 几乎就是定义了`attribute`设定的所有参数了
371 |
372 | 这个`Padding`并不会占用实际的内存,在`BufferBuilder`内,会直接跳过
373 | ```java
374 | public void nextElement() {
375 | ImmutableList immutablelist = this.format.getElements();
376 | this.elementIndex = (this.elementIndex + 1) % immutablelist.size();
377 | this.nextElementByte += this.currentElement.getByteSize();
378 | VertexFormatElement vertexformatelement = immutablelist.get(this.elementIndex);
379 | this.currentElement = vertexformatelement;
380 | if (vertexformatelement.getUsage() == VertexFormatElement.Usage.PADDING) { //!!看这
381 | this.nextElement();
382 | }
383 |
384 | if (this.defaultColorSet && this.currentElement.getUsage() == VertexFormatElement.Usage.COLOR) {
385 | BufferVertexConsumer.super.color(this.defaultR, this.defaultG, this.defaultB, this.defaultA);
386 | }
387 | }
388 | ```
389 |
390 | ## VertexFormatElement
391 |
392 | 而`VertexFormatElement`则则更进一步,将attribute与type绑定
393 | 在类`DefaultVertexFormat`内可以查到所有的mc内定义的
394 |
395 | | name | index | type | usage | count |
396 | |------------------|-------|-------|----------|-------|
397 | | ELEMENT_POSITION | 0 | FLOAT | POSITION | 3 |
398 | | ELEMENT_COLOR | 0 | UBYTE | COLOR | 4 |
399 | | ELEMENT_UV0 | 0 | FLOAT | UV | 2 |
400 | | ELEMENT_UV1 | 1 | SHORT | UV | 2 |
401 | | ELEMENT_UV2 | 2 | SHORT | UV | 2 |
402 | | ELEMENT_NORMAL | 0 | BYTE | NORMAL | 3 |
403 | | ELEMENT_PADDING | 0 | BYTE | PADDING | 1 |
404 |
405 | 还有一个`ELEMENT_UV`与`ELEMENT_UV0`一致
406 |
407 | 如下是`VertexForamt`的构造函数
408 |
409 | ```java
410 | public VertexFormat(ImmutableMap pElementMapping) {
411 | this.elementMapping = pElementMapping;
412 | this.elements = pElementMapping.values().asList();
413 | int i = 0;
414 |
415 | for(VertexFormatElement vertexformatelement : pElementMapping.values()) {
416 | this.offsets.add(i);
417 | i += vertexformatelement.getByteSize();
418 | }
419 |
420 | this.vertexSize = i;
421 | }
422 | ```
423 |
424 | 可以看到`VertexFormat`即多个命名的`VertexFormatElement`合集
425 | 在类`DefaultVertexFormat`内可以查到所有的mc内定义的
426 |
427 | | name | content(name/element is special) |
428 | |-----------------------------|-------------------------------------------|
429 | | BLIT_SCREEN | Position,UV,Color |
430 | | BLOCK | Position,Color,UV0,UV2,Normal,Padding |
431 | | NEW_ENTITY | Position,Color,UV0,UV1,UV2,Normal,Padding |
432 | | PARTICLE | Position,UV0,Color,UV2 |
433 | | POSITION | Position |
434 | | POSITION_COLOR | Position,Color |
435 | | POSITION_COLOR_NORMAL | Position,Color,Normal |
436 | | POSITION_COLOR_LIGHTMAP | Position,Color,UV2 |
437 | | POSITION_TEX | Position,UV0 |
438 | | POSITION_COLOR_TEX | Position,Color,UV0 |
439 | | POSITION_TEX_COLOR | Position,UV0,Color |
440 | | POSITION_COLOR_TEX_LIGHTMAP | Position,Color,UV0,UV2 |
441 | | POSITION_TEX_LIGHTMAP_COLOR | Position,UV0,UV2,Color |
442 | | POSITION_TEX_COLOR_NORMAL | Position,UV0,Color,Normal,Padding |
443 |
444 | ## upload vertex
445 |
446 | 我们在调用`BufferBuilder`的方法为每个顶点传递顶点数据时,应与后方的定义顺序一致.并在单个顶点所需内容全传递完成后,调用`BufferBuilder#endVertex`
447 |
448 | 调用`BufferBuilder#end`,将在其内部产生一个`DrawState`,并且存储该次的相关数据`Format`,`VertexCount`,`IndexCount`,`Mode`,`IndexType`,`IndexOnly`
449 | ,`SequentialIndex`
450 |
451 | 调用`BufferUploader.end(BufferBuilder)`,间接调用`BufferUploader.updateVertexSetup(VertexFormat)`
452 | 其内部绑定了当前`Opengl context`的`VertexArrayObject`与`Buffer Object(存储顶点数据)`
453 | 又调用了`glBufferData`与`drawElements`,完成一切的工作
454 |
455 | ## upload index
456 |
457 | 至于`Buffer Object(存储顶点索引数据)`则是由`AutoStorageIndexBuffer`完成的
458 |
459 | 截取自`BufferUploader#_end`
460 | ```java
461 | int i = pVertexCount * pFormat.getVertexSize();
462 | if (pSequentialIndex) {
463 | RenderSystem.AutoStorageIndexBuffer rendersystem$autostorageindexbuffer = RenderSystem.getSequentialBuffer(pMode, pIndexCount);
464 | int indexBufferName = rendersystem$autostorageindexbuffer.name();
465 | if (indexBufferName != lastIndexBufferObject) {
466 | GlStateManager._glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferName);
467 | lastIndexBufferObject = indexBufferName;
468 | }
469 | } else {
470 | int indexBufferName = pFormat.getOrCreateIndexBufferObject();
471 | if (indexBufferName != lastIndexBufferObject) {
472 | GlStateManager._glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferName);
473 | lastIndexBufferObject = indexBufferName;
474 | }
475 | pBuffer.position(i);
476 | pBuffer.limit(i + pIndexCount * pIndexType.bytes);
477 | GlStateManager._glBufferData(GL_ELEMENT_ARRAY_BUFFER, pBuffer, GL_DYNAMIC_DRAW);
478 | }
479 | ```
480 |
481 | 上面那块,即if判断为true的时候执行的代码块(pSequentialIndex指的是在bufferBuilder时,有无指定过sortingPoints,没有则为true)
482 |
483 | 而那段`RenderSystem.getSequentialBuffer(pMode, pIndexCount)`
484 | ```java
485 | public static RenderSystem.AutoStorageIndexBuffer getSequentialBuffer(VertexFormat.Mode pFormatMode, int pNeededIndexCount) {
486 | assertOnRenderThread();
487 | RenderSystem.AutoStorageIndexBuffer rendersystem$autostorageindexbuffer;
488 | if (pFormatMode == VertexFormat.Mode.QUADS) {
489 | rendersystem$autostorageindexbuffer = sharedSequentialQuad;
490 | } else if (pFormatMode == VertexFormat.Mode.LINES) {
491 | rendersystem$autostorageindexbuffer = sharedSequentialLines;
492 | } else {
493 | rendersystem$autostorageindexbuffer = sharedSequential;
494 | }
495 |
496 | rendersystem$autostorageindexbuffer.ensureStorage(pNeededIndexCount);
497 | return rendersystem$autostorageindexbuffer;
498 | }
499 | ```
500 | 这里可能返回的三个值分别定义为
501 | ```java
502 | private static final RenderSystem.AutoStorageIndexBuffer sharedSequential
503 | = new RenderSystem.AutoStorageIndexBuffer(/*pVertexStride*/ 1,/*pIndexStride*/ 1, IntConsumer::accept);
504 | private static final RenderSystem.AutoStorageIndexBuffer sharedSequentialQuad
505 | = new RenderSystem.AutoStorageIndexBuffer(4, 6, (IntConsumer pConsumer, int pIndex) -> {
506 | pConsumer.accept(pIndex + 0);
507 | pConsumer.accept(pIndex + 1);
508 | pConsumer.accept(pIndex + 2);
509 | pConsumer.accept(pIndex + 2);
510 | pConsumer.accept(pIndex + 3);
511 | pConsumer.accept(pIndex + 0);
512 | });
513 | private static final RenderSystem.AutoStorageIndexBuffer sharedSequentialLines
514 | = new RenderSystem.AutoStorageIndexBuffer(4, 6,/*pGenerator*/ (pConsumer, pIndex) -> {
515 | pConsumer.accept(pIndex + 0);
516 | pConsumer.accept(pIndex + 1);
517 | pConsumer.accept(pIndex + 2);
518 | pConsumer.accept(pIndex + 3);
519 | pConsumer.accept(pIndex + 2);
520 | pConsumer.accept(pIndex + 1);
521 | });
522 | ```
523 |
524 | lambda在这里调用
525 | ```java
526 | void ensureStorage(int pNeededIndexCount) {
527 | if (pNeededIndexCount > this.indexCount) {
528 | pNeededIndexCount = Mth.roundToward(pNeededIndexCount * 2, this.indexStride);
529 | RenderSystem.LOGGER.debug("Growing IndexBuffer: Old limit {}, new limit {}.", this.indexCount, pNeededIndexCount);
530 | if (this.name == 0) {
531 | this.name = GlStateManager._glGenBuffers();
532 | }
533 |
534 | VertexFormat.IndexType vertexformat$indextype = VertexFormat.IndexType.least(pNeededIndexCount);
535 | int i = Mth.roundToward(pNeededIndexCount * vertexformat$indextype.bytes, 4);
536 | GlStateManager._glBindBuffer(34963, this.name);
537 | GlStateManager._glBufferData(34963, (long)i, 35048);
538 | ByteBuffer bytebuffer = GlStateManager._glMapBuffer(34963, 35001);
539 | if (bytebuffer == null) {
540 | throw new RuntimeException("Failed to map GL buffer");
541 | } else {
542 | this.type = vertexformat$indextype;
543 | it.unimi.dsi.fastutil.ints.IntConsumer intconsumer = this.intConsumer(bytebuffer);
544 |
545 | for(int j = 0; j < pNeededIndexCount; j += this.indexStride) {
546 | this.generator.accept(intconsumer, j * this.vertexStride / this.indexStride); //there
547 | }
548 |
549 | GlStateManager._glUnmapBuffer(34963);
550 | GlStateManager._glBindBuffer(34963, 0);
551 | this.indexCount = pNeededIndexCount;
552 | BufferUploader.invalidateElementArrayBufferBinding();
553 | }
554 | }
555 | }
556 | ```
557 |
558 | `this.indexCount`为该`AutoStorageIndexBuffer`已缓存的`index`数据,只有当有必要,即当前需要提交的
559 | `Buffer Object(顶点数据)`所需的`index`数量大于已缓存的数量前,才会重新生成,刷新,提交
560 | 所以函数名起`ensure`还算比较恰当?
561 |
562 | >[!note]
563 | > 如果你想要手动验证其中的内容
564 | > 注意数据访问的方式
565 | > 例如`(0 .. 100 step 2).map { bytebuffer.getShort(it) }`
566 | > 注意写入的数据类型和读取方式
567 | > 可查看`AutoStorageIndexBuffer#intConsumer`
568 |
569 | ## sort
570 |
571 | 对于Sorted的`BufferBuilder`,自身在调用`end`时,还会调用`putSortedQuadIndices`
572 |
573 | ```java
574 | private void putSortedQuadIndices(VertexFormat.IndexType pIndexType) {
575 | float[] afloat = new float[this.sortingPoints.length];
576 | int[] aint = new int[this.sortingPoints.length];
577 |
578 | for(int i = 0; i < this.sortingPoints.length; aint[i] = i++) {
579 | float f = this.sortingPoints[i].x() - this.sortX;
580 | float f1 = this.sortingPoints[i].y() - this.sortY;
581 | float f2 = this.sortingPoints[i].z() - this.sortZ;
582 | afloat[i] = f * f + f1 * f1 + f2 * f2;
583 | }
584 |
585 | IntArrays.mergeSort(aint, (p_166784_, p_166785_) -> {
586 | return Floats.compare(afloat[p_166785_], afloat[p_166784_]);
587 | });
588 | IntConsumer intconsumer = this.intConsumer(pIndexType);
589 | //intConsumer是对自身持有的ByteBuffer的包装,与上文的index相似
590 | this.buffer.position(this.nextElementByte);
591 |
592 | for(int j : aint) {
593 | intconsumer.accept(j * this.mode.primitiveStride + 0);
594 | intconsumer.accept(j * this.mode.primitiveStride + 1);
595 | intconsumer.accept(j * this.mode.primitiveStride + 2);
596 | intconsumer.accept(j * this.mode.primitiveStride + 2);
597 | intconsumer.accept(j * this.mode.primitiveStride + 3);
598 | intconsumer.accept(j * this.mode.primitiveStride + 0);
599 | }
600 |
601 | }
602 | ```
603 | 没错,`index`数据与`vertex`数据写入了同一个`ByteBuffer`
604 |
--------------------------------------------------------------------------------
/style.md:
--------------------------------------------------------------------------------
1 | # 特殊样式一览
2 | ---
3 | ## Diagram
4 | [插件地址](https://mermaid-js.github.io/mermaid/#/)
5 |
6 | ## Tab
7 | [插件地址](https://jhildenbiddle.github.io/docsify-tabs)
8 |
9 | #### **Tab1**
10 | thonk
11 | #### **Tab2**
12 | emmm?
13 |
14 |
15 | ## Flexible Alerts
16 | [插件地址](https://github.com/fzankl/docsify-plugin-flexible-alerts)
17 | >[!note]
18 | > 提示:
19 | > 在\[\!note]里的内容可以在index.html -> flexible-alerts中找到
20 | > 可用 : note tip attention warning
21 |
22 | ## Katex
23 | [插件地址](https://upupming.site/docsify-katex/docs/#/supported)
--------------------------------------------------------------------------------
/svg/overlayTexture.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------