├── .gitignore ├── .nojekyll ├── README.md ├── _coverpage.md ├── _sidebar.md ├── begin ├── check.md └── kt.md ├── customTags.css ├── functions.js ├── index.html ├── opengl ├── IntelGpuOpenglSupport.png ├── IntelPanel.png ├── IntroCoreProfile.md ├── VisualRenderPipeLine.png ├── explainImmediateMode.md ├── explainImmediateMode │ └── withoutGlClear.gif ├── init.md ├── memory.md ├── preparation.md ├── preparationImages │ ├── ColorfulTraiangle.png │ ├── SimpleWhiteTrinagle.png │ ├── callFunByAddress.png │ ├── createCapabilities.png │ ├── glfwCreateWindowComment.png │ ├── openglCapabilities.png │ ├── openglIsStandard.png │ └── retrieveFunAddress.png ├── renderPipeLine.png └── triangle.md ├── picture ├── blockModel │ ├── colorfulBlock.gif │ ├── exampleForMultiPart.png │ └── exampleForVariant.png ├── coordinateSystem │ ├── SDF_show.gif │ └── coordinateSystem.png ├── itemModel │ ├── colorfulChalk.gif │ ├── drawable_chalk.gif │ ├── empty.png │ ├── normalChalk.png │ ├── overrideExplanation.png │ └── weather_indicator.gif ├── overlayTexture │ └── tnt.gif ├── renderInLevel │ ├── renderInLevelBlock.png │ ├── renderInLevelFluid.png │ └── renderInLevelItem.png ├── renderType │ ├── blockRenderType.png │ └── chunkBufferLayers.png ├── shader │ └── dissolve.gif └── textureAtlas │ ├── animated_model.gif │ └── atlasblocks.png ├── plugin └── prism-treeview │ ├── prism-treeview.css │ └── prism-treeview.js ├── render ├── StateStore.md ├── TextureAtlas.md ├── blockModel.md ├── coordinateSystem.md ├── itemModel.md ├── misc.md ├── overlayTexture.md ├── overview.md ├── renderInLevel.md ├── renderType.md ├── shader.md └── vertexLife.md ├── style.md └── svg └── overlayTexture.svg /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/.nojekyll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cobalt 2 | --- 3 | 4 | Cobalt是一套Minecraft的渲染相关的说明文档 5 | 6 | github docs仓库:[![github仓库](https://shields.io/badge/github-CobaltDocs-blue?logo=Github&style=for-the-badge)](https://github.com/zomb-676/CobaltDocs) 7 | 工程仓库:[![github仓库](https://shields.io/badge/github-Cobalt-blue?logo=Github&style=for-the-badge)](https://github.com/zomb-676/CobaltDocs) 8 | discord服务器[![discord](https://shields.io/badge/DiscordServer-Rua-purple?logo=Discord&style=for-the-badge)](https://discord.gg/V4jmpZ2rMX) 9 | 邮箱[![outlook](https://shields.io/badge/outlook-zomb_676-pink?logo=MicrosoftOutlook&style=for-the-badge)](mailto:zomb_676@outlook.com) 10 | 开黑啦[邀请链接](https://kaihei.co/aesHF6) 11 | 12 | 有任何问题请在本项目的[问题追踪器](https://github.com/zomb-676/CobaltDocs/issues)指出 13 | 14 | > [!attention] 15 | > 黑幕内的内容带有强烈的个人观点,请选择观看 16 | 17 | > [!note] 18 | > 请仔细注意以下注意事项 19 | > * 本文含有opengl的内容,请务必耐心阅读 20 | > * 由于个人水平有限,错误在所难免,有改进的意见请发Pull Request 21 | > * **请确保你有足够的编程水平,这不是Java教程** 22 | > * 同样的,对Modded Minecraft也有一定的了解 23 | > * **文档内的代码提供kotlin/java版本,可通过右上角进行切换** 24 | > * Minecraft代码部分采用Forge+mojang表+idea parchment插件 25 | > * 并非所有局部遍历/参数都在parchment下有可读性高的名字,文内贴出代码经过可读性的改善,并不完全与代码相同 26 | > * 本文偏向使用英文,第一次出现的单词,会在()内标注参考中文,并不一定准确 27 | -------------------------------------------------------------------------------- /_coverpage.md: -------------------------------------------------------------------------------- 1 | # Cobalt 2 | 3 | > Minecraft渲染文档 4 | 5 | 6 | ![color](linear-gradient(45deg,#B3B8FF,#FFCBB3)) 7 | -------------------------------------------------------------------------------- /_sidebar.md: -------------------------------------------------------------------------------- 1 | - [序言](README.md) 2 | - Render 3 | - [State Store](render/StateStore.md) 4 | - [Vertex Life](render/vertexLife.md) 5 | - [RenderType](render/renderType.md) 6 | - [Shader](render/shader.md) 7 | - [OverlayTexture](render/overlayTexture.md) 8 | - [ItemModel](render/itemModel.md) 9 | - [BlockModel](render/blockModel.md) 10 | - [RenderInLevel](render/renderInLevel.md) 11 | - [textureAtlas](render/TextureAtlas.md) 12 | - [CoordinateSystem](render/coordinateSystem.md) 13 | - [Misc](render/misc.md) 14 | - [style](style.md) 15 | -------------------------------------------------------------------------------- /begin/check.md: -------------------------------------------------------------------------------- 1 | # 开始前的检验 2 | --- 3 | 下面将有一些常见的java方面的问题 4 | 请确保你知晓其中的绝大多数 5 | 6 | 好的,让我们开始吧 7 | 8 | 1. `throw`和`throws`有何区别? 9 | 10 | > throw用于抛出异常,出现于代码块中 11 | > throws用于声明函数会抛出的checked异常 12 | 13 | 2. Daemon Thread的Daemon指什么 14 | 15 | > 即守护线程(后台线程),进程的终止不需要Daemon Thread运行结束 16 | 17 | 3. Thread类下,start还是run用于启动一个新的线程 18 | 19 | > start 20 | 21 | 4. 请用简写一下代码 22 | 23 | ````java 24 | Thread Thread = new Thread(new Runnable(){ 25 | @Override 26 | public void run() { 27 | // 28 | } 29 | }); 30 | ```` 31 | 32 | > var Thread = new Thread(() -> {}); 33 | 34 | 5. XXX::new 与 new XXX()有何区别 35 | 36 | > XXX::new代表任意构造函数 37 | > 可能为Supplier,Function,BiFunction或其他 38 | > 39 | > new XXX()只代表调用无参构 40 | > 不太恰当的说,相当于Supplier.get() 41 | 42 | 6. 下列两个函数声明可否共存(在同一作用域下) 43 | 44 | ````javas 45 | public int FunA(int parameter); 46 | public long FunA(int parameter); 47 | ```` 48 | 49 | > 不可,java在语法层面并不允许返回值重载 50 | 51 | 7. 下列两个函数声明可否共存(在同一作用域下) 52 | 53 | ````javas 54 | public String FunA(List parameter); 55 | public String FunA(List parameter); 56 | ```` 57 | 58 | > 不可,在valhalla落地前,java的泛型会被擦除,例中两者都会被擦除为(Ljava/util/List;)Ljava/lang/String; 59 | > 且前者的泛型参数Int为原始类型,不可作为 60 | > 泛型参数,若使用应用其包装类Integer 61 | 62 | 8.@FunctionalInterface(函数式接口)是什么 63 | 64 | > 配合lambda使用,若有 65 | > 接口 66 | > @FunctionalInterface 67 | > Interface I{A invoke(B p);} 68 | > 函数 69 | > void Fun(Function); 70 | > void Fun(I); 71 | > 都可通过 72 | > Fun((p)->{/**/})调用 73 | 74 | -------------------------------------------------------------------------------- /begin/kt.md: -------------------------------------------------------------------------------- 1 | # 简要的Kt说明 2 | 3 | --- 4 | 5 | 变量声明 6 | >var mutable_parameter : Type = variable 7 | >val immutable_parameter : Type = variable 8 | > : Type可选会自动推断 9 | 10 | 构造对象 11 | > java:new XX(p1,p2) 12 | > kotlin:XX(p1,p2) 13 | 14 | lambda 15 | >java:function((p1,(p2,p3)->/*do xxx*/) 16 | >kotlin: 17 | > <=>function(p1,{p2,p3->/*do xxx*/}) 18 | > <=>function(p1){p2,p3->/*do xxx*/} 19 | >>特殊 20 | >>function({p1,p2->/*do xxx*/})<=>function{p1,p2->/*do xxx*/} 21 | > function{p1->p1.funA()}<=>function{it.funcA()} 22 | 23 | 解构 24 | >val map = mapOf("A" to 1) 25 | >val (key , value ) = map.iterator().next() 26 | >key为"A",value为1 -------------------------------------------------------------------------------- /customTags.css: -------------------------------------------------------------------------------- 1 | option { 2 | display: inline; 3 | color: rgba(0, 0, 0, 0); 4 | background-color: black; 5 | } 6 | 7 | option:hover { 8 | display: inline; 9 | color: white; 10 | } 11 | 12 | summary { 13 | list-style: none; 14 | } 15 | 16 | details { 17 | padding: 10px; 18 | background-color: #f8f8f8; 19 | margin-bottom: 10px; 20 | border: rgba(54, 146, 225, 0.12) solid 3px; 21 | border-radius: 10px; 22 | width: fit-content; 23 | } 24 | 25 | details[open] { 26 | width: auto; 27 | transition: width 2s, height 2s; 28 | } 29 | 30 | img { 31 | border: rgba(80, 140, 189, 0.26) 3px solid; 32 | border-radius: 10px; 33 | box-shadow: 5px 5px 6px; 34 | } 35 | 36 | pre[v-pre],pre[v-pre] code{ 37 | background-color : #2d2d2d; 38 | border-radius: 8px; 39 | } 40 | 41 | pre[v-pre] code{ 42 | color: #ccc; 43 | } 44 | 45 | .token.treeview-part .entry-name:before { 46 | filter: invert(75%) sepia(47%) saturate(0%) hue-rotate(153deg) brightness(102%) contrast(89%); 47 | } 48 | 49 | /*for toggle/switcher*/ 50 | 51 | .switch { 52 | position: relative; 53 | display: inline-block; 54 | width: 60px; 55 | height: 34px; 56 | } 57 | 58 | .switch input { 59 | opacity: 0; 60 | width: 0; 61 | height: 0; 62 | } 63 | 64 | .slider { 65 | position: absolute; 66 | cursor: pointer; 67 | top: 0; 68 | left: 0; 69 | right: 0; 70 | bottom: 0; 71 | background-color: #b0dee7; 72 | -webkit-transition: .4s; 73 | transition: .4s; 74 | border-radius: 34px; 75 | } 76 | 77 | .slider:before { 78 | position: absolute; 79 | content: ""; 80 | height: 26px; 81 | width: 26px; 82 | left: 4px; 83 | bottom: 4px; 84 | background-color: white; 85 | -webkit-transition: .4s; 86 | transition: .4s; 87 | border-radius: 50%; 88 | } 89 | 90 | input:checked + .slider { 91 | background-color: #2196F3; 92 | } 93 | 94 | input:focus + .slider { 95 | box-shadow: 0 0 1px #2196F3; 96 | } 97 | 98 | input:checked + .slider:before { 99 | -webkit-transform: translateX(26px); 100 | -ms-transform: translateX(26px); 101 | transform: translateX(26px); 102 | } 103 | 104 | .tooltiptext { 105 | visibility: hidden; 106 | width: 120px; 107 | background-color: black; 108 | color: #fff; 109 | text-align: center; 110 | border-radius: 6px; 111 | padding: 5px 0; 112 | position: absolute; 113 | z-index: 1; 114 | top: 150%; 115 | left: 50%; 116 | margin-left: -60px; 117 | } 118 | 119 | .tooltiptext::after { 120 | content: ""; 121 | position: absolute; 122 | bottom: 100%; 123 | left: 50%; 124 | margin-left: -5px; 125 | border-width: 5px; 126 | border-style: solid; 127 | border-color: transparent transparent black transparent; 128 | } 129 | 130 | .switch:hover + .tooltiptext{ 131 | visibility: visible; 132 | } -------------------------------------------------------------------------------- /functions.js: -------------------------------------------------------------------------------- 1 | function switchLanguageShow() { 2 | let switcher = document.getElementById("languageSwitcher"); 3 | let isJava = switcher.checked 4 | let containers = document.getElementsByClassName("docsify-tabs docsify-tabs--material") 5 | for (let container of containers) { 6 | if (container.getElementsByClassName("languageChangeAware").length === 0) { 7 | continue 8 | } 9 | let raw = container.innerHTML 10 | if (raw.includes("java")) { 11 | container.style.display = isJava ? "" : "none" 12 | } else if (raw.includes("kotlin")) { 13 | container.style.display = isJava ? "none" : "" 14 | } 15 | } 16 | 17 | for (let java of document.getElementsByClassName("languageChangeAware-java")) { 18 | java.style.display = isJava ? "" : "none" 19 | } 20 | for (let kt of document.getElementsByClassName("languageChangeAware-kotlin")) { 21 | kt.style.display = isJava ? "none" : "" 22 | } 23 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Cobalt 16 | 17 | 18 |
19 | 20 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /opengl/IntelGpuOpenglSupport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/IntelGpuOpenglSupport.png -------------------------------------------------------------------------------- /opengl/IntelPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/IntelPanel.png -------------------------------------------------------------------------------- /opengl/IntroCoreProfile.md: -------------------------------------------------------------------------------- 1 | # 核心模式 2 | 3 | --- 4 | 5 | # 介绍 6 | 7 | 据[wiki](https://www.khronos.org/opengl/wiki/OpenGL_Context#Context_types), 8 | `Core Profile(核心模式)`的引入是因为在OpenGL3.0,引入了废弃函数的感念 9 | 虽然在Opengl3.1就移除了就大多数过时的API,但是许多实现仍然支持废弃和被移除的动能 10 | 所以OpenGL提供了一种获取兼容指定版本的OpenGL的方法 11 | 并且上下文类型也由此分为两种模式(Profile): 12 | `Core Profile(核心模式)`和`CompatibilityProfile(兼容模式)` 13 | 14 | 可以通过`GLFW.glfwWindowHint`来指定 15 | 16 | > [!note] 17 | > 这个函数还可以指定一些别的参数 18 | > 可以参考lwjgl的[javadoc](https://javadoc.lwjgl.org/org/lwjgl/glfw/GLFW.html#glfwWindowHint(int,int)) 19 | > 或glfw的[docs](https://www.glfw.org/docs/latest/window_guide.html#window_hints_ctx) 20 | 21 | > [!note] 22 | > 可以请求的OpenGL版本可以在[这里](https://www.khronos.org/registry/OpenGL/index_gl.php)看到 23 | > 每一个规范都代表了一个版本 24 | 25 | # 切换 26 | 27 | 修改一下初始化代码 28 | 29 | ````kotlin 30 | init() 31 | 32 | GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR,4) 33 | GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR,6) 34 | GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE,GLFW.GLFW_OPENGL_CORE_PROFILE) 35 | GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT,GLFW.GLFW_FALSE) 36 | 37 | val window = createWindow(900, 900, "rua") 38 | GLFW.glfwMakeContextCurrent(window) 39 | GL.createCapabilities() 40 | ```` 41 | 42 | 再次运行我们的代码 43 | 44 | ```` 45 | FATAL ERROR in native method: Thread[main,5,main]: No context is current or a function that is not available in the current context was called. The JVM will abort execution. 46 | ```` 47 | 48 | 发生什么了? 49 | 经过一番查找 java启动后被默认分配为集成显卡 50 | 在笔者的电脑上为i7-7700HQ附带的Intel(R)HD Graphics 630 51 | 查阅[Intel文档](https://www.intel.com/content/www/us/en/support/articles/000005524/graphics.html) 52 | ![img.png](IntelGpuOpenglSupport.png) 53 | 但是 54 | ![img.png](IntelPanel.png) 55 | 在设备管理器检查了显卡驱动,是最新的 56 | 总之如果你也出现了上述的错误,可以查看一下你的显卡是否支持指定的OpenGL版本 57 | 所以我们切换成独立显卡试试 58 | 59 | > [!attention] 60 | > 获取的版本一定是兼容我们要求的版本,但不一定是指定的版本 61 | > 也就是说,我们会获取到比要求更高的版本 62 | > 如果要测试你所在的平台能支持的最大版本,应由高到低进行测试 63 | > 如果错误出现,会在glfwCreateWindow调时出现 64 | > > GLFW_CONTEXT_VERSION_MAJOR and GLFW_CONTEXT_VERSION_MINOR specify the client API version that the created context must be compatible with. The exact behavior of these hints depend on the requested client API. 65 | > 摘自[glfw docs](https://www.glfw.org/docs/latest/window.html#window_hints) 66 | 67 | ```` 68 | FATAL ERROR in native method: Thread[main,5,main]: No context is current or a function that is not available in the current context was called. The JVM will abort execution. 69 | at org.lwjgl.opengl.GL11.glBegin(Native Method) 70 | at zomb_676.cobalt.grahic.MainKt.main(Main.kt:27) 71 | at zomb_676.cobalt.grahic.MainKt.main(Main.kt) 72 | ```` 73 | 74 | 错误仍然存在,但是好像变了? 75 | 根据栈信息查询函数信息 76 | 77 | ````java 78 | /** 79 | * Begins the definition of vertex attributes of a sequence of primitives to be transferred to the GL. 80 | * 81 | * @param mode the primitive type being defined. One of:
{@link #GL_POINTS POINTS}{@link #GL_LINE_STRIP LINE_STRIP}{@link #GL_LINE_LOOP LINE_LOOP}{@link #GL_LINES LINES}{@link #GL_TRIANGLE_STRIP TRIANGLE_STRIP}{@link #GL_TRIANGLE_FAN TRIANGLE_FAN}{@link #GL_TRIANGLES TRIANGLES}
{@link GL32#GL_LINES_ADJACENCY LINES_ADJACENCY}{@link GL32#GL_LINE_STRIP_ADJACENCY LINE_STRIP_ADJACENCY}{@link GL32#GL_TRIANGLES_ADJACENCY TRIANGLES_ADJACENCY}{@link GL32#GL_TRIANGLE_STRIP_ADJACENCY TRIANGLE_STRIP_ADJACENCY}{@link GL40#GL_PATCHES PATCHES}{@link #GL_POLYGON POLYGON}{@link #GL_QUADS QUADS}
{@link #GL_QUAD_STRIP QUAD_STRIP}
82 | * 83 | * @see Reference Page - This function is deprecated and unavailable in the Core profile 84 | */ 85 | public static native void glBegin(@NativeType("GLenum") int mode); 86 | ```` 87 | 88 | 可以看到最后一行 89 | > This function is deprecated and unavailable in the Core profile 90 | 91 | 将 92 | 93 | ````kotlin 94 | GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE,GLFW.GLFW_OPENGL_CORE_PROFILE) 95 | GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT,GLFW.GLFW_FALSE) 96 | ```` 97 | 98 | 改为 99 | 100 | ````kotlin 101 | GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE,GLFW.GLFW_OPENGL_FORWARD_COMPAT) 102 | GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT,GLFW.GLFW_TRUE) 103 | ```` 104 | 105 | 理论上能够使报错消失 106 | 但正如wiki所说[**compatibility is not guaranteed to be available(兼容并不被保证)**](https://www.khronos.org/opengl/wiki/OpenGL_Context#Context_types) 107 | 108 | 再我们讲述如何在`Core Profile`下渲染一个简单的三角形前 109 | 110 | 我们先介绍一下LWJGL中内存相关的操作 -------------------------------------------------------------------------------- /opengl/VisualRenderPipeLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/VisualRenderPipeLine.png -------------------------------------------------------------------------------- /opengl/explainImmediateMode.md: -------------------------------------------------------------------------------- 1 | # 立即模式 2 | 3 | --- 4 | 5 | ## 一些补充 6 | 7 | `GLFW.glfwWindowShouldClose(window)`用于判断窗口是否需要关闭 8 | 与他配套使用的是`GLFW.glfwPollEvents()`,用于处理所有待办事件 例如窗口移动,调整窗口大小,窗口关闭 9 | 也就是说,如果不调用`glfwPollEvents()`,`glfwWindwosShouldClose(window)`是不会返回的true的 10 | 11 | `GL11.glBegin(mode)`和`GL11.glEnd()`成对调用 前者表示开始收集数据,后者表示停止收集 12 | 13 | ## 顶点 14 | 15 | `GL11.glVertex2f(x,y)`和`GL11.glColor3f(r,g,b)`用于提交所谓的顶点数据 16 | `顶点`可以理解为空间中点的信息的集合,可以包含坐标,颜色等信息 17 | 18 | 有了点,又通过`GL11.glBegin(mode)`的mode指定的,就我们就得到了一个个的形状 19 | 这一个个形状称之为`primitive(图元)`,将点变为图元的过程称之为`shape assembly(图元装配)` 20 | 21 | 绘制好后,我们需要调用`GLFW.glfwSwapBuffers(window)` 22 | 将绘制好的内容交换到屏幕上显示出来 23 | 因为画面不是一瞬间绘制出来的,通过双缓冲,可以避免图像闪烁 24 | 25 | `GL11.glClear(GL11.GL_COLOR_BUFFER_BIT)`会清除画布上的内容 否则就会这样(色块感是因为gif只有256色) 26 |
27 | click to unfold 28 | 29 | ![withoutGlClear](explainImmediateMode/withoutGlClear.gif) 30 |
31 | 32 |
33 | code 34 | 35 | ````kotlin 36 | var offset = 0f.toFloat() 37 | while (!GLFW.glfwWindowShouldClose(window)) { 38 | //GL11.glClear(GL11.GL_COLOR_BUFFER_BIT) 39 | 40 | GL11.glBegin(GL11.GL_TRIANGLES) 41 | GL11.glVertex2f(0f, 0.5f + offset) 42 | GL11.glColor3f(1f, 0f, 0f) 43 | GL11.glVertex2f(-0.5f, -0.5f+ offset) 44 | GL11.glColor3f(0f, 1f, 0f) 45 | GL11.glVertex2f(0.5f, -0.5f+ offset) 46 | GL11.glColor3f(0f, 0f, 1f) 47 | GL11.glEnd() 48 | 49 | offset+=0.001f 50 | 51 | GLFW.glfwSwapBuffers(window) 52 | GLFW.glfwPollEvents() 53 | } 54 | ```` 55 | 56 |
57 | 58 | > ![!note] 59 | > 有些内容暂且按下不表 60 | > 为何glVertex2f中传入的参数都在-1\~1 (不严格) 61 | > 为何glColor3f中传入的参数为0\~1 62 | 63 | 从上我们不难发现绘制的过程可以这样表示,我们需要做的仅仅是其中的第一部分 64 | 65 | ````mmd 66 | flowchart LR 67 | 收集顶点数据 --> 图元装配 --> 绘制 68 | ```` 69 | 70 | 这种绘制的流程称之为**Immediate Mode(立即模式)** 71 | 它的优点成为了它的缺点,它太过简单,也太缺乏灵活行(不可配置),我们无法控制其中的其他部分,也缺乏性能 72 | Stack Over Flow上[有篇](https://stackoverflow.com/questions/6733934/what-does-immediate-mode-mean-in-opengl) 73 | 相关内容 74 | 75 | 而与之相对而言对立的概念称之为**Core Profile(核心模式)** 76 | 77 | > ![!note] 78 | > 立即模式已经过时 79 | > 本文使用仅仅为了能让读者尽早写出一个程序 80 | > 并且对顶点有一个粗浅的认识 -------------------------------------------------------------------------------- /opengl/explainImmediateMode/withoutGlClear.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/explainImmediateMode/withoutGlClear.gif -------------------------------------------------------------------------------- /opengl/init.md: -------------------------------------------------------------------------------- 1 | # 初试 2 | 3 | --- 4 | 5 | ## 大致流程 6 | 7 | 我们的目标是 8 | 9 | ````mmd 10 | flowchart LR 11 | 绘制画面 12 | ```` 13 | 14 | 但是在此之前我们要初始化opengl,在窗口关闭后要进行收尾 15 | 16 | ````mmd 17 | flowchart LR 18 | 初始化opengl --> 绘制画面 --窗口关闭--> 收尾 19 | ```` 20 | 21 | 画面每帧都有可能变化,所以我们要不断更新画面 22 | 23 | ````mmd 24 | flowchart LR 25 | 初始化opengl --> 绘制画面 --窗口关闭--> 收尾 26 | 绘制画面 --更新画面--> 绘制画面 27 | ```` 28 | 29 | ## 初始化opengl 30 | 31 | 调用`GLFW.glfwInit():boolean` 32 | 返回true为成功,false为失败 所以我们可以这样 33 | 34 | ````kotlin 35 | @Throws(RuntimeException::class) 36 | fun init() { 37 | if (!GLFW.glfwInit()) { 38 | throw RuntimeException("failed to init glfw") 39 | } 40 | } 41 | ```` 42 | 43 | 然后调用即可 44 | 45 | --- 46 | 47 | 调用`GLFW.glfwCreateWindow(int width, int height,CharSequence title,long monitor,long share)` 48 | 来创建窗口 49 | `monitor`和`share`可以传入`MemoryUtil.NULL` 50 | `MemoryUtil.NULL`查定义可知就是0L,只不过这样可读性更高,在kt也不会报错 51 | ![img.png](preparationImages/glfwCreateWindowComment.png) 52 | 53 | 封装一下 54 | 55 | ````kotlin 56 | fun createWindow(width: Int, height: Int, title: String): Long = 57 | GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL) 58 | ```` 59 | 60 | 调用 61 | 62 | ````kotlin 63 | val window = createWindow(900, 900, "rua") 64 | ```` 65 | 66 | --- 67 | 调用`glfwMakeContextCurrent(long window)` 68 | 使得我们刚才创建的那个窗口的opengl上下文(状态)处在当前线程 69 | 一个线程有且仅有一个opengl上下文且在设置新的上下文前,旧的必须被移除 70 | 如果不太清楚什么是Context(上下文)的话,可以理解为opengl的一堆状态的合集 71 | 或者说它对应了一个单例对象,对象内有许多字段表明了当前的状态 72 | 73 | > An OpenGL context represents many things. A context stores all of the state associated with this instance of OpenGL 74 | > 摘自wiki[OpenGL context](https://www.khronos.org/opengl/wiki/OpenGL_Context)的开头 75 | 76 | --- 77 | 78 | 调用`GL.createCapabilities()` 79 | 具体是在获取我们需要用到的opengl函数的实际地址 80 | ![img.png](preparationImages/createCapabilities.png) 81 | ![img.png](preparationImages/retrieveFunAddress.png) 82 | 然后通过获取的函数的地址来调用真正的函数 83 | ![img.png](preparationImages/callFunByAddress.png) 84 | **因为opengl只是规范!!真正的函数实现在显卡驱动内** 85 | ![img.png](preparationImages/openglIsStandard.png) 86 | 只能通过函数的地址来间接调用函数 这里的capabilities具体可以在这里找到 87 | ![img.png](preparationImages/openglCapabilities.png) 88 | 89 | --- 90 | 91 | 调用`GLFW.glfwMakeContextCurrent(window)` 92 | 把Capabilities绑定到当前线程 如果忘记保存之前的window handle 93 | 可以调用`GLFW.glfwGetCurrentContext()`获取当前线程的window handle 94 | 95 | ````kotlin 96 | GLFW.glfwMakeContextCurrent(window) 97 | ```` 98 | 99 | --- 100 | 101 | ## 收尾 102 | 103 | 在我们正式开始渲染前,我们首先处理收尾工作 104 | 105 | 调用`GLFW.glfwDestroyWindow(window)` 106 | 关闭窗口和它的上下文 调用`GLFW.glfwTerminate()` 107 | 终止GLFW库的剩余内容 108 | 109 | ````kotlin 110 | GLFW.glfwDestroyWindow(window) 111 | GLFW.glfwTerminate() 112 | ```` 113 | 114 | --- 115 | 116 | ## 初次绘制 117 | 118 | >[!note] 119 | >这里只是概览,下一章将会详细解释 120 | 121 | 写下我们的绘制循环 122 | 123 | ````kotlin 124 | while(!GLFW.glfwWindowShouldClose(window)){ 125 | GL11.glClear(GL11.GL_COLOR_BUFFER_BIT) 126 | //draw logic 127 | GLFW.glfwSwapBuffers(window) 128 | GLFW.glfwPollEvents() 129 | } 130 | ```` 131 | 132 | 先绘制一个普普通通的三角形吧 133 | 134 | ````kotlin 135 | GL11.glBegin(GL11.GL_TRIANGLES) 136 | GL11.glVertex2f(0f, 0.5f) 137 | GL11.glVertex2f(-0.5f, -0.5f) 138 | GL11.glVertex2f(0.5f, -0.5f) 139 | GL11.glEnd() 140 | ```` 141 | 142 | ![img.png](preparationImages/SimpleWhiteTrinagle.png) 143 | 修改一下,来点色彩 144 | 145 | ````kotlin 146 | GL11.glBegin(GL11.GL_TRIANGLES) 147 | GL11.glColor3f(1f, 0f, 0f) 148 | GL11.glVertex2f(0f, 0.5f) 149 | GL11.glColor3f(0f, 1f, 0f) 150 | GL11.glVertex2f(-0.5f, -0.5f) 151 | GL11.glColor3f(0f, 0f, 1f) 152 | GL11.glVertex2f(0.5f, -0.5f) 153 | GL11.glEnd() 154 | ```` 155 | 156 | ![img.png](preparationImages/ColorfulTraiangle.png) 157 | 很好! -------------------------------------------------------------------------------- /opengl/memory.md: -------------------------------------------------------------------------------- 1 | # 内存 2 | 3 | --- 4 | 5 | > [!note] 6 | > 如果没有接触过c式的部分内存概念可能会有上手障碍 7 | > 有相关经验者可直接跳过本章 8 | > 本章与[LWJGL的介绍](https://github.com\/LWJGL/lwjgl3-wiki/wiki/1.3.-Memory-FAQ)几乎一致 9 | 10 | 由于lwjgl是一个java库,而glfw是一个c库 11 | 所以lwjgl提供了一些方法让我们能够使用堆外内存 12 | 13 | # stack memory 14 | 15 | ````java 16 | int vbo; 17 | try (MemoryStack stack = stackPush()) { 18 | IntBuffer ip = stack.callocInt(1); 19 | glGenBuffers(ip); 20 | vbo = ip.get(0); 21 | } // stack automatically popped, ip memory automatically reclaimed 22 | ```` 23 | 24 | 在kt中,我们可以这样 25 | 26 | ````kotlin 27 | inline fun memoryStack(function:MemoryStack.()->Unit){ 28 | MemoryStack.stackPush().use { 29 | function.invoke(it) 30 | } 31 | } 32 | ```` 33 | 34 | # heap memory (manually) 35 | 36 | 分配内存 37 | `ByteBuffer = MemoryUtil.memAlloc(int size)` 38 | 以及各式的 39 | `IBuffer=MemoryUtil.memAllocI(int size)` 40 | I可以代表`int` `long` `short` `double` `float`以及 `point` 41 | 用完用`MemoryUtil.memFree(buffer)`释放 42 | 43 | 当然还有 44 | `MemoryUtil.memRealloc(point,size)`重分配内存 45 | point可以用 46 | `MemoryUtil.memAddress(buffer)`拿到 47 | `buffer memDuplicate(buffer)`复制一份 48 | 49 | 等等 50 | 51 | # heap memory (gc) 52 | 53 | `IBuffer BufferUtils.createIBuffer(capacity)`,I与上文类似 54 | 55 | 这样获取的内存会被GC管理,但是需要两次GC 56 | 57 | --- 58 | 59 | # 总结 60 | 61 | | 方法 | 适用对象 | 62 | |-------------------------------|---------| 63 | | org.lwjgl.system.MemoryStack | 小且短周期对象 | 64 | | org.lwjgl.system.MemoryUtil | 生命周期已知 | 65 | | org.lwjgl.BufferUtil | 生命周期未知 | 66 | -------------------------------------------------------------------------------- /opengl/preparation.md: -------------------------------------------------------------------------------- 1 | # 准备工作 2 | 3 | --- 4 | 5 | ## 约定 6 | * 函数介绍若无特殊说明,则函数名即为其作用,即见名知意 7 | * OpenGL为api,需要大量的函数调用,本文以目标->细分实施->函数调用为方法进行介绍 8 | 9 | ## FAQ 10 | 11 | > 为什么从opengl讲起而不是直接从mc的代码入手? 12 | > mc配上forge已有一层抽象层,难以直接从底层入手 13 | 14 | > 为什么用kotlin不用java 15 | > 用法简洁好用且内联函数很有用 16 | 17 | > 为什么使用imgui,它的作用是什么 18 | > imgui是一个立即模式的UI编写库,写一些需要交互的操作会很方便 19 | 20 | ## 参考资料 21 | 22 | 1. [learnOpenGL CN](https://learnopengl-cn.github.io/) 23 | 2. Cherno的opengl教程 24 | 1. [国内搬运](https://www.bilibili.com/video/BV1MJ411u7Bc) 25 | 2. 国外原地址 26 | 1. [opengl](https://www.youtube.com/watch?v=W3gAzLwfIP0) 27 | 2. [Batch Rendering](https://www.youtube.com/watch?v=Th4huqR77rI) 28 | 3. [docs.gl](https://docs.gl/) 29 | 4. [opengl wiki](https://www.khronos.org/opengl/wiki/) 30 | 5. [opengl spec](https://www.khronos.org/registry/OpenGL/specs/gl/) 31 | 32 | > [!warning] 33 | > 内容与参考资料相似度可能过高 34 | 35 | 36 | > [!note] 37 | > 所有上述除learn opengl cn都为英文 38 | > 所有上述都为cpp编写 39 | 40 | ## 依赖项目 41 | 42 | ### LWJGL 43 | 44 | [lwjgl仓库](https://github.com/LWJGL/lwjgl3) 45 | 本文编写时候lwjgl版本号为3.3.0 46 | [自定义配置地址](https://www.lwjgl.org/customize) 47 | 下面的runtimeOnly配置不可省,否则ide内运行时会找不到native library 48 | 49 | ### Imgui 50 | 51 | Java的binging在 52 | [官方链接](https://github.com/ocornut/imgui/wiki/Bindings) 53 | 上有两个 54 | 本文选择的是 55 | https://github.com/SpaiR/imgui-java 56 | 57 | ### Kotlin标准库 58 | 59 | [Maven中心仓库](https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib) 60 | 61 | ### Kotlin协程库 62 | 63 | [Maven中心仓库](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core) -------------------------------------------------------------------------------- /opengl/preparationImages/ColorfulTraiangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/preparationImages/ColorfulTraiangle.png -------------------------------------------------------------------------------- /opengl/preparationImages/SimpleWhiteTrinagle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/preparationImages/SimpleWhiteTrinagle.png -------------------------------------------------------------------------------- /opengl/preparationImages/callFunByAddress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/preparationImages/callFunByAddress.png -------------------------------------------------------------------------------- /opengl/preparationImages/createCapabilities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/preparationImages/createCapabilities.png -------------------------------------------------------------------------------- /opengl/preparationImages/glfwCreateWindowComment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/preparationImages/glfwCreateWindowComment.png -------------------------------------------------------------------------------- /opengl/preparationImages/openglCapabilities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/preparationImages/openglCapabilities.png -------------------------------------------------------------------------------- /opengl/preparationImages/openglIsStandard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/preparationImages/openglIsStandard.png -------------------------------------------------------------------------------- /opengl/preparationImages/retrieveFunAddress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/preparationImages/retrieveFunAddress.png -------------------------------------------------------------------------------- /opengl/renderPipeLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/opengl/renderPipeLine.png -------------------------------------------------------------------------------- /opengl/triangle.md: -------------------------------------------------------------------------------- 1 | # 三角形,又一次? 2 | 3 | > [!note] 4 | > 本章较长,请耐心观看 5 | 6 | [上文](explainImmediateMode.md)我们提到的渲染流程为 7 | 8 | ````mmd 9 | flowchart LR 10 | 收集顶点数据 --> 图元装配 --> 绘制 11 | ```` 12 | 13 | 但要切换到`Core Profile`,我们要修改一下 14 | 15 | ````mmd 16 | flowchart LR 17 | 收集顶点数据 --> 处理顶点数据 --> 图元装配 --> 处理像素 -->绘制 18 | ```` 19 | 20 | 换用专用的术语,修改一下流程 21 | 22 | ````mmd 23 | flowchart LR 24 | VertexPuller --> VertexShader(顶点着色器) --> 图元装配 --> FragmentShader(片元/片段着色器) --> DrawCall 25 | ```` 26 | 27 | > [!note] 28 | > 上图简化自 29 | > ![renderPipeLine](renderPipeLine.png) 30 | > 出自opengl规范4.6 35页 31 | > 为了便于理解以及没有想到更好的办法就翻译成上文了 32 | 33 | ![VisualRenderPipeLine.png](VisualRenderPipeLine.png) 34 | 引用自LearnOpenglCN 35 | 36 | `Shader(着色器)`,[定义]([参考自](https://www.khronos.org/opengl/wiki/Shader)) 37 | 为用户编写的运行在GPU上的程序 38 | 39 | # Vertex Buffer Object 40 | 41 | 之前提到,我们需要将顶点数据提交到显卡来绘制我们所需的对象 42 | 而在`Core Profile`模式下,提交顶点数据方法由`glVertex2f` `glColor3f ` 43 | 修改为`VBO`即 44 | `Vertex Buffer Object(顶点缓冲对象)`用于存储将要发送给GPU的数据 45 | 46 | ````java 47 | /** 48 | * Generates buffer object names. 49 | * 50 | * @see Reference Page 51 | */ 52 | NativeType("void") 53 | public static int glGenBuffers() { 54 | return GL15C.glGenBuffers(); 55 | } 56 | ```` 57 | 58 | 创建VBO,返回值为所创建VBO的索引,在参数形参名中有时称之为的name 59 | 60 | ````java 61 | /** 62 | * Binds a named buffer object. 63 | * 64 | * @param buffer the name of a buffer object 65 | * 66 | * @see Reference Page 67 | */ 68 | public static void glBindBuffer(@NativeType("GLenum") int target, @NativeType("GLuint") int buffer) { 69 | GL15C.glBindBuffer(target, buffer); 70 | } 71 | ```` 72 | 73 | 用于将指定的VBO绑定到当前上下文 74 | 75 | > [!note] 76 | > 在OpenGl中,我们上文产生的VBO对象称之为Buffer Objects 77 | > 里面可以存储各种不同的类型数据,包括但不仅限于顶点数据,详见[wiki](https://www.khronos.org/opengl/wiki/Buffer_Object) 78 | > 数据的类型通过target指定,这里我们使用`GL_ARRAY_BUFFER` 79 | 80 | 81 | 82 | #### **JVM** 83 | 84 | ````kotlin 85 | val vbo: Int =GL43.glGenBuffers() 86 | .also { GL43.glBindBuffer(GL43.GL_ARRAY_BUFFER, it) } 87 | ```` 88 | 89 | #### **Memory Stack** 90 | 91 | ````kotlin 92 | val vbo = MemoryStack.stackPush().use { 93 | val point = it.mallocInt(0) 94 | GL43.glGenBuffers(point) 95 | point.get(0) 96 | } 97 | ```` 98 | 99 | 100 | 101 | # Allocate Memory on ram 102 | 103 | 104 | 105 | #### **JVM** 106 | 107 | ````kotlin 108 | val data: FloatArray = floatArrayOf( 109 | 0f, 0.5f, 1f, 0f, 0f, 110 | -0.5f, -0.5f, 0f, 1f, 0f, 111 | 0.5f, -0.5f, 0f, 0f, 1f 112 | ) 113 | ```` 114 | 115 | #### **Native** 116 | 117 | ````kotlin 118 | val data: FloatBuffer = MemoryUtil.memAllocFloat(15).apply { 119 | put(0f).put(0.5f).put(1f).put(0f).put(0f) 120 | put(-0.5f).put(-0.5f).put(0f).put(1f).put(0f) 121 | put(0.5f).put(-0.5f).put(0f).put(0f).put(1f) 122 | flip() 123 | } 124 | ```` 125 | 126 | > [!note] 127 | > 注意flip不能省 128 | 129 | 130 | 131 | # Upload Data 132 | 133 | 通过 134 | 135 | ````java 136 | public static void glBufferData(int target,FloatBuffer/float[] data,int usage) { 137 | GL15C.glBufferData(target, data, usage); 138 | } 139 | ```` 140 | 141 | `target`与上文相同,代表Buffer Object内数据的类型,此处同样填入`GL_ARRAY_BUFFER` 142 | `data`即数据在RAM上的容器两种方式分配的内存都有对应的重载 143 | 144 | > [!note] 145 | > 这里的data也可以给一个MemoryUtil.NONE 146 | > 在之后再提供数据 147 | 148 | `usage`指你与OpenGL**约定**你将如何使用/访问分配的缓存 149 | 有`Draw` `READ` `COPY`表示你会如何访问 150 | 有`STATIC` `DYNAMIC` `STREAM`表示修改数据的频次 151 | 152 | > [!note] 153 | > usage仅为约定,并不代表实际如何使用 154 | > 仅仅用于指示OpenGL用于优化 155 | > 比如:指定`DYNAMIC/STREAM`也行会比`STATIC`为你分配更高速的显存 156 | 157 | 这里我们 158 | 159 | ````kotlin 160 | GL43.glBufferData(GL43.GL_ARRAY_BUFFER, data, GL43.GL_STATIC_DRAW) 161 | ```` 162 | 163 | # Vertex Shader 164 | 165 | 在`Vertex Shader`,我们将获得我们提交的顶点数据 166 | 并且在这一步决定每一个顶点在屏幕上所处的位置 167 | 并且将数据传给下一步,即为`Fragment Shader` 168 | 169 | ````glsl 170 | #version 460 core 171 | 172 | layout (location = 0) in vec2 pos; 173 | layout (location = 1) in vec3 m_color; 174 | 175 | out vec3 color; 176 | 177 | void main(){ 178 | color = m_color; 179 | gl_Position = vec4(pos,0.5f,1.0f); 180 | } 181 | ```` 182 | 183 | `#version 460 core`用于显示指定版本 184 | 185 | `layout (location = 0) in vec2 pos;` 186 | `layout (location = 1) in vec3 m_color;` 187 | 指定了我们输入数据的结构 188 | 189 | > [!note] 190 | > 这种指定方式被称之为`In-shader specification` 191 | > 有另一种在非glsl中指定的方式为`Pre-link specification` 192 | > 当然,还有缺省方法为`Automatic assignment` 193 | > 在`Fragment Shader`中同理 194 | > 在[wiki](https://www.khronos.org/opengl/wiki/Vertex_Shader#Input) 195 | > 有详细的介绍 196 | 197 | `layout`为关键字 198 | `in`代表数据为输入,在`Vertex Shader`即为我们提交的数据 199 | 200 | | slot | code | type | code | variable | code | 201 | |-------|---------------|----------|------|------------------|---------| 202 | | 指定槽位0 | location = 0 | 二维浮点类型向量 | vec2 | 指定对应的变量名为pos | pos | 203 | | 指定槽位1 | location = 1 | 三位浮点类型向量 | vec3 | 指定对应的变量名为m_color | m_color | 204 | 205 | `out vec3 color;`代表我们输出的数据 206 | 207 | 对输出而言,并不存在槽位的概念 208 | `color` 代表输出变量名,需与`Fragment Shader`内的输入变量名一致 209 | 210 | ````glsl 211 | void main(){ 212 | //do xxx 213 | } 214 | ```` 215 | 216 | `color = m_color;`将输入变量m_color赋值给输出变量`color` 217 | 218 | `gl_Position = vec4(pos,0.5f,1.0f);` 219 | 220 | `gl_Position`是OpenGL的一个内建变量 221 | 表示了对应顶点输出到`clip-space(剪切/裁剪空间)`的位置 222 | `vec4`可理解为一个接收四个参数,返回一个vec4的函数 223 | 但由于[`Swizzling(重组)`](https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Swizzling) 224 | 特性的存在,我们可以这样很很方便只传两个参数 225 | 226 | # Swizzling 227 | 228 | 假设我们有`vec4 v`和`vec2 v2` 229 | 那么其中的各个分量依次可以为 230 | `v.x` `v.y` `v.z` `v.w` 231 | `v.r` `v.g` `v.b` `v.a`(color) 232 | `v.s` `v.t` `v.p` `v.q`(texture coordinates) 233 | 234 | `xyzw` `rgba` `stpq` 均等效 235 | 236 | 那么 237 | `v.xy = vec2(v.x,x.y)` 238 | `v.xzw = vec3(v.x,v.z,v.w)` 239 | `v.xx = vec2(v.x,v.x)` 240 | 241 | `vec4(v.xz,v2.x,v2.y) = vec4(v.xz,v2)` 242 | 243 | # Fragment Shader 244 | 245 | > [!note] 246 | > 这是一个[可选](https://www.khronos.org/opengl/wiki/Fragment_Shader#Optional) Shader 247 | > 但是如果不指定的画,我们是无法指定内容的 248 | 249 | 在`Fragment Shader`中,我们将决定每一个像素的颜色 250 | 我们可以从这里接收到来自顶点着色器的输出作为输入 251 | 252 | ````glsl 253 | #version 460 core 254 | 255 | in vec3 color; 256 | 257 | out vec4 FragColor; 258 | 259 | void main(){ 260 | FragColor = vec4(color,1.0f); 261 | } 262 | ```` 263 | 264 | `in vec3 color`在这里等价于`layout (location = 0 ) vec3 color` 265 | 前者的写法是由于利用了`Automatic assignment` 266 | `Fragment Shader`是可以拥有多个输出的 267 | 268 | 这里的vec4指代的是red,green,blue,alpha 269 | 270 | # Vertex Attribute 271 | 272 | `Vertex Attribute(顶点属性)`即我们前文一直所说的顶点格式/结构 273 | 274 | 需要与`Vertex Shader`中`layout`相对应 275 | 276 | ````java 277 | glVertexAttribPointer(int index,int size,int type,bool normalized,int stride long pointer) 278 | ```` 279 | 280 | 用于指定`Vertex Attrubute` 281 | 282 | > [!note] 283 | > 它有几个几乎同名的方法 284 | > `glVertexAttribIPointer` 285 | > `glVertexAttribPointer` 286 | > `glVertexAttribLPointer` 287 | > 它们的区别仅在于允许使用的`type`参数不同 288 | > 以及使用的VBO的数据的类型不同 289 | > 具体见[wiki](https://docs.gl/gl4/glVertexAttribPointer) 290 | 291 | `index(索引)`即前文所指槽位,与`layout (location = n)`中的n应保持一致 292 | `size`指有变量的维数/数量,`float<=>1`,`vec2<=>2`,`vec3<=>3`,`vec4<=>4` 293 | `type`即float,int等 294 | `normalized`指数据是否已经被`标准化`,将会在后续章节详细介绍 295 | `stride(步长?)`,指一个顶点数据的总长度,以字节为单位 296 | `pointer`是指第一个数据相对起始地址的偏移量,也以字节为单位 297 | 298 | 指定完成需要使用 299 | `glEnableVertexAttribArray(int index)`来开启 300 | `glDisableVertexAttribArray(int index)`与之对应 301 | 302 | ````kotlin 303 | GL43.glVertexAttribPointer(0, 2, GL43.GL_FLOAT, true, TypeSize.float * 5, 0) 304 | GL43.glEnableVertexAttribArray(0) 305 | GL43.glVertexAttribPointer(1, 3, GL43.GL_FLOAT, true, TypeSize.float * 5, (TypeSize.float * 2).toLong()) 306 | GL43.glEnableVertexAttribArray(1) 307 | ```` 308 | 309 |
310 | TypeSize 311 | 312 | 简单编写的用于计算类型字节大小的代码 313 | 314 | ````kotlin 315 | @JvmInline 316 | value class TypeSize private constructor(val byteSize: Int) { 317 | companion object { 318 | val int = TypeSize(Int.SIZE_BYTES) 319 | val float = TypeSize(Float.SIZE_BYTES) 320 | val double = TypeSize(Double.SIZE_BYTES) 321 | val long = TypeSize(Long.SIZE_BYTES) 322 | val short = TypeSize(Short.SIZE_BYTES) 323 | val byte = TypeSize(Byte.SIZE_BYTES) 324 | } 325 | 326 | operator fun times(num: Int): Int = byteSize * num 327 | infix fun size(num: Int): Int = byteSize * num 328 | 329 | operator fun get(num: Int): TypeSize = TypeSize(byteSize * num) 330 | infix fun multi(num: Int): TypeSize = TypeSize(byteSize * num) 331 | } 332 | ```` 333 | 334 |
335 | 336 | # Vertex Array Object 337 | 338 | ````java 339 | glDrawArrays(@NativeType("GLenum") int mode, @NativeType("GLint") int first, @NativeType("GLsizei") int count) 340 | ```` 341 | 342 | `first` `count` 代表从第几个顶点起绘制几个几点 343 | 344 | `GL43.glDrawArrays(GL43.GL_TRIANGLES, 0, 3)`运行! 345 | em-mm?一篇黑屏? 346 | 347 | 这是因为我们缺少了对于`Vertex Array Object(顶点数组对象的配置)` 348 | 里面包含了`Vertex Format`,`Vertex Buffer Object`的相关配 349 | 在`core profile`下,若不指定 350 | 将会抛出错误`GL_INVALID_OPERATION error generated. Array object is not active.` 351 | 错误级别为`GL_DEBUG_TYPE_ERROR` 352 | 353 | 如果你不指定GLFWWindowHint的画是可以正常运行的 354 | OpenGL会帮你创建一个默认的VAO 355 | 356 | `glGenVertexArrays` `glBindVertexArray` `glDeleteVertexArrays` `GL43.glIsVertexArray` 357 | 用于生成,绑定,删除,判断VAO 358 | 359 | > [!note] 360 | > 大部分OpenGL对象都拥有与之相似的 361 | > `glGenXXX` `glBindXXX` `glDeleteXXX` `glIsXXX` 362 | > `Buffer Object`用于是一个`generic(通用)`的容器,所以参数会有所不同 363 | 364 | VBO有两种与之配套的使用方式,具体依据你对数据的绑定时机不同而作区分 365 | 366 | ## Way1 367 | 368 | 与上文相同,即`glVertexAttribPointer`,但是这里我们补充下细节 369 | 370 | 上文说到,VAO中绑定了与VBO有关的信息 371 | 在调用`glVertexAttribPointer`时 372 | 会对应当前上下文中所绑定的`VBO`即`GL_ARRAY_BUFFER` 373 | 如果在调用时,上下文中尚未绑定`VBO` 374 | 375 | 就会引发JVM崩溃... 376 | 377 | ````plaintext 378 | # 379 | # A fatal error has been detected by the Java Runtime Environment: 380 | # 381 | # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffc874d71b0, pid=19340, tid=9784 382 | # 383 | # JRE version: OpenJDK Runtime Environment (17.0.1+12) (build 17.0.1+12-39) 384 | # Java VM: OpenJDK 64-Bit Server VM (17.0.1+12-39, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, windows-amd64) 385 | # Problematic frame: 386 | # C [nvoglv64.dll+0xbf71b0] 387 | # 388 | # No core dump will be written. Minidumps are not enabled by default on client versions of Windows 389 | # 390 | # An error report file with more information is saved as: 391 | # C:\github\Cobalt\hs_err_pid19340.log 392 | # 393 | # If you would like to submit a bug report, please visit: 394 | # https://bugreport.java.com/bugreport/crash.jsp 395 | # The crash happened outside the Java Virtual Machine in native code. 396 | # See problematic frame for where to report the bug. 397 | # 398 | ```` 399 | 400 | 从`EXCEPTION_ACCESS_VIOLATION (0xc0000005)`可以?推测出应该时`Null Pointer Exception`了 401 | 402 | 所以,从绑定的时机,不难推测,一个`VAO`的每个`Vertex Attribute`可以来自不同的`VBO` 403 | 404 | ## Way2 405 | 406 | Way1的方法有一个问题: 407 | 他将[两件事同时在一起做了](https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex%20Buffer%20Object) 即 408 | `指定Vertex Attribute`和`指定VBO` 409 | 通过Way2,就可以将这两件事单独指定 410 | 411 | ````java 412 | public static void glVertexAttribFormat(int attribindex,int size,int type,boolean normalized,int stride,int relativeoffset) 413 | ```` 414 | 415 | `attribueindex`与原本使用的函数`glVertexAttribPointer`的参数`index`同义 416 | 即为`Vertex Shader`中`layout (location = n)`中的n 417 | `relativeoffset`与`glVertexAttribPointer`的参数`pointer`相似 418 | 419 | 这个函数仅仅指定了`Vertex Shader`特定槽位/索引`的`Vertex Attribute` 420 | 而这个设置的`index` 421 | 422 | ````java 423 | public static void glBindVertexBuffer(int bindingindex,int buffer,long offset,int stride) { 424 | ```` 425 | 426 | `buffer`指的就是我们绑定的`VBO`的name/id 427 | `stride`仍指单个顶点的长度,以字节为单位 428 | `offset`仍指开始读取第一个顶点数据前的起始偏移 429 | 430 | `bingingindex`是用于链接`VBO`和`Vertex Attribute` 431 | 432 | 而链接的方法则是 433 | 434 | ````java 435 | glVertexAttribBinding(int attributeindex,int binding index) 436 | ```` 437 | 438 | 相同`bindingindex`即绑定在一起了 439 | 440 | 所以代码变成了 441 | 442 | ````kotlin 443 | GL43.glEnableVertexAttribArray(0) 444 | GL43.glVertexAttribFormat(0,2,GL43.GL_FLOAT,true,0) 445 | GL43.glVertexAttribBinding(0,1) 446 | 447 | GL43.glEnableVertexAttribArray(1) 448 | GL43.glVertexAttribFormat(1,3,GL43.GL_FLOAT,true,TypeSize.float * 2) 449 | GL43.glVertexAttribBinding(1,1) 450 | 451 | GL43.glBindVertexBuffer(1,vbo,0,TypeSize.float * 5) 452 | ```` 453 | -------------------------------------------------------------------------------- /picture/blockModel/colorfulBlock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/blockModel/colorfulBlock.gif -------------------------------------------------------------------------------- /picture/blockModel/exampleForMultiPart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/blockModel/exampleForMultiPart.png -------------------------------------------------------------------------------- /picture/blockModel/exampleForVariant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/blockModel/exampleForVariant.png -------------------------------------------------------------------------------- /picture/coordinateSystem/SDF_show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/coordinateSystem/SDF_show.gif -------------------------------------------------------------------------------- /picture/coordinateSystem/coordinateSystem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/coordinateSystem/coordinateSystem.png -------------------------------------------------------------------------------- /picture/itemModel/colorfulChalk.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/itemModel/colorfulChalk.gif -------------------------------------------------------------------------------- /picture/itemModel/drawable_chalk.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/itemModel/drawable_chalk.gif -------------------------------------------------------------------------------- /picture/itemModel/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/itemModel/empty.png -------------------------------------------------------------------------------- /picture/itemModel/normalChalk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/itemModel/normalChalk.png -------------------------------------------------------------------------------- /picture/itemModel/overrideExplanation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/itemModel/overrideExplanation.png -------------------------------------------------------------------------------- /picture/itemModel/weather_indicator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/itemModel/weather_indicator.gif -------------------------------------------------------------------------------- /picture/overlayTexture/tnt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/overlayTexture/tnt.gif -------------------------------------------------------------------------------- /picture/renderInLevel/renderInLevelBlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/renderInLevel/renderInLevelBlock.png -------------------------------------------------------------------------------- /picture/renderInLevel/renderInLevelFluid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/renderInLevel/renderInLevelFluid.png -------------------------------------------------------------------------------- /picture/renderInLevel/renderInLevelItem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/renderInLevel/renderInLevelItem.png -------------------------------------------------------------------------------- /picture/renderType/blockRenderType.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/renderType/blockRenderType.png -------------------------------------------------------------------------------- /picture/renderType/chunkBufferLayers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/renderType/chunkBufferLayers.png -------------------------------------------------------------------------------- /picture/shader/dissolve.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/shader/dissolve.gif -------------------------------------------------------------------------------- /picture/textureAtlas/animated_model.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/textureAtlas/animated_model.gif -------------------------------------------------------------------------------- /picture/textureAtlas/atlasblocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zomb-676/CobaltDocs/63d951b65a63133250416f66ca8dbcaf6dea7a04/picture/textureAtlas/atlasblocks.png -------------------------------------------------------------------------------- /plugin/prism-treeview/prism-treeview.css: -------------------------------------------------------------------------------- 1 | .treeview-part .entry-line { 2 | position: relative; 3 | text-indent: -99em; 4 | display: inline-block; 5 | vertical-align: top; 6 | width: 1.2em; 7 | } 8 | .treeview-part .entry-line:before, 9 | .treeview-part .line-h:after { 10 | content: ''; 11 | position: absolute; 12 | top: 0; 13 | left: 50%; 14 | width: 50%; 15 | height: 100%; 16 | } 17 | .treeview-part .line-h:before, 18 | .treeview-part .line-v:before { 19 | border-left: 1px solid #ccc; 20 | } 21 | .treeview-part .line-v-last:before { 22 | height: 50%; 23 | border-left: 1px solid #ccc; 24 | border-bottom: 1px solid #ccc; 25 | } 26 | .treeview-part .line-h:after { 27 | height: 50%; 28 | border-bottom: 1px solid #ccc; 29 | } 30 | .treeview-part .entry-name { 31 | position: relative; 32 | display: inline-block; 33 | vertical-align: top; 34 | padding: 0 0 0 1.5em; 35 | } 36 | .treeview-part .entry-name:before { 37 | content: ''; 38 | position: absolute; 39 | top: 0; 40 | left: 0.25em; 41 | height: 100%; 42 | width: 1em; 43 | background: no-repeat 50% 50% / contain; 44 | } 45 | 46 | .treeview-part .entry-name.dotfile { 47 | opacity: 0.5; 48 | } 49 | 50 | .treeview-part .entry-name:before { 51 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogIiAvPg0KCTwvZz4NCjwvc3ZnPg=='); 52 | } 53 | .treeview-part .entry-name.dir:before { 54 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTUzNiAyMjR2NzA0cTAgNDAgLTI4IDY4dC02OCAyOGgtNzA0cS00MCAwIC02OCAyOHQtMjggNjh2NjRxMCA0MCAtMjggNjh0LTY4IDI4aC0zMjBxLTQwIDAgLTY4IC0yOHQtMjggLTY4di05NjBxMCAtNDAgMjggLTY4dDY4IC0yOGgxMjE2cTQwIDAgNjggMjh0MjggNjh6TTE2NjQgOTI4di03MDRxMCAtOTIgLTY2IC0xNTh0LTE1OCAtNjZoLTEyMTZxLTkyIDAgLTE1OCA2NnQtNjYgMTU4djk2MHEwIDkyIDY2IDE1OHQxNTggNjZoMzIwIHE5MiAwIDE1OCAtNjZ0NjYgLTE1OHYtMzJoNjcycTkyIDAgMTU4IC02NnQ2NiAtMTU4eiIgLz4NCgk8L2c+DQo8L3N2Zz4='); 55 | 56 | } 57 | .treeview-part .entry-name.ext-bmp:before, 58 | .treeview-part .entry-name.ext-eps:before, 59 | .treeview-part .entry-name.ext-gif:before, 60 | .treeview-part .entry-name.ext-jpe:before, 61 | .treeview-part .entry-name.ext-jpg:before, 62 | .treeview-part .entry-name.ext-jpeg:before, 63 | .treeview-part .entry-name.ext-png:before, 64 | .treeview-part .entry-name.ext-svg:before, 65 | .treeview-part .entry-name.ext-tiff:before { 66 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTEyODAgMzIwdi0zMjBoLTEwMjR2MTkybDE5MiAxOTJsMTI4IC0xMjhsMzg0IDM4NHpNNDQ4IDUxMnEtODAgMCAtMTM2IDU2dC01NiAxMzZ0NTYgMTM2dDEzNiA1NnQxMzYgLTU2dDU2IC0xMzZ0LTU2IC0xMzZ0LTEzNiAtNTZ6IiAvPg0KCTwvZz4NCjwvc3ZnPg=='); 67 | } 68 | .treeview-part .entry-name.ext-cfg:before, 69 | .treeview-part .entry-name.ext-conf:before, 70 | .treeview-part .entry-name.ext-config:before, 71 | .treeview-part .entry-name.ext-csv:before, 72 | .treeview-part .entry-name.ext-ini:before, 73 | .treeview-part .entry-name.ext-log:before, 74 | .treeview-part .entry-name.ext-md:before, 75 | .treeview-part .entry-name.ext-nfo:before, 76 | .treeview-part .entry-name.ext-txt:before { 77 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTM4NCA3MzZxMCAxNCA5IDIzdDIzIDloNzA0cTE0IDAgMjMgLTl0OSAtMjN2LTY0cTAgLTE0IC05IC0yM3QtMjMgLTloLTcwNHEtMTQgMCAtMjMgOXQtOSAyM3Y2NHpNMTEyMCA1MTJxMTQgMCAyMyAtOXQ5IC0yM3YtNjRxMCAtMTQgLTkgLTIzdC0yMyAtOWgtNzA0cS0xNCAwIC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDcwNHpNMTEyMCAyNTZxMTQgMCAyMyAtOXQ5IC0yM3YtNjRxMCAtMTQgLTkgLTIzdC0yMyAtOWgtNzA0IHEtMTQgMCAtMjMgOXQtOSAyM3Y2NHEwIDE0IDkgMjN0MjMgOWg3MDR6IiAvPg0KCTwvZz4NCjwvc3ZnPg=='); 78 | } 79 | .treeview-part .entry-name.ext-asp:before, 80 | .treeview-part .entry-name.ext-aspx:before, 81 | .treeview-part .entry-name.ext-c:before, 82 | .treeview-part .entry-name.ext-cc:before, 83 | .treeview-part .entry-name.ext-cpp:before, 84 | .treeview-part .entry-name.ext-cs:before, 85 | .treeview-part .entry-name.ext-css:before, 86 | .treeview-part .entry-name.ext-h:before, 87 | .treeview-part .entry-name.ext-hh:before, 88 | .treeview-part .entry-name.ext-htm:before, 89 | .treeview-part .entry-name.ext-html:before, 90 | .treeview-part .entry-name.ext-jav:before, 91 | .treeview-part .entry-name.ext-java:before, 92 | .treeview-part .entry-name.ext-js:before, 93 | .treeview-part .entry-name.ext-php:before, 94 | .treeview-part .entry-name.ext-rb:before, 95 | .treeview-part .entry-name.ext-xml:before { 96 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTQ4MCA3NjhxOCAxMSAyMSAxMi41dDI0IC02LjVsNTEgLTM4cTExIC04IDEyLjUgLTIxdC02LjUgLTI0bC0xODIgLTI0M2wxODIgLTI0M3E4IC0xMSA2LjUgLTI0dC0xMi41IC0yMWwtNTEgLTM4cS0xMSAtOCAtMjQgLTYuNXQtMjEgMTIuNWwtMjI2IDMwMXEtMTQgMTkgMCAzOHpNMTI4MiA0NjdxMTQgLTE5IDAgLTM4bC0yMjYgLTMwMXEtOCAtMTEgLTIxIC0xMi41dC0yNCA2LjVsLTUxIDM4cS0xMSA4IC0xMi41IDIxdDYuNSAyNGwxODIgMjQzIGwtMTgyIDI0M3EtOCAxMSAtNi41IDI0dDEyLjUgMjFsNTEgMzhxMTEgOCAyNCA2LjV0MjEgLTEyLjV6TTY2MiA2cS0xMyAyIC0yMC41IDEzdC01LjUgMjRsMTM4IDgzMXEyIDEzIDEzIDIwLjV0MjQgNS41bDYzIC0xMHExMyAtMiAyMC41IC0xM3Q1LjUgLTI0bC0xMzggLTgzMXEtMiAtMTMgLTEzIC0yMC41dC0yNCAtNS41eiIgLz4NCgk8L2c+DQo8L3N2Zz4='); 97 | } 98 | .treeview-part .entry-name.ext-7z:before, 99 | .treeview-part .entry-name.ext-bz:before, 100 | .treeview-part .entry-name.ext-bz2:before, 101 | .treeview-part .entry-name.ext-gz:before, 102 | .treeview-part .entry-name.ext-rar:before, 103 | .treeview-part .entry-name.ext-tar:before, 104 | .treeview-part .entry-name.ext-tgz:before, 105 | .treeview-part .entry-name.ext-zip:before { 106 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNNjQwIDExNTJ2MTI4aC0xMjh2LTEyOGgxMjh6TTc2OCAxMDI0djEyOGgtMTI4di0xMjhoMTI4ek02NDAgODk2djEyOGgtMTI4di0xMjhoMTI4ek03NjggNzY4djEyOGgtMTI4di0xMjhoMTI4ek0xNDY4IDExNTZxMjggLTI4IDQ4IC03NnQyMCAtODh2LTExNTJxMCAtNDAgLTI4IC02OHQtNjggLTI4aC0xMzQ0cS00MCAwIC02OCAyOHQtMjggNjh2MTYwMHEwIDQwIDI4IDY4dDY4IDI4aDg5NnE0MCAwIDg4IC0yMHQ3NiAtNDh6TTEwMjQgMTQwMCB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC0xMjh2LTEyOGgtMTI4djEyOGgtNTEydi0xNTM2aDEyODB6TTc4MSA1OTNsMTA3IC0zNDlxOCAtMjcgOCAtNTJxMCAtODMgLTcyLjUgLTEzNy41dC0xODMuNSAtNTQuNXQtMTgzLjUgNTQuNXQtNzIuNSAxMzcuNXEwIDI1IDggNTJxMjEgNjMgMTIwIDM5NnYxMjhoMTI4di0xMjhoNzkgcTIyIDAgMzkgLTEzdDIzIC0zNHpNNjQwIDEyOHE1MyAwIDkwLjUgMTl0MzcuNSA0NXQtMzcuNSA0NXQtOTAuNSAxOXQtOTAuNSAtMTl0LTM3LjUgLTQ1dDM3LjUgLTQ1dDkwLjUgLTE5eiIgLz4NCgk8L2c+DQo8L3N2Zz4='); 107 | } 108 | .treeview-part .entry-name.ext-aac:before, 109 | .treeview-part .entry-name.ext-au:before, 110 | .treeview-part .entry-name.ext-cda:before, 111 | .treeview-part .entry-name.ext-flac:before, 112 | .treeview-part .entry-name.ext-mp3:before, 113 | .treeview-part .entry-name.ext-oga:before, 114 | .treeview-part .entry-name.ext-ogg:before, 115 | .treeview-part .entry-name.ext-wav:before, 116 | .treeview-part .entry-name.ext-wma:before { 117 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTYyMCA2ODZxMjAgLTggMjAgLTMwdi01NDRxMCAtMjIgLTIwIC0zMHEtOCAtMiAtMTIgLTJxLTEyIDAgLTIzIDlsLTE2NiAxNjdoLTEzMXEtMTQgMCAtMjMgOXQtOSAyM3YxOTJxMCAxNCA5IDIzdDIzIDloMTMxbDE2NiAxNjdxMTYgMTUgMzUgN3pNMTAzNyAtM3EzMSAwIDUwIDI0cTEyOSAxNTkgMTI5IDM2M3QtMTI5IDM2M3EtMTYgMjEgLTQzIDI0dC00NyAtMTRxLTIxIC0xNyAtMjMuNSAtNDMuNXQxNC41IC00Ny41IHExMDAgLTEyMyAxMDAgLTI4MnQtMTAwIC0yODJxLTE3IC0yMSAtMTQuNSAtNDcuNXQyMy41IC00Mi41cTE4IC0xNSA0MCAtMTV6TTgyNiAxNDVxMjcgMCA0NyAyMHE4NyA5MyA4NyAyMTl0LTg3IDIxOXEtMTggMTkgLTQ1IDIwdC00NiAtMTd0LTIwIC00NC41dDE4IC00Ni41cTUyIC01NyA1MiAtMTMxdC01MiAtMTMxcS0xOSAtMjAgLTE4IC00Ni41dDIwIC00NC41cTIwIC0xNyA0NCAtMTd6IiAvPg0KCTwvZz4NCjwvc3ZnPg=='); 118 | } 119 | .treeview-part .entry-name.ext-avi:before, 120 | .treeview-part .entry-name.ext-flv:before, 121 | .treeview-part .entry-name.ext-mkv:before, 122 | .treeview-part .entry-name.ext-mov:before, 123 | .treeview-part .entry-name.ext-mp4:before, 124 | .treeview-part .entry-name.ext-mpeg:before, 125 | .treeview-part .entry-name.ext-mpg:before, 126 | .treeview-part .entry-name.ext-ogv:before, 127 | .treeview-part .entry-name.ext-webm:before { 128 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTc2OCA3NjhxNTIgMCA5MCAtMzh0MzggLTkwdi0zODRxMCAtNTIgLTM4IC05MHQtOTAgLTM4aC0zODRxLTUyIDAgLTkwIDM4dC0zOCA5MHYzODRxMCA1MiAzOCA5MHQ5MCAzOGgzODR6TTEyNjAgNzY2cTIwIC04IDIwIC0zMHYtNTc2cTAgLTIyIC0yMCAtMzBxLTggLTIgLTEyIC0ycS0xNCAwIC0yMyA5bC0yNjUgMjY2djkwbDI2NSAyNjZxOSA5IDIzIDlxNCAwIDEyIC0yeiIgLz4NCgk8L2c+DQo8L3N2Zz4='); 129 | } 130 | .treeview-part .entry-name.ext-pdf:before { 131 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTg5NCA0NjVxMzMgLTI2IDg0IC01NnE1OSA3IDExNyA3cTE0NyAwIDE3NyAtNDlxMTYgLTIyIDIgLTUycTAgLTEgLTEgLTJsLTIgLTJ2LTFxLTYgLTM4IC03MSAtMzhxLTQ4IDAgLTExNSAyMHQtMTMwIDUzcS0yMjEgLTI0IC0zOTIgLTgzcS0xNTMgLTI2MiAtMjQyIC0yNjJxLTE1IDAgLTI4IDdsLTI0IDEycS0xIDEgLTYgNXEtMTAgMTAgLTYgMzZxOSA0MCA1NiA5MS41dDEzMiA5Ni41cTE0IDkgMjMgLTZxMiAtMiAyIC00cTUyIDg1IDEwNyAxOTcgcTY4IDEzNiAxMDQgMjYycS0yNCA4MiAtMzAuNSAxNTkuNXQ2LjUgMTI3LjVxMTEgNDAgNDIgNDBoMjFoMXEyMyAwIDM1IC0xNXExOCAtMjEgOSAtNjhxLTIgLTYgLTQgLThxMSAtMyAxIC04di0zMHEtMiAtMTIzIC0xNCAtMTkycTU1IC0xNjQgMTQ2IC0yMzh6TTMxOCA1NHE1MiAyNCAxMzcgMTU4cS01MSAtNDAgLTg3LjUgLTg0dC00OS41IC03NHpNNzE2IDk3NHEtMTUgLTQyIC0yIC0xMzJxMSA3IDcgNDRxMCAzIDcgNDNxMSA0IDQgOCBxLTEgMSAtMSAydC0wLjUgMS41dC0wLjUgMS41cS0xIDIyIC0xMyAzNnEwIC0xIC0xIC0ydi0yek01OTIgMzEzcTEzNSA1NCAyODQgODFxLTIgMSAtMTMgOS41dC0xNiAxMy41cS03NiA2NyAtMTI3IDE3NnEtMjcgLTg2IC04MyAtMTk3cS0zMCAtNTYgLTQ1IC04M3pNMTIzOCAzMjlxLTI0IDI0IC0xNDAgMjRxNzYgLTI4IDEyNCAtMjhxMTQgMCAxOCAxcTAgMSAtMiAzeiIgLz4NCgk8L2c+DQo8L3N2Zz4='); 132 | } 133 | .treeview-part .entry-name.ext-xls:before, 134 | .treeview-part .entry-name.ext-xlsx:before { 135 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTQyOSAxMDZ2LTEwNmgyODF2MTA2aC03NWwxMDMgMTYxcTUgNyAxMCAxNi41dDcuNSAxMy41dDMuNSA0aDJxMSAtNCA1IC0xMHEyIC00IDQuNSAtNy41dDYgLTh0Ni41IC04LjVsMTA3IC0xNjFoLTc2di0xMDZoMjkxdjEwNmgtNjhsLTE5MiAyNzNsMTk1IDI4Mmg2N3YxMDdoLTI3OXYtMTA3aDc0bC0xMDMgLTE1OXEtNCAtNyAtMTAgLTE2LjV0LTkgLTEzLjVsLTIgLTNoLTJxLTEgNCAtNSAxMHEtNiAxMSAtMTcgMjNsLTEwNiAxNTloNzZ2MTA3IGgtMjkwdi0xMDdoNjhsMTg5IC0yNzJsLTE5NCAtMjgzaC02OHoiIC8+DQoJPC9nPg0KPC9zdmc+'); 136 | } 137 | .treeview-part .entry-name.ext-doc:before, 138 | .treeview-part .entry-name.ext-docm:before, 139 | .treeview-part .entry-name.ext-docx:before { 140 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTIzMyA3Njh2LTEwN2g3MGwxNjQgLTY2MWgxNTlsMTI4IDQ4NXE3IDIwIDEwIDQ2cTIgMTYgMiAyNGg0bDMgLTI0cTEgLTMgMy41IC0yMHQ1LjUgLTI2bDEyOCAtNDg1aDE1OWwxNjQgNjYxaDcwdjEwN2gtMzAwdi0xMDdoOTBsLTk5IC00MzhxLTUgLTIwIC03IC00NmwtMiAtMjFoLTRsLTMgMjFxLTEgNSAtNCAyMXQtNSAyNWwtMTQ0IDU0NWgtMTE0bC0xNDQgLTU0NXEtMiAtOSAtNC41IC0yNC41dC0zLjUgLTIxLjVsLTQgLTIxaC00bC0yIDIxIHEtMiAyNiAtNyA0NmwtOTkgNDM4aDkwdjEwN2gtMzAweiIgLz4NCgk8L2c+DQo8L3N2Zz4='); 141 | } 142 | .treeview-part .entry-name.ext-pps:before, 143 | .treeview-part .entry-name.ext-ppt:before, 144 | .treeview-part .entry-name.ext-pptx:before { 145 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxNzkuMiIgd2lkdGg9IjE3OS4yIj4NCgk8Zz4NCgkJPHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjEsLTAuMSkgdHJhbnNsYXRlKDAsLTE1MzYpIiBkPSJNMTQ2OCAxMTU2cTI4IC0yOCA0OCAtNzZ0MjAgLTg4di0xMTUycTAgLTQwIC0yOCAtNjh0LTY4IC0yOGgtMTM0NHEtNDAgMCAtNjggMjh0LTI4IDY4djE2MDBxMCA0MCAyOCA2OHQ2OCAyOGg4OTZxNDAgMCA4OCAtMjB0NzYgLTQ4ek0xMDI0IDE0MDB2LTM3NmgzNzZxLTEwIDI5IC0yMiA0MWwtMzEzIDMxM3EtMTIgMTIgLTQxIDIyek0xNDA4IC0xMjh2MTAyNGgtNDE2cS00MCAwIC02OCAyOHQtMjggNjh2NDE2aC03Njh2LTE1MzZoMTI4MHogTTQxNiAxMDZ2LTEwNmgzMjd2MTA2aC05M3YxNjdoMTM3cTc2IDAgMTE4IDE1cTY3IDIzIDEwNi41IDg3dDM5LjUgMTQ2cTAgODEgLTM3IDE0MXQtMTAwIDg3cS00OCAxOSAtMTMwIDE5aC0zNjh2LTEwN2g5MnYtNTU1aC05MnpNNzY5IDM4NmgtMTE5djI2OGgxMjBxNTIgMCA4MyAtMThxNTYgLTMzIDU2IC0xMTVxMCAtODkgLTYyIC0xMjBxLTMxIC0xNSAtNzggLTE1eiIgLz4NCgk8L2c+DQo8L3N2Zz4='); 146 | } -------------------------------------------------------------------------------- /plugin/prism-treeview/prism-treeview.js: -------------------------------------------------------------------------------- 1 | Prism.languages.treeview = { 2 | "treeview-part": { 3 | pattern: /(^|\n).+/, 4 | inside: { 5 | "entry-line": [ 6 | { 7 | pattern: /\|-- |├── /, 8 | alias: "line-h" 9 | }, 10 | { 11 | pattern: /\| |│ /, 12 | alias: "line-v" 13 | }, 14 | { 15 | pattern: /`-- |└── /, 16 | alias: "line-v-last" 17 | }, 18 | { 19 | pattern: / {4}/, 20 | alias: "line-v-gap" 21 | } 22 | ], 23 | "entry-name": { 24 | pattern: /.*\S.*/, 25 | inside: { 26 | // symlink 27 | "operator": / -> /, 28 | } 29 | } 30 | } 31 | } 32 | }; 33 | 34 | Prism.hooks.add('wrap', function(env) { 35 | if (env.language === 'treeview') { 36 | // Remove line breaks 37 | if(env.type === 'treeview-part') { 38 | env.content = env.content.replace(/\n/g,'')+'
'; 39 | } 40 | if(env.type === 'entry-name') { 41 | if(/(^|[^\\])\/\s*$/.test(env.content)) { 42 | env.content = env.content.slice(0,-1); 43 | // This is a folder 44 | env.classes.push('dir'); 45 | } else { 46 | 47 | if(/(^|[^\\])[=*|]\s*$/.test(env.content)) { 48 | env.content = env.content.slice(0,-1); 49 | } 50 | 51 | var parts = env.content.toLowerCase().split('.'); 52 | while (parts.length > 1) { 53 | parts.shift(); 54 | // Ex. 'foo.min.js' would become 'foo.min.js' 55 | env.classes.push('ext-' + parts.join('-')); 56 | } 57 | } 58 | 59 | if(env.content.charAt(0)==='.') { 60 | env.classes.push('dotfile'); 61 | } 62 | } 63 | } 64 | }); -------------------------------------------------------------------------------- /render/StateStore.md: -------------------------------------------------------------------------------- 1 | # State Store 2 | 3 | --- 4 | 5 | 在出现[DSA(Direct State Access)](https://www.khronos.org/opengl/wiki/Direct_State_Access)前,OpenGL一直都几乎仅有基于状态机设计的API 6 | 可惜直到OpenGL4.5才正式进入规范,而mc的要求仅式OpenGL3.2 7 | 如果是mod是Sodium/Rubidium的附属,则可以无视这点,此mod以要求OpenGL4.5,参见[此](https://github.com/CaffeineMC/sodium-fabric/commit/01321b0082cea08316b74eff374227d12bb50645) 8 | 9 | 因此,为了减少GUP-CPU通信及状态切换的开销,在cpu侧会对当前状态进行缓存,并在每次尝试调用前检查缓存以提高性能。 10 | 11 | minecraft对此的实现有如下 12 | 13 | > [!tip] 14 | > 并不是每一个被存储的类型,都会核实是否需要更新,有些仅起了记录的作用 15 | 16 | ## GlStateManager 17 | 18 | * BLEND 19 | * DEPTH 20 | * CULL 21 | * POLY_OFFSET 22 | * COLOR_LOGIC 23 | * STENCIL 24 | * SCISSOR 25 | * activeTexture 26 | * TEXTURES(TextureState) 27 | * COLOR_MASK 28 | 29 | ## RenderSystem 30 | 31 | mc的常用uniform 32 | * inverseViewRotationMatrix 33 | * projectionMatrix 34 | * savedProjectionMatrix 35 | * modelViewStack 36 | * modelViewMatrix 37 | * textureMatrix 38 | * shaderColor 39 | * shaderFogStart 40 | * shaderFogEnd 41 | * shaderFogColor 42 | * shaderFogShape 43 | * shaderLightDirections 44 | * shaderGameTime 45 | * shaderTextures(int) 46 | --- 47 | * shaderLineWidth 48 | 49 | ## BufferUploader 50 | 51 | * lastVertexArrayObject 52 | * lastVertexBufferObject 53 | * lastIndexBufferObject 54 | * lastFormat 55 | 56 | ## BlendMode 57 | 58 | > [!tip]与上文不同,这是给`ShaderInstance`和`EffectInstance` 使用的 59 | > 非干涉情况下,BlendMode的优先级更高,更晚被设置 60 | 61 | * BlendMode lastApplied 62 | * int srcColorFactor 63 | * int srcAlphaFactor 64 | * int dstColorFactor 65 | * int dstAlphaFactor 66 | * int blendFunc 67 | * boolean separateBlend 68 | * boolean opaque 69 | 70 | 71 | > [!warning] 72 | > mc对于BlendMode和全局GlStateManager.BLEND混合使用的情况极大可能有异常 73 | > 如果渲染上出现了混合错误,请尝以下方案 74 | 75 | ```java 76 | import com.mojang.blaze3d.shaders.BlendMode; 77 | import org.spongepowered.asm.mixin.Mixin; 78 | import org.spongepowered.asm.mixin.gen.Accessor; 79 | 80 | @Mixin(BlendMode.class) 81 | public interface BlendModeMixin { 82 | @Accessor 83 | static void setLastApplied(BlendMode mode) {} 84 | @Accessor 85 | static BlendMode getLastApplied() { return null;} 86 | } 87 | 88 | // warp your render call during getLastApplied and setLastApplied 89 | ``` 90 | -------------------------------------------------------------------------------- /render/TextureAtlas.md: -------------------------------------------------------------------------------- 1 | # TextureAtlas 2 | 3 | --- 4 | 5 | mc在游戏加载期间,讲大量分散的贴图合并,用于`Batched Render`,加速渲染 6 | 代码上对应类`net.minecraft.client.renderer.texture.TextureAtlas` 7 | 本身即继承自`AbstractTexture`,证明其本身也是一张图片,这里列出`atlas/blocks` 8 | 9 | ![](../picture/textureAtlas/atlasblocks.png) 10 | 11 | 而自身含有大量分散的贴图,则被抽象为`TextureAtlasSprite`,从中可以拿到`u0,u1,v0,v1` 12 | 类型为float,表明其已是经过标准化的`uv坐标` 13 | 14 | `TextureAtlas`内有字段`Map texturesByName` 15 | `key`为材质对应的`ResourceLocation` 16 | 17 | 部分`TextureAtlas`被`AtlasSet`所引用,而每一个`TextureAtlas`也有一个`ResourceLocation`作为其表征 18 | 因此,`AtlasSet`可被视为`Map>` 19 | 20 | | TextureAtlas stored by AtlasSet | field name | 21 | |------------------------------------------|-----------------------------| 22 | | minecraft:textures/atlas/blocks | BLOCK_ATLAS/LOCATION_BLOCKS | 23 | | minecraft:textures/atlas/signs | SIGN_SHEET | 24 | | minecraft:textures/atlas/banner_patterns | BANNER_SHEET | 25 | | minecraft:textures/atlas/shield_patterns | SHIELD_SHEET | 26 | | minecraft:textures/atlas/chest | CHEST_SHEET | 27 | | minecraft:textures/atlas/beds | BED_SHEET | 28 | | minecraft:textures/atlas/shulker_boxes | SHULKER_SHEET | 29 | 30 | 其余部分的被`TextureAtlasHolder`的实现者持有 31 | 32 | | TextureAtlas | owner | 33 | |--------------------------------------|-------------------------| 34 | | minecraft:textures/atlas/mod_effects | MobEffectTextureManager | 35 | | minecraft:textures/atlas/paintings | PaintingTextureManager | 36 | 37 | 例外 38 | 39 | | TextureAtlas | owner | 40 | |--------------------------------------|-------------------------| 41 | | minecraft:textures/atlas/particles | ParticleEngine | 42 | 43 | 可通过`Minecraft`类下`Function getTextureAtlas(ResourceLocation pLocation)`拿到所需`TextureAtlasSprite` 44 | 45 | ## add 46 | 47 | mc会将被引用的材质自动添加,若想手动添加 48 | 49 | 可订阅`TextureStitchEvent.Pre` 50 | 通过`getAtlas()`拿到`TextureAtlas`,若对其添加`TextureAtlasSprite`,可调用`addSprite(ResourceLocation)` 51 | 52 | ## animate texture 53 | 54 | `TextureAtlas`实现了`net.minecraft.client.renderer.texture.Tickable` 55 | 最终会调用`TextureAtlasSprite$AnimatedTexture#tick`,内部调用`glPixelStorei` 56 | 57 | ## 利用TextureAtlasHolder实现动态盔甲纹理 58 | 59 | forge为item添加了如下方法`default String getArmorTexture(ItemStack stack, Entity entity, EquipmentSlot slot, String type)` 60 | 通过这个方法,可实现为盔甲指定纹理,然而mc在渲染盔甲时,与渲染方块不同,并不使用类似于Atlas,而是直接使用纹理文件作为目标,此过程.mcmeta中指定的动画内容无效 61 | 可通过委托TextureAtlasHolder,利用它帮助解析.mcmeta文件,然后手动移除mc在`TextureAtlas#getLoadedSprites`添加的`MissingTextureAtlasSprite` 62 | 并且手动设置纹理大小,则在复写forge添加的返回的方法中,返回`TextureAtlasHolder`持有的`TextureAtlas`对象的`ResourceLocation`即可 63 | 64 | **注意`TextureAtlasHolder#getSprite`会修改你在`getResourcesToLoad`中提交的的委托的纹理路径** 65 | 66 | Example: 67 | ![animated_model](../picture/textureAtlas/animated_model.gif) -------------------------------------------------------------------------------- /render/blockModel.md: -------------------------------------------------------------------------------- 1 | # BlockModel物品模型 2 | 3 | --- 4 | 5 | ## JSON Model Structure 6 | 7 | 方块的物品模型,与`json`模型相比更加之复杂,我们会阐释一下 8 | 9 | 首先是`models/block/block.json`,定义了方块在不同`视角/TransformType`对应的变换参数 10 | 11 |
12 | block.json 13 | 14 | ```json 15 | { 16 | "gui_light": "side", 17 | "display": { 18 | "gui": { 19 | "rotation": [ 30, 225, 0 ], 20 | "translation": [ 0, 0, 0], 21 | "scale":[ 0.625, 0.625, 0.625 ] 22 | }, 23 | "ground": { 24 | "rotation": [ 0, 0, 0 ], 25 | "translation": [ 0, 3, 0], 26 | "scale":[ 0.25, 0.25, 0.25 ] 27 | }, 28 | "fixed": { 29 | "rotation": [ 0, 0, 0 ], 30 | "translation": [ 0, 0, 0], 31 | "scale":[ 0.5, 0.5, 0.5 ] 32 | }, 33 | "thirdperson_righthand": { 34 | "rotation": [ 75, 45, 0 ], 35 | "translation": [ 0, 2.5, 0], 36 | "scale": [ 0.375, 0.375, 0.375 ] 37 | }, 38 | "firstperson_righthand": { 39 | "rotation": [ 0, 45, 0 ], 40 | "translation": [ 0, 0, 0 ], 41 | "scale": [ 0.40, 0.40, 0.40 ] 42 | }, 43 | "firstperson_lefthand": { 44 | "rotation": [ 0, 225, 0 ], 45 | "translation": [ 0, 0, 0 ], 46 | "scale": [ 0.40, 0.40, 0.40 ] 47 | } 48 | } 49 | } 50 | 51 | ``` 52 | 53 |
54 | 55 | 然后对于普通的六面方块来说,都来直接或间接来自`models/block/cube.json` 56 | 57 | ```json 58 | { 59 | "parent": "block/block", 60 | "elements": [ 61 | { "from": [ 0, 0, 0 ], 62 | "to": [ 16, 16, 16 ], 63 | "faces": { 64 | "down": { "texture": "#down", "cullface": "down" }, 65 | "up": { "texture": "#up", "cullface": "up" }, 66 | "north": { "texture": "#north", "cullface": "north" }, 67 | "south": { "texture": "#south", "cullface": "south" }, 68 | "west": { "texture": "#west", "cullface": "west" }, 69 | "east": { "texture": "#east", "cullface": "east" } 70 | } 71 | } 72 | ] 73 | } 74 | ``` 75 | 76 | 这里比较特殊的是`texture`后面的`#down` `#up`等,将会查找自将继承此模型的模型,比如`models/block/cube_all.json` 77 | 78 | ```json 79 | { 80 | "parent": "block/cube", 81 | "textures": { 82 | "particle": "#all", 83 | "down": "#all", 84 | "up": "#all", 85 | "north": "#all", 86 | "east": "#all", 87 | "south": "#all", 88 | "west": "#all" 89 | } 90 | } 91 | ``` 92 | 93 | 其他非普通六面的模型,则定义其`elements`,如台阶 94 | 95 |
96 | stair.json 97 | 98 | ```json 99 | { "parent": "block/block", 100 | "display": { 101 | "gui": { 102 | "rotation": [ 30, 135, 0 ], 103 | "translation": [ 0, 0, 0], 104 | "scale":[ 0.625, 0.625, 0.625 ] 105 | }, 106 | "head": { 107 | "rotation": [ 0, -90, 0 ], 108 | "translation": [ 0, 0, 0 ], 109 | "scale": [ 1, 1, 1 ] 110 | }, 111 | "thirdperson_lefthand": { 112 | "rotation": [ 75, -135, 0 ], 113 | "translation": [ 0, 2.5, 0], 114 | "scale": [ 0.375, 0.375, 0.375 ] 115 | } 116 | }, 117 | "textures": { 118 | "particle": "#side" 119 | }, 120 | "elements": [ 121 | { "from": [ 0, 0, 0 ], 122 | "to": [ 16, 8, 16 ], 123 | "faces": { 124 | "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, 125 | "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top" }, 126 | "north": { "uv": [ 0, 8, 16, 16 ], "texture": "#side", "cullface": "north" }, 127 | "south": { "uv": [ 0, 8, 16, 16 ], "texture": "#side", "cullface": "south" }, 128 | "west": { "uv": [ 0, 8, 16, 16 ], "texture": "#side", "cullface": "west" }, 129 | "east": { "uv": [ 0, 8, 16, 16 ], "texture": "#side", "cullface": "east" } 130 | } 131 | }, 132 | { "from": [ 8, 8, 0 ], 133 | "to": [ 16, 16, 16 ], 134 | "faces": { 135 | "up": { "uv": [ 8, 0, 16, 16 ], "texture": "#top", "cullface": "up" }, 136 | "north": { "uv": [ 0, 0, 8, 8 ], "texture": "#side", "cullface": "north" }, 137 | "south": { "uv": [ 8, 0, 16, 8 ], "texture": "#side", "cullface": "south" }, 138 | "west": { "uv": [ 0, 0, 16, 8 ], "texture": "#side" }, 139 | "east": { "uv": [ 0, 0, 16, 8 ], "texture": "#side", "cullface": "east" } 140 | } 141 | } 142 | ] 143 | } 144 | 145 | ``` 146 | 147 |
148 | 149 | 可以观察出,一个`element`,由`from`和`to`定义其在16个体素范围内的位置,由`face`定义其每个面的材质 150 | 151 | >[!note] 152 | > 方块对应的物品默认是没有材质的 153 | > 请详见[ModelResourceLocation](render/itemModel.md#modelresourcelocation)和[Use Block Model](render/itemModel.md#use-block-model) 154 | 155 | ## BlockState 156 | 157 | 打开游戏,按下f3,可以看到右侧的`Targeted Block`和`Targeted Fluid`下,除了显示所指向的方块名称 158 | 在以`#`为标识的`Tag`之上,有的方块/流体还显示了一些别的信息,这就是`BlockState` 159 | 160 | 在Minecraft内创建世界的时候,有一种隐藏的世界类型称之为`debug mode`,见[wiki](https://minecraft.fandom.com/wiki/Debug_mode) 161 | 里面枚举所有方块/流体的BlockState 162 | 163 | 想要为你的方块添加`BlockState`,你需要复写`protected void createBlockStateDefinition(StateDefinition.Builder pBuilder)` 164 | 原版已定义的在类`BlockStateProperties`内,可以直接引用 165 | 166 | 若你想创建自己的BlockState,可以选择实现抽象类`net.minecraft.world.level.block.state.properties.Property>` 167 | 当然,原版已经有特化实现`BooleanProperty`,`DirectionProperty`,`EnumProperty`,`IntegerProperty` 168 | 169 | >[!warning] 170 | > 方块所持有的`BlockState`会在加载模型时候,穷举其所有排列组合,即笛卡尔积 171 | > 请确保其枚举总可能结果数量在一个合理的范围内 172 | 173 | `BlockState`的切换需要你手动设置,可以覆写诸如 174 | `getStateForPlacement`在放置时设置 175 | `neighborChanged`毗邻方块更新时设置 176 | `updateIndirectNeighbourShapes`,`updateShape`等 177 | 178 | ## JSON model 179 | 180 | --- 181 | 182 | ### Variants Model 183 | 184 | 原版拥有两种以`BlockState`描述模型的方式,在[wiki](https://minecraft.fandom.com/wiki/Model#Block_states)都有描述 185 | 其一便是`Variants Block Model` 186 | 其思路为排列组合枚举所有的`BlockState`,并要求逐一给出对应的模型 187 | 这里以草方块为例,其拥有一个布尔类型的名为snow的BlockState 188 | 189 | 首先是`blockState`的文件,blockstates/grass_block.json 190 | ```json 191 | { 192 | "variants": { 193 | "snowy=false": [ 194 | { 195 | "model": "minecraft:block/grass_block" 196 | }, 197 | { 198 | "model": "minecraft:block/grass_block", 199 | "y": 90 200 | }, 201 | { 202 | "model": "minecraft:block/grass_block", 203 | "y": 180 204 | }, 205 | { 206 | "model": "minecraft:block/grass_block", 207 | "y": 270 208 | } 209 | ], 210 | "snowy=true": { 211 | "model": "minecraft:block/grass_block_snow" 212 | } 213 | } 214 | } 215 | ``` 216 | 217 | 然后是模型文件 218 | 219 | 220 | #### **block/grass_block.json** 221 | 222 | ```json 223 | { "parent": "block/block", 224 | "textures": { 225 | "particle": "block/dirt", 226 | "bottom": "block/dirt", 227 | "top": "block/grass_block_top", 228 | "side": "block/grass_block_side", 229 | "overlay": "block/grass_block_side_overlay" 230 | }, 231 | "elements": [ 232 | { "from": [ 0, 0, 0 ], 233 | "to": [ 16, 16, 16 ], 234 | "faces": { 235 | "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, 236 | "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top", "cullface": "up", "tintindex": 0 }, 237 | "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "north" }, 238 | "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "south" }, 239 | "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "west" }, 240 | "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "east" } 241 | } 242 | }, 243 | { "from": [ 0, 0, 0 ], 244 | "to": [ 16, 16, 16 ], 245 | "faces": { 246 | "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "tintindex": 0, "cullface": "north" }, 247 | "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "tintindex": 0, "cullface": "south" }, 248 | "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "tintindex": 0, "cullface": "west" }, 249 | "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "tintindex": 0, "cullface": "east" } 250 | } 251 | } 252 | ] 253 | } 254 | 255 | ``` 256 | 257 | #### **block/grass_block_snow** 258 | 259 | ```json 260 | { 261 | "parent": "minecraft:block/cube_bottom_top", 262 | "textures": { 263 | "top": "minecraft:block/grass_block_top", 264 | "bottom": "minecraft:block/dirt", 265 | "side": "minecraft:block/grass_block_snow", 266 | "particle": "minecraft:block/dirt" 267 | } 268 | } 269 | ``` 270 | 271 | #### **item/grass_block** 272 | ```json 273 | { 274 | "parent": "minecraft:block/grass_block" 275 | } 276 | ``` 277 | 278 | 279 | 280 | 这里我们给出一个多阶段作物的[例子](https://github.com/MalayPrime/rotarism-decorations/blob/master/src/generated/resources/assets/blockstates/canola.json) 281 | 282 | ![exampleForVariant](../picture/blockModel/exampleForVariant.png) 283 | 284 | ### Multipart Model 285 | 286 | 另一种则称之为`Multipart Model`,与`Variants`不同,这种方式可以视为模型在一系列条件下的叠加 287 | 以原版的栅栏为例 288 | 289 | ```json 290 | { 291 | "multipart": [ 292 | { 293 | "apply": {"model": "minecraft:block/acacia_fence_post"} 294 | }, 295 | { 296 | "when": {"north": "true"}, 297 | "apply": {"model": "minecraft:block/acacia_fence_side","uvlock": true} 298 | }, 299 | { 300 | "when": {"east": "true"}, 301 | "apply": {"model": "minecraft:block/acacia_fence_side","y": 90,"uvlock": true} 302 | }, 303 | { 304 | "when": {"south": "true"}, 305 | "apply": {"model": "minecraft:block/acacia_fence_side","y": 180,"uvlock": true} 306 | }, 307 | { 308 | "when": {"west": "true"}, 309 | "apply": {"model": "minecraft:block/acacia_fence_side","y": 270,"uvlock": true} 310 | } 311 | ] 312 | } 313 | ``` 314 | 315 | 其渲染流程可视为,对一系列`when`进行判断,如果成立,则`叠加/应用/apply`所指定的模型 316 | 当然这这一系列操作并不会发生在渲染时,在模型加载阶段就已经完成 317 | 318 | 在这里我们给出一个管道的[例子](https://github.com/MalayPrime/rotarism-decorations/blob/master/src/generated/resources/assets/blockstates/normal_pipe.json) 319 | 320 | ![exampleForMultiPart](../picture/blockModel/exampleForMultiPart.png) 321 | 322 | ## RenderType 323 | 324 | 参见[renderType](render/renderType.md#normal-block) 325 | 326 | 如果你的方块使用了带有alpha通道的贴图,那么你可能需要调用`ItemBlockRenderTypes#setRenderLayer` 327 | 328 | ## noOcclusion 329 | 330 | 如果你的方块出现了不正常的面剔除现象,那么你需要为你的方块的`BlockBehaviour.Properties#noOcclusion` 331 | 差别可见图片上下两排 332 | ![](../picture/renderType/blockRenderType.png) 333 | 334 | ## IModelData 335 | 336 | `forge`对于原版的扩充,基本可以理解为一个`Map,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 | ![colorfulBlock](../picture/blockModel/colorfulBlock.gif) 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 | ![coordinateSystem.png](../picture/coordinateSystem/coordinateSystem.png) 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 | ![sdf](../picture/coordinateSystem/SDF_show.gif) 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 | ![empty](../picture/itemModel/empty.png) 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 | ![weather_indicator](../picture/itemModel/weather_indicator.gif) 194 | 195 | 逻辑如下 196 | ![overrideExplanation](../picture/itemModel/overrideExplanation.png) 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 | ![normalChalk](../picture/itemModel/normalChalk.png) 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 | ![colorfulChalk](../picture/itemModel/colorfulChalk.gif) 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 | ![drawable_chalk](../picture/itemModel/drawable_chalk.gif) 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 | ![overlayTexture](..\svg\overlayTexture.svg) 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 | ![tnt](../picture/overlayTexture/tnt.gif) 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 | ![renderInLevelBlock.png](../picture/renderInLevel/renderInLevelBlock.png) 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 | ![renderInLevelItem.png](../picture/renderInLevel/renderInLevelItem.png) 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 | ![renderInLevelFluid.png](../picture/renderInLevel/renderInLevelFluid.png) 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 | ![chunkBufferLayers](../picture/renderType/chunkBufferLayers.png) 425 | 来自[TheGreyGhost](https://greyminecraftcoder.blogspot.com/2020/04/block-rendering-1144.html) 426 | #### **3T** 427 | ![chunkBufferLayers](../picture/renderType/blockRenderType.png) 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 | ![](../picture/shader/dissolve.gif) 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------