使用视区作为纹理

介绍

本教程将向您介绍如何使用 Viewport 作为可应用于三维对象的纹理。为了做到这一点,它将引导您完成一个程序化行星的制作过程,如下面所示:

../../_images/planet_example.png

注解

本教程不包括如何编码一个动态的大气层,就像这个星球一样。

本教程假设您熟悉如何设置基本场景,包括: Camera ,A light source ,A Mesh Instance 用一个 Primitive Mesh ,并应用 Spatial Material 到网格。重点是使用 Viewport 动态创建可应用于网格的纹理。

在本教程的过程中,我们将介绍以下主题:

  • 如何使用 Viewport 作为渲染纹理

  • 使用等矩形映射将纹理映射到球体

  • 程序行星的碎片着色技术

  • 设置粗糙度图 Viewport Texture

设置视区

首先,添加 Viewport 去现场。

接下来,设置 Viewport(1024, 512) . 这个 Viewport 实际上可以是任何尺寸,只要宽度是高度的两倍。宽度必须是高度的两倍,这样图像才能准确地映射到球体上,就像我们将使用等矩形投影一样,但稍后会更多。

../../_images/planet_new_viewport.png

接下来,禁用hdr和3d。我们不需要hdr,因为我们的星球表面不会特别明亮,所以值介于 01 会很好的。我们将使用 ColorRect 为了渲染表面,我们也不需要3D。

选择视区并添加 ColorRect 作为一个孩子。

将锚定“右”和“下”设置为 1 ,然后确保所有页边距都设置为 0 . 这将确保 ColorRect 占据了整个 Viewport .

../../_images/planet_new_colorrect.png

接下来,我们添加一个 Shader MaterialColorRect (颜色>CanvasItem>材质>材质> New ShaderMaterial

注解

本教程建议您基本熟悉底纹。然而,即使您是新的着色程序,所有的代码都将被提供,所以您应该没有问题跟随。

颜色矩形>CanvasItem>材质>材质>单击/编辑>材质>着色>着色> New Shader >单击/编辑:

shader_type canvas_item;

void fragment() {
    COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
}

上面的代码呈现类似下面的渐变。

../../_images/planet_gradient.png

现在我们有了 Viewport 我们呈现给它,我们有一个独特的图像,我们可以应用到球体上。

应用纹理

网格实例>几何体Instance>几何体>材质覆盖(MeshInstance>GeometryInstance>Geometry>Material Override)> New SpatialMaterial

现在我们进入 Mesh Instance 添加一个 Spatial Material 为了它。不需要特别的 Shader Material (尽管这对于更先进的效果是个好主意,比如上面例子中的大气)。

网格实例>几何体Instance>几何体>材质覆盖(MeshInstance>GeometryInstance>Geometry>Material Override)> click / Edit

打开新创建的 Spatial Material 向下滚动到“反照率”部分,单击“纹理”属性旁边添加反照率纹理。在这里我们将应用我们制作的纹理。选择“new viewporttexture”

../../_images/planet_new_viewport_texture.png

然后,从弹出的菜单中,选择我们之前渲染到的视口。

../../_images/planet_pick_viewport_texture.png

您的球体现在应该用我们渲染到视区的颜色着色。

../../_images/planet_seam.png

注意到在纹理环绕的地方形成的难看的接缝吗?这是因为我们要根据UV坐标选择颜色,而UV坐标不会环绕纹理。这是二维地图投影中的经典问题。游戏开发人员通常有一个二维的地图,他们希望投影到一个球体上,但当它环绕时,它有很大的接缝。对于这个问题有一个很好的解决方法,我们将在下一节中加以说明。

使行星纹理

所以现在,当我们 Viewport 它神奇地出现在球体上。但我们的纹理坐标创造了一个难看的接缝。那么我们怎样才能得到一系列围绕球体的坐标呢?一种解决方案是使用一个在纹理域中重复的函数。 sincos 是两个这样的功能。让我们把它们应用到纹理上,看看会发生什么。

COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
../../_images/planet_sincos.png

不错。如果你环顾四周,你可以看到接缝已经消失了,但是在它的位置上,我们已经在电线杆上捏了一下。这种挤压是由于Godot将纹理映射到 Spatial Material . 它使用一种称为等矩形投影的投影技术,将球面图转换为二维平面。

注解

如果您对这项技术感兴趣,我们将从球面坐标转换成笛卡尔坐标。球面坐标映射球体的经度和纬度,而笛卡尔坐标则是从球体中心到该点的矢量。

对于每个像素,我们将计算其在球体上的三维位置。由此,我们将使用3D噪波来确定颜色值。通过对三维噪声的计算,解决了极点处的挤压问题。要了解原因,请想象正在计算的噪声是通过球体表面而不是通过二维平面计算的。当你计算球体的表面时,你永远不会碰到一个边缘,因此你永远不会在极点上创建一个接缝或一个夹点。以下代码转换 UVs 转换为笛卡尔坐标。

float theta = UV.y * 3.14159;
float phi = UV.x * 3.14159 * 2.0;
vec3 unit = vec3(0.0, 0.0, 0.0);

unit.x = sin(phi) * sin(theta);
unit.y = cos(theta) * -1.0;
unit.z = cos(phi) * sin(theta);
unit = normalize(unit);

如果我们使用 unit 作为输出 COLOR 价值,我们得到:

../../_images/planet_normals.png

现在我们可以计算球体表面的三维位置了,我们可以使用三维噪声来制作行星。我们将直接从 Shadertoy

vec3 hash(vec3 p) {
    p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
             dot(p, vec3(269.5, 183.3, 246.1)),
             dot(p, vec3(113.5, 271.9, 124.6)));

    return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float noise(vec3 p) {
  vec3 i = floor(p);
  vec3 f = fract(p);
  vec3 u = f * f * (3.0 - 2.0 * f);

  return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
                     dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
                 mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
                     dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),
             mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
                     dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
                 mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
                     dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );
}

