使用视区作为纹理

简介

This tutorial will introduce you to using the Viewport as a texture that can be applied to 3D objects. In order to do so, it will walk you through the process of making a procedural planet like the one below:

../../_images/planet_example.png

注解

本教程没有介绍如何编写像这个星球那样的动态氛围。

This tutorial assumes you are familiar with how to set up a basic scene including: a Camera, a light source, a Mesh Instance with a Primitive Mesh, and applying a SpatialMaterial to the mesh. The focus will be on using the Viewport to dynamically create textures that can be applied to the mesh.

In this tutorial, we’ll cover the following topics:

  • 如何使用 Viewport 作为渲染纹理
  • 使用equirectangular映射将纹理映射到球体
  • 程序行星的碎片着色器技术
  • 从a Viewport Texture 设置粗糙度贴图

设置视区

首先,在场景中添加 Viewport

Next, set the size of the Viewport to (1024, 512). The Viewport can actually be any size so long as the width is double the height. The width needs to be double the height so that the image will accurately map onto the sphere, as we will be using equirectangular projection, but more on that later.

../../_images/planet_new_viewport.png

Next, disable HDR and disable 3D. We don’t need HDR because our planet’s surface will not be especially bright, so values between 0 and 1 will be fine. And we will be using a ColorRect to render the surface, so we don’t need 3D either.

选择视区并添加 ColorRect 作为子项。

将锚“Right”和“Bottom”设置为“1”,然后确保所有边距都设置为“0”。 这将确保 ColorRect 占用整个 Viewport

../../_images/planet_new_colorrect.png

Next, we add a Shader Material to the ColorRect (ColorRect > CanvasItem > Material > Material > New ShaderMaterial).

注解

Basic familiarity with shading is recommended for this tutorial. However, even if you are new to shaders, all the code will be provided, so you should have no problem following along.

颜色矩形>画布组件>材质>材质>点击/编辑>着色器材质>着色器>“新建着色器”>点击/编辑:

  1. shader_type canvas_item;
  2. void fragment() {
  3. COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
  4. }

上面的代码呈现如下所示的渐变。

../../_images/planet_gradient.png

现在我们有一个基础 Viewport 我们渲染到的,我们有一个可以应用于球体的独特图像。

应用纹理

网格实例>几何体实例>几何体>材质覆盖>“新建空间材质”:

Now we go into the Mesh Instance and add a SpatialMaterial to it. No need for a special Shader Material (although that would be a good idea for more advanced effects, like the atmosphere in the example above).

网格实例>几何体实例>几何体>材质覆盖>“点击/编辑”:

Open the newly created SpatialMaterial and scroll down to the “Albedo” section and click beside the “Texture” property to add an Albedo Texture. Here we will apply the texture we made. Choose “New ViewportTexture”

../../_images/planet_new_viewport_texture.png

Then, from the menu that pops up, select the Viewport that we rendered to earlier.

../../_images/planet_pick_viewport_texture.png

现在,您的球体应使用我们渲染到视区的颜色进行着色。

../../_images/planet_seam.png

Notice the ugly seam that forms where the texture wraps around? This is because we are picking a color based on UV coordinates and UV coordinates do not wrap around the texture. This is a classic problem in 2D map projection. Game developers often have a 2-dimensional map they want to project onto a sphere, but when it wraps around, it has large seams. There is an elegant workaround for this problem that we will illustrate in the next section.

使行星纹理

所以现在我们渲染到我们的 Viewport 它在球体上神奇地出现了。 但是我们的纹理坐标会产生一个丑陋的缝隙。 那么我们如何以一种很好的方式获得围绕球体的一系列坐标? 一种解决方案是使用在纹理域上重复的函数。 ``sin``和``cos``是两个这样的函数。 让我们将它们应用于纹理,看看会发生什么。

  1. 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

Not too bad. If you look around, you can see that the seam has now disappeared, but in its place, we have pinching at the poles. This pinching is due to the way Godot maps textures to spheres in its SpatialMaterial. It uses a projection technique called equirectangular projection, which translates a spherical map onto a 2D plane.

注解

If you are interested in a little extra information on the technique, we will be converting from spherical coordinates into Cartesian coordinates. Spherical coordinates map the longitude and latitude of the sphere, while Cartesian coordinates are, for all intents and purposes, a vector from the center of the sphere to the point.

For each pixel, we will calculate its 3D position on the sphere. From that, we will use 3D noise to determine a color value. By calculating the noise in 3D, we solve the problem of the pinching at the poles. To understand why, picture the noise being calculated across the surface of the sphere instead of across the 2D plane. When you calculate across the surface of the sphere, you never hit an edge, and hence you never create a seam or a pinch point on the pole. The following code converts the UVs into Cartesian coordinates.

  1. float theta = UV.y * 3.14159;
  2. float phi = UV.x * 3.14159 * 2.0;
  3. vec3 unit = vec3(0.0, 0.0, 0.0);
  4. unit.x = sin(phi) * sin(theta);
  5. unit.y = cos(theta) * -1.0;
  6. unit.z = cos(phi) * sin(theta);
  7. unit = normalize(unit);

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

