使用视区作为纹理¶
介绍¶
本教程将向您介绍如何使用 Viewport 作为可应用于三维对象的纹理。为了做到这一点,它将引导您完成一个程序化行星的制作过程,如下面所示:
注解
本教程不包括如何编码一个动态的大气层,就像这个星球一样。
本教程假设您熟悉如何设置基本场景,包括: Camera ,A light source ,A Mesh Instance 用一个 Primitive Mesh ,并应用 Spatial Material 到网格。重点是使用 Viewport 动态创建可应用于网格的纹理。
在本教程的过程中,我们将介绍以下主题:
如何使用 Viewport 作为渲染纹理
使用等矩形映射将纹理映射到球体
程序行星的碎片着色技术
设置粗糙度图 Viewport Texture
设置视区¶
首先,添加 Viewport 去现场。
接下来,设置 Viewport 到 (1024, 512)
. 这个 Viewport 实际上可以是任何尺寸,只要宽度是高度的两倍。宽度必须是高度的两倍,这样图像才能准确地映射到球体上,就像我们将使用等矩形投影一样,但稍后会更多。
接下来,禁用hdr和3d。我们不需要hdr,因为我们的星球表面不会特别明亮,所以值介于 0
和 1
会很好的。我们将使用 ColorRect 为了渲染表面,我们也不需要3D。
选择视区并添加 ColorRect 作为一个孩子。
将锚定“右”和“下”设置为 1
,然后确保所有页边距都设置为 0
. 这将确保 ColorRect 占据了整个 Viewport .
接下来,我们添加一个 Shader Material 到 ColorRect (颜色>CanvasItem>材质>材质> New ShaderMaterial
)
注解
本教程建议您基本熟悉底纹。然而,即使您是新的着色程序,所有的代码都将被提供,所以您应该没有问题跟随。
颜色矩形>CanvasItem>材质>材质>单击/编辑>材质>着色>着色> New Shader
>单击/编辑:
shader_type canvas_item;
void fragment() {
COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
}
上面的代码呈现类似下面的渐变。
现在我们有了 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”
然后,从弹出的菜单中,选择我们之前渲染到的视口。
您的球体现在应该用我们渲染到视区的颜色着色。
注意到在纹理环绕的地方形成的难看的接缝吗?这是因为我们要根据UV坐标选择颜色,而UV坐标不会环绕纹理。这是二维地图投影中的经典问题。游戏开发人员通常有一个二维的地图,他们希望投影到一个球体上,但当它环绕时,它有很大的接缝。对于这个问题有一个很好的解决方法,我们将在下一节中加以说明。
使行星纹理¶
所以现在,当我们 Viewport 它神奇地出现在球体上。但我们的纹理坐标创造了一个难看的接缝。那么我们怎样才能得到一系列围绕球体的坐标呢?一种解决方案是使用一个在纹理域中重复的函数。 sin
和 cos
是两个这样的功能。让我们把它们应用到纹理上,看看会发生什么。
COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
不错。如果你环顾四周,你可以看到接缝已经消失了,但是在它的位置上,我们已经在电线杆上捏了一下。这种挤压是由于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
价值,我们得到:
现在我们可以计算球体表面的三维位置了,我们可以使用三维噪声来制作行星。我们将直接从 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);
注解
为了突出显示纹理,我们将材质设置为无阴影。
现在你可以看到噪音确实无缝地环绕着球体。虽然这看起来和你承诺过的那个星球一点都不像。所以让我们来看看更丰富多彩的东西。
给地球着色¶
现在来制作行星的颜色。虽然有很多方法可以做到这一点,但目前,我们将坚持水和土地之间的梯度。
为了在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
平稳变化 -1
和 1
. 所以我们把它映射到 0-1
范围 mix
预期。现在你可以看到颜色在蓝色和红色之间变化。
这比我们想要的要模糊一点。行星通常在陆地和海洋之间有一个相对清晰的分离。为了做到这一点,我们将把最后一个学期改为 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
如果第三个论点大于第二个论点,并且在 0
和 1
如果第三个数字在第一个和第二个之间。所以在这条线上, smoothstep
收益率 0
无论何时 n
小于 -0.1
它又回来了 1
无论何时 n
在上面 0
.
还有一件事要让地球看起来更像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;
现在这个星球看起来像:
在打开着色的情况下,它看起来像:
制造海洋¶
最后一件事就是让这个看起来更像一个行星。海洋和陆地反射光的方式不同。所以我们希望海洋比陆地更明亮一点。我们可以通过将第四个值传递到 alpha
我们的输出通道 COLOR
并将其用作粗糙度图。
COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);
此行返回 0.3
对于水和 1.0
为了土地。这意味着土地将变得相当粗糙,而水将非常光滑。
然后,在材料的“金属”部分下,确保 Metallic
设置为 0
和 Specular
设置为 1
. 原因是水能很好地反射光线,但不是金属的。这些值在物理上并不精确,但对于本演示来说已经足够好了。
接下来,在“粗糙度”部分下,设置 Roughness
到 1
并将粗糙度纹理设置为 Viewport Texture 指向我们的星球纹理 Viewport . 最后,设置 Texture Channel
到 Alpha
. 这将指示渲染器使用 alpha
我们的输出通道 COLOR
作为 Roughness
价值。
你会注意到,除了行星不再反射天空之外,几乎没有什么变化。这是因为,默认情况下,当使用alpha值渲染某个对象时,它会在背景上绘制为透明对象。而且由于默认的背景 Viewport 是不透明的, alpha
的频道 Viewport Texture 是 1
从而使行星的纹理被绘制成略淡的颜色和 Roughness
价值 1
到处都是。要更正此问题,我们将进入 Viewport 并将“transparent bg”设置为“on”。由于我们现在正在将一个透明对象呈现在另一个对象之上,因此我们希望启用 blend_premul_alpha
:
render_mode blend_premul_alpha;
这将颜色预乘 alpha
然后将它们正确地混合在一起。通常,在将一种透明颜色与另一种颜色混合时,即使背景具有 alpha
属于 0
(就像在这个例子中一样),你最终会出现奇怪的颜色出血问题。设置 blend_premul_alpha
解决了这个问题。
现在这个星球看起来应该像是在海洋上反射光,而不是在陆地上。如果您还没有这样做,请添加一个 OmniLight 到场景中,这样你就可以四处移动并看到反射对海洋的影响。
就在这里。程序行星 Viewport .