注解

所有的功劳都归功于作者伊尼戈·奎尔兹。它是在 MIT 许可证。

现在使用 noise ,将以下内容添加到 fragment 功能:

float n = noise(unit * 5.0);
COLOR.xyz = vec3(n * 0.5 + 0.5);
../../_images/planet_noise.png

注解

为了突出显示纹理,我们将材质设置为无阴影。

现在你可以看到噪音确实无缝地环绕着球体。虽然这看起来和你承诺过的那个星球一点都不像。所以让我们来看看更丰富多彩的东西。

给地球着色

现在来制作行星的颜色。虽然有很多方法可以做到这一点,但目前,我们将坚持水和土地之间的梯度。

为了在glsl中创建渐变,我们使用 mix 功能。 mix 在和第三个参数之间插入两个值,以选择在它们之间插入多少;本质上,它 混合 这两个值在一起。在其他API中,经常调用此函数 lerp . 然而, lerp 通常用于将两个浮球混合在一起; mix 可以获取任何值,无论是浮点类型还是向量类型。

COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5);

第一种颜色是蓝色,适合海洋。第二种颜色是一种红色(因为所有的外星行星都需要红色的地形)。最后,它们被混合在一起 n * 0.5 + 0.5 . n 平稳变化 -11 . 所以我们把它映射到 0-1 范围 mix 预期。现在你可以看到颜色在蓝色和红色之间变化。

../../_images/planet_noise_color.png

这比我们想要的要模糊一点。行星通常在陆地和海洋之间有一个相对清晰的分离。为了做到这一点,我们将把最后一个学期改为 smoothstep(-0.1, 0.0, n) . 因此整条线变成:

COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n));

什么 smoothstep 是返回吗 0 如果第三个参数低于第一个和 1 如果第三个论点大于第二个论点,并且在 01 如果第三个数字在第一个和第二个之间。所以在这条线上, smoothstep 收益率 0 无论何时 n 小于 -0.1 它又回来了 1 无论何时 n 在上面 0 .

../../_images/planet_noise_smooth.png

还有一件事要让地球看起来更像Y。这片土地不应该这么肥大;让我们把边缘弄得粗糙一点。阴影器中经常使用的一个技巧是以不同的频率将噪波层叠在一起,从而使具有噪波的粗糙地形看起来更粗糙。我们用一层来制作大陆的整体块状结构。然后另一层将边缘稍微分开,然后另一层,依此类推。我们要做的是计算 n 使用四行着色代码而不是一行。 n 变成:

float n = noise(unit * 5.0) * 0.5;
n += noise(unit * 10.0) * 0.25;
n += noise(unit * 20.0) * 0.125;
n += noise(unit * 40.0) * 0.0625;

现在这个星球看起来像:

../../_images/planet_noise_fbm.png

在打开着色的情况下,它看起来像:

../../_images/planet_noise_fbm_shaded.png

制造海洋

最后一件事就是让这个看起来更像一个行星。海洋和陆地反射光的方式不同。所以我们希望海洋比陆地更明亮一点。我们可以通过将第四个值传递到 alpha 我们的输出通道 COLOR 并将其用作粗糙度图。

COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);

此行返回 0.3 对于水和 1.0 为了土地。这意味着土地将变得相当粗糙,而水将非常光滑。

然后,在材料的“金属”部分下,确保 Metallic 设置为 0Specular 设置为 1 . 原因是水能很好地反射光线,但不是金属的。这些值在物理上并不精确,但对于本演示来说已经足够好了。

接下来,在“粗糙度”部分下,设置 Roughness1 并将粗糙度纹理设置为 Viewport Texture 指向我们的星球纹理 Viewport . 最后,设置 Texture ChannelAlpha . 这将指示渲染器使用 alpha 我们的输出通道 COLOR 作为 Roughness 价值。

../../_images/planet_ocean.png

你会注意到,除了行星不再反射天空之外,几乎没有什么变化。这是因为,默认情况下,当使用alpha值渲染某个对象时,它会在背景上绘制为透明对象。而且由于默认的背景 Viewport 是不透明的, alpha 的频道 Viewport Texture1 从而使行星的纹理被绘制成略淡的颜色和 Roughness 价值 1 到处都是。要更正此问题,我们将进入 Viewport 并将“transparent bg”设置为“on”。由于我们现在正在将一个透明对象呈现在另一个对象之上,因此我们希望启用 blend_premul_alpha

render_mode blend_premul_alpha;

这将颜色预乘 alpha 然后将它们正确地混合在一起。通常,在将一种透明颜色与另一种颜色混合时,即使背景具有 alpha 属于 0 (就像在这个例子中一样),你最终会出现奇怪的颜色出血问题。设置 blend_premul_alpha 解决了这个问题。

现在这个星球看起来应该像是在海洋上反射光,而不是在陆地上。如果您还没有这样做,请添加一个 OmniLight 到场景中,这样你就可以四处移动并看到反射对海洋的影响。

../../_images/planet_ocean_reflect.png

就在这里。程序行星 Viewport .