java - 当只有像素作为输入时,Minecraft shader 如何知道对象

我知道 Minecraft Shaders 是用 GLSL 编写的。有人向我解释说,它会得到一组像素作为输入,并会返回一堆新像素。但是 shader 是如何知道实体位置、模型等细节的呢?

回答1

将 shader 描述为“获取一组像素作为输入并返回一堆新像素”有点过于简单化了。我不知道你对 shaders 有什么了解,所以我将介绍几个与你的问题相关的概念。

渲染过程,称为渲染 pipeline,由 GPU 执行,分为几个阶段。对于其中一些阶段,您可以实现自定义 shader 程序。因此,每个可编程渲染阶段都有自己的 shader,其输入和输出取决于阶段:https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview

对于 Minecraft,您可以在 wiki 上找到有关 shader 布局的一些信息:https://minecraft.fandom.com/wiki/Shaders

这里还有一个页面列出了关于 shader 编程和 Optifine 的 pipeline 的详细资源:https://wiki.shaderlabs.org/wiki/Getting_Started 一定要检查它,你可能会发现您在此处查找的大部分信息。

无论如何,我将尝试对 shaders 以及他们如何“了解”有关场景(位置,实体...)的详细信息进行简要总结,而不仅仅是像素颜色。

顶点和 fragment shaders

如前所述,shader 有多种类型。在渲染期间执行的两个“主要”shaders 是顶点 shader 和 fragment shader(有时称为像素 shader)。在 Minecraft 着色包中,它们分别具有扩展名 .vsh.fsh

顶点 shader 是渲染过程中执行的第一个 shader ,它将场景的顶点和属性作为输入,并输出新的顶点。在这里,您可以为某些效果实现转换和置换网格的顶点。

fragment shader 是在渲染期间执行的最后一个 shader,并且是生成要放在屏幕上的图像的那个。它获取像素坐标以及从先前阶段(顶点 shader)计算的数据,并返回每个像素的颜色。

将数据从 shader 传递到 shader

属性是为每个顶点传递给 GPU 的特殊数据,您可以在 shader 中使用这些数据。例如,对于场景中的每个顶点,您可以拥有一个整数变量,指示该顶点属于哪个对象或实体,并在您的顶点 shader 代码中使用它。您还可以生成新数据,然后将其从顶点 shader 传递到 fragment shader 输入,这些是称为变量的特殊变量。

在 GLSL 中,属性和变量是您在脚本中声明的特殊变量。一个顶点 shader 可能包含:

// A variable that receives a value passed by the actual program
// on the CPU side.
attribute float someAttribute;

// A variable that you can fill with a value from this shader,
// and its content will be passed to the fragment shader.
varying vec3 someOutput;

然后在 fragment shader :

// Value of the variable with the same name from the vertex shader.
varying vec3 someOutput;

每个顶点都有自己的 someAttributesomeOutput 变量。

(您可能想知道如何将分配给每个顶点的 value 传递给每个像素。我不会详细说明,但发生的情况是 fragment shader 接收一个像素是 someValue 变量的 values 之间的插值,该变量是投影在该像素上的三角形顶点。)

缓冲器

shader 可以访问更多信息而不仅仅是顶点和像素(这可能与您的问题更相关)的另一种方法是使用缓冲区。通常,将场景渲染到屏幕上的过程不止一次。您可以在一个纹理中渲染阴影,然后在另一个纹理中渲染几何体,最后将这些纹理与另一个 shader 组合以创建最终图像以输出到屏幕。

例如,在 Minecraft Optifine shaderpacks 中,您可以编写特定的 shaders 以在缓冲区纹理中渲染图像,其中每个像素的颜色代表您要独立处理的一些特定数据。例如,您可以使用缓冲区来仅渲染您所瞄准的实体,将属于该实体的白色像素着色,将其他像素着色为黑色,并将结果图像存储在纹理中。然后,您可以从另一个 shader 访问此蒙版纹理以添加一些后期处理效果,例如突出显示您瞄准的实体区域。访问 shader 中的纹理只需使用诸如 texture() 之类的特殊函数来获取在某个变量 (sampler2D type) 中引用的纹理中某个坐标处的像素颜色。

至于 shader 如何选择要渲染的对象,这取决于 CPU 端。该程序(此处为 Minecraft)基本上通过 OpenGL API 告诉 GPU 要渲染哪些 3D 模型,使用哪些 shader 脚本,以及是否必须将生成的图像直接发送到屏幕或 store 将其发送到纹理中。该程序可以请求GPU在具有不同shaders和设置的不同预加载渲染程序之间快速切换。

在 Optifine 中,缓冲区的 shader 文件对应于以 gdbuffers_ 开头的文件。直接取自文档 (https://pastebin.com/aB5MJ7aN):

这些文件用于渲染地形、实体、天空以及游戏中的几乎所有其他内容。文件的具体名称可以告诉您更多关于它用于渲染的内容。 Skybasic 首先运行,并处理主要的天空颜色。接下来是处理太阳和月亮的天空纹理。接下来是地形,它处理所有不透明的块。

在同一个链接上,他们给出了一些如何使用它的例子:

创建 2 个缓冲区。一个是材质缓冲区,另一个是半透明缓冲区。使所有透明对象将其颜色输出到半透明缓冲区,并将表示其 ID 的数字(通过变量传入)输出到材质缓冲区。 Composite【Optifine定义的一种shader】可以读取材质缓冲,根据ID不同混合半透明缓冲和不透明颜色缓冲。

我希望这有助于回答你的问题。我还建议你直接查看一些着色包的源文件,看看它们是如何实现的。

相似文章

c - Shader 已成功链接但未显示在屏幕上

我已经实现了一个系统,从理论上讲,它遍历一个生命实体列表并绘制与每个实体关联的shader(假设它有一个)。但是,无论我尝试了什么,屏幕上都没有渲染。我已经确认每个组件都已正确创建/加载,并且数据实际...

c - OpenGL Shader 编译和链接错误

我有一个方法可以从两个给定的字符串(顶点和片段shader文件名)创建并返回一个shader程序。最初,它运行良好,编译和链接成功,然后随机失败并给我一个unicodeemoji的错误消息。示例代码:...

最新文章