../../_images/planet_normals.png

Now that we can calculate the 3D position of the surface of the sphere, we can use 3D noise to make the planet. We will be using this noise function directly from a Shadertoy:

  1. vec3 hash(vec3 p) {
  2. p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
  3. dot(p, vec3(269.5, 183.3, 246.1)),
  4. dot(p, vec3(113.5, 271.9, 124.6)));
  5. return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
  6. }
  7. float noise(vec3 p) {
  8. vec3 i = floor(p);
  9. vec3 f = fract(p);
  10. vec3 u = f * f * (3.0 - 2.0 * f);
  11. return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
  12. dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
  13. mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
  14. dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),
  15. mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
  16. dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
  17. mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
  18. dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );
  19. }

注解

All credit goes to the author, Inigo Quilez. It is published under the MIT licence.

现在使用 noised ,将以下内容添加到 fragment 函数中:

  1. float n = noise(unit * 5.0);
  2. COLOR.xyz = vec3(n * 0.5 + 0.5);

../../_images/planet_noise.png

注解

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

You can see now that the noise indeed wraps seamlessly around the sphere. Although this looks nothing like the planet you were promised. So let’s move onto something more colorful.

着色这个星球

Now to make the planet colors. While there are many ways to do this, for now, we will stick with a gradient between water and land.

要在 GLSL 中创建渐变,我们使用 mix 函数。mix 需要两个值来插值和第三个参数来选择在它们之间插入多少,实质上它*将两个值混合在一起。 在其他API中,此函数通常称为 lerp 。虽然 lerp 通常用于将两个浮点数混合在一起,但 mix 可以取任何值,无论它是浮点数还是向量类型。

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

The first color is blue for the ocean. The second color is a kind of reddish color (because all alien planets need red terrain). And finally, they are mixed together by n * 0.5 + 0.5. n smoothly varies between -1 and 1. So we map it into the 0-1 range that mix expects. Now you can see that the colors change between blue and red.

../../_images/planet_noise_color.png

That is a little more blurry than we want. Planets typically have a relatively clear separation between land and sea. In order to do that, we will change the last term to smoothstep(-0.1, 0.0, n). And thus the whole line becomes:

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

What smoothstep does is return 0 if the third argument is below the first and 1 if the third argument is larger than the second and smoothly blends between 0 and 1 if the third number is between the first and the second. So in this line, smoothstep returns 0 whenever n is less than -0.1 and it returns 1 whenever n is above 0.

../../_images/planet_noise_smooth.png

One more thing to make this a little more planet-y. The land shouldn’t be so blobby; let’s make the edges a little rougher. A trick that is often used in shaders to make rough looking terrain with noise is to layer levels of noise over one another at various frequencies. We use one layer to make the overall blobby structure of the continents. Then another layer breaks up the edges a bit, and then another, and so on. What we will do is calculate n with four lines of shader code instead of just one. n becomes:

  1. float n = noise(unit * 5.0) * 0.5;
  2. n += noise(unit * 10.0) * 0.25;
  3. n += noise(unit * 20.0) * 0.125;
  4. n += noise(unit * 40.0) * 0.0625;

现在这个星球看起来像:

../../_images/planet_noise_fbm.png

And with shading turned back on, it looks like:

../../_images/planet_noise_fbm_shaded.png

制作海洋

让这个看起来更像是一颗行星的最后一件事。 海洋和陆地以不同的方式反射光线。 因此,我们希望海洋比陆地更加闪耀。 我们可以通过将第四个值传递到输出 COLOR 的``alpha``通道并将其用作粗糙度图来实现。

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

This line returns 0.3 for water and 1.0 for land. This means that the land is going to be quite rough, while the water will be quite smooth.

And then, in the material, under the “Metallic” section, make sure Metallic is set to 0 and Specular is set to 1. The reason for this is the water reflects light really well, but isn’t metallic. These values are not physically accurate, but they are good enough for this demo.

Next, under the “Roughness” section, set Roughness to 1 and set the roughness texture to a Viewport Texture pointing to our planet texture Viewport. Finally, set the Texture Channel to Alpha. This instructs the renderer to use the alpha channel of our output COLOR as the Roughness value.

../../_images/planet_ocean.png

You’ll notice that very little changes except that the planet is no longer reflecting the sky. This is happening because, by default, when something is rendered with an alpha value, it gets drawn as a transparent object over the background. And since the default background of the Viewport is opaque, the alpha channel of the Viewport Texture is 1, resulting in the planet texture being drawn with slightly fainter colors and a Roughness value of 1 everywhere. To correct this, we go into the Viewport and enable the “Transparent Bg” property. Since we are now rendering one transparent object on top of another, we want to enable blend_premul_alpha:

  1. render_mode blend_premul_alpha;

This pre-multiplies the colors by the alpha value and then blends them correctly together. Typically, when blending one transparent color on top of another, even if the background has an alpha of 0 (as it does in this case), you end up with weird color bleed issues. Setting blend_premul_alpha fixes that.

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

../../_images/planet_ocean_reflect.png

您有它。 使用 Viewport 生成的简单程序行星。