第一个空间材质球

您已决定开始编写自己的自定义空间明暗器。也许你在网上看到了一个很酷的关于材质球的技巧,或者你发现了 SpatialMaterial 不能完全满足你的需要。不管怎样,你已经决定自己写了,现在你需要找出从哪里开始。

本教程将解释如何编写空间明暗器,并将涵盖比 CanvasItem 辅导的。

空间明暗器比CanvasItem明暗器具有更多的内置功能。空间着色器的期望是Godot已经为常见用例提供了功能,并且用户在该着色器中需要做的所有操作都设置了适当的参数。这对于PBR(基于物理的渲染)工作流尤其适用。

这是一个由两部分组成的教程。在第一部分中,我们将介绍如何在顶点函数中使用来自高度图的顶点位移来制作简单的地形。在 second part 我们将学习本教程中的概念,并学习如何通过编写海水材质球在片段材质球中设置自定义材质。

注解

本教程假设了一些基本的明暗器知识,例如类型 (vec2floatsampler2D )和函数。如果您对这些概念不满意,最好从 The Book of Shaders 在完成本教程之前。

在哪里分配我的材料

在3D中,使用 Meshes . 网格是一种资源类型,以称为“曲面”的单位存储几何图形(对象的形状)和材质(对象的颜色以及对光的反应方式)。网格可以有多个曲面,也可以只有一个曲面。通常,您会从另一个程序(如搅拌机)导入网格。但Godot也有一些 PrimitiveMeshes 这样可以在不导入网格的情况下将基本几何图形添加到场景中。

有多种节点类型可用于绘制网格。主要的是 MeshInstance ,但也可以使用 ParticlesMultiMeshes (用) MultiMeshInstance )或其他。

通常,材质与网格中的给定曲面关联,但某些节点(如网格实例)允许您覆盖特定曲面或所有曲面的材质。

如果在曲面或网格本身上设置材质,则共享该网格的所有网格实例都将共享该材质。但是,如果要在多个网格实例中重用相同的网格,但每个实例的材质不同,则应在网格实例上设置材质。

在本教程中,我们将在网格本身上设置材质,而不是利用网格实例覆盖材质的能力。

设置

添加新的 MeshInstance 节点到场景。

在“网格”旁边的“检查器”选项卡中单击 [空的] 并选择“New PlaneMesh”。然后单击出现的平面图像。

这将添加一个 PlaneMesh 到我们的现场。

然后,在视区中,单击左上角的“透视”按钮。将出现一个菜单。在菜单的中间是如何显示场景的选项。选择“显示线框”。

这样可以看到构成平面的三角形。

../../../_images/plane.png

现在设置 Subdivide WidthSubdivide Depth32 .

../../../_images/plane-sub-set.png

你可以看到现在有更多的三角形在 Mesh . 这将给我们提供更多的顶点来使用,从而允许我们添加更多的细节。

../../../_images/plane-sub.png

PrimitiveMeshes 像平面网一样,只有一个表面,所以没有一组材料,只有一个。单击“Material”旁边的“Material”。 [空的] 并选择“New ShaderMaterial”。然后单击出现的球体。

现在单击“shader”旁边的“shader”。 [空的] 并选择“New shader”。

现在应该弹出明暗器编辑器,准备开始编写第一个空间明暗器!

明暗器魔法

../../../_images/shader-error.png

注意怎么已经有错误了?这是因为“明暗器编辑器”会动态重新加载明暗器。Godot明暗器需要的第一件事是声明它们是什么类型的明暗器。我们设置变量 shader_typespatial 因为这是一个空间着色。

shader_type spatial;

接下来我们将定义 vertex() 功能。这个 vertex() 函数确定顶点的位置 Mesh 出现在最终场景中。我们将使用它来偏移每个顶点的高度,使平面看起来像一个小地形。

我们像这样定义顶点明暗器:

void vertex() {

}

里面什么都没有 vertex() 函数,godot将使用其默认顶点着色器。我们可以通过添加一行轻松开始进行更改:

void vertex() {
  VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
}

添加这一行,您应该得到一个像下面这样的图像。

../../../_images/cos.png

好吧,我们把这个打开。这个 y 价值观 VERTEX 正在增加。我们正经过 xz 的组件 VERTEX 作为 cossin 这让我们在 xz 轴。

毕竟,我们想要实现的是小山丘的外观。 cossin 看起来已经有点像山了。我们通过将输入扩展到 cossin 功能。

void vertex() {
  VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0);
}
../../../_images/cos4.png

这看起来更好,但它仍然过于尖锐和重复,让我们让它更有趣一点。

噪声高度图

噪声是一种非常流行的工具,用来模拟地形的外观。把它想象成类似于余弦函数,在这里你有重复的山,除了有噪音,每座山都有不同的高度。

Godot提供 NoiseTexture 用于生成可从明暗器访问的噪波纹理的资源。

要访问材质球中的纹理,请在材质球顶部附近的 vertex() 功能。

uniform sampler2D noise;

这将允许您向明暗器发送噪波纹理。现在看看你的材料下面的检查员。您应该看到一个名为“shader params”的部分。如果你打开它,你会看到一个叫做“噪音”的部分。

在旁边单击它所说的“ [空的] 并选择“New NoiseTexture”。然后在NoiseTexture中,单击它所说的“Noise”旁边,并选择“New OpenSimplexNoise”。

OpenSimplexNoise 被noisetexture用来生成高度图。

一旦你设置好它,应该像这样。

../../../_images/noise-set.png

现在,使用 texture() 功能。 texture() 将纹理作为第一个参数, vec2 作为第二个参数的纹理上的位置。我们使用 xz 频道 VERTEX 确定纹理上要查找的位置。 texture() 返回A vec4r, g, b, a 位置上的通道。因为噪波纹理是灰度的,所以所有的值都是相同的,所以我们可以使用任何一个通道作为高度。在这种情况下,我们将使用 rx 通道。

float height = texture(noise, VERTEX.xz / 2.0 ).x; //divide by the size of the PlaneMesh
VERTEX.y += height;

注: xyzw 是一样的 rgba 在GLSL中,所以不是 texture().x 上面,我们可以使用 texture().r . 见 OpenGL documentation 了解更多详细信息。

使用此代码,您可以看到纹理创建随机的山。

../../../_images/noise.png

现在山峰太尖了,我们想让山峰变软一点。为此,我们将使用制服。你已经使用了上面的制服来传递噪音纹理,现在让我们来学习它们是如何工作的。

制服

统一变量允许您将数据从游戏传递到明暗器中。它们对于控制明暗器效果非常有用。制服几乎可以是任何可以在材质球中使用的数据类型。要使用制服,请在 Shader 使用关键字 uniform .

让我们做一件能改变地形高度的制服。

uniform float height_scale = 0.5;

Godot允许您用一个值初始化一个统一体;这里, height_scale 设置为 0.5 . 可以通过调用函数从gdscript设置制服 set_shader_param() 在与材质球对应的材质上。从gdscript传递的值优先于用于在明暗器中初始化该值的值。

# called from the MeshInstance
mesh.material.set_shader_param("height_scale", 0.5)

注解

从空间节点更改制服与CanvasItem节点不同。在这里,我们设置平面网格资源中的材质。在其他网格资源中,您可能需要首先通过调用 surface_get_material() . 在网格实例中,可以使用 get_surface_material()material_override .

记住,字符串传递到 set_shader_param() 必须与中统一变量的名称匹配 Shader . 您可以在您的 Shader . 这里,我们将使用它来设置高度值,而不是任意乘以 0.5 .

VERTEX.y += height * height_scale;

现在看起来好多了。

../../../_images/noise-low.png

使用统一格式,我们甚至可以更改每帧的值来设置地形高度的动画。结合 Tweens 这对于简单的动画特别有用。

与光相互作用

首先,关闭线框。要执行此操作,请再次单击视区左上角的“透视”,然后选择“显示正常”。

../../../_images/normal.png

注意网格颜色是如何变平的。这是因为它的灯光是平的。让我们加个灯!

首先,我们将添加一个 OmniLight 去现场。

../../../_images/light.png

你可以看到影响地形的光线,但看起来很奇怪。问题是光线影响着地形,就好像它是一个平面。这是因为灯光明暗器使用来自 Mesh 计算光线。

法线存储在网格中,但我们正在更改材质球中网格的形状,因此法线不再正确。为了解决这个问题,我们可以重新计算明暗器中的法线,或者使用与我们的噪波相对应的正常纹理。Godot让我们两个都容易。

您可以在顶点函数中手动计算新法线,然后设置 NORMAL . 用 NORMAL 集,Godot将为我们做所有困难的照明计算。我们将在本教程的下一部分中介绍此方法,现在我们将从纹理中读取法线。

相反,我们将再次依赖于noisetexture来计算我们的法线。我们通过传递第二个噪波纹理来实现这一点。

uniform sampler2D normalmap;

用另一个OpenSimplexNoise将第二个均匀纹理设置为另一个NoiseTexture。但这次,请勾选“as normalmap”。

../../../_images/normal-set.png

现在,因为这是一个法线贴图,而不是逐顶点法线。我们将在 fragment() 功能。这个 fragment() 在本教程的下一部分中将更详细地解释函数。

void fragment() {
}

当我们有对应于特定顶点的法线时,我们设置 NORMAL ,但如果有来自纹理的法线贴图,请使用 NORMALMAP . 这样Godot将自动处理围绕网格的纹理包裹。

最后,为了确保我们在噪声纹理和NormalMap纹理上读取相同的位置,我们将通过 VERTEX.xz 位置 vertex() 函数到 fragment() 功能。我们用varying来做。

vertex() 定义 vec2 打电话 vertex_position . 在里面 vertex() 功能分配 VERTEX.xzvertex_position .

varying vec2 vertex_position;

void vertex() {
  ...
  vertex_position = VERTEX.xz / 2.0;
}

现在我们可以进入 vertex_positionfragment() 功能。

void fragment() {
  NORMALMAP = texture(normalmap, vertex_position).xyz;
}

当法线就位时,灯光现在会动态地对网格的高度做出反应。

../../../_images/normalmap.png

我们甚至可以拖动灯光,灯光将自动更新。

../../../_images/normalmap2.png

以下是本教程的完整代码。你可以看到,这不是很长的Godot处理大部分困难的东西为你。

shader_type spatial;

uniform float height_scale = 0.5;
uniform sampler2D noise;
uniform sampler2D normalmap;

varying vec2 vertex_position;

void vertex() {
  vertex_position = VERTEX.xz / 2.0;
  float height = texture(noise, vertex_position).x * height_scale;
  VERTEX.y += height * height_scale;
}

void fragment() {
  NORMALMAP = texture(normalmap, vertex_position).xyz;
}

这就是这部分的全部内容。希望您现在了解了Godot中顶点着色的基本知识。在本教程的下一部分中,我们将编写一个片段函数来伴随这个顶点函数,我们将介绍一种更高级的技术,将这个地形变成一个移动波的海洋。