使用 Viewport 作为纹理
前言
Viewport 作为可应用于 3D 对象的纹理。为了做到这一点,它将引导您完成制作程序式星球的过程,如下所示: 注解 本教程没有介绍如何编写像这个星球那样的动态氛围. Camera, 一个 light source, 一个 Mesh Instance 与一个 Primitive Mesh, 并将一个 SpatialMaterial 应用于网格. 重点是使用 Viewport 来动态地创建可应用于网格的纹理. 在本教程中, 我们将介绍以下主题:
- Viewport 作为渲染纹理
- 使用equirectangular映射将纹理映射到球体
- 程序式行星的片段着色器技术
- Viewport Texture 设置粗糙度贴图
设置视区
Viewport . Viewport 的大小设置为(1024, 512). Viewport 实际上可以是任何尺寸, 只要其宽度是高度的两倍. 宽度需要是高度的两倍, 这样图像才能准确地映射到球体上, 因为我们将使用等量矩形投影, 后面会有更多的介绍.0和1之间就可以了. 将使用一个 ColorRect 来渲染表面, 所以我们也不需要3D. ColorRect 作为子项. ColorRect 占用整个 Viewport. Shader Material 添加一个 ColorRect (ColorRect > CanvasItem > Material > Material >New ShaderMaterial). 注解 建议本教程基本了解阴影. 但是, 即使您不熟悉着色器, 也将提供所有代码, 因此后续操作应该没有问题. 颜色矩形>画布组件>材质>材质>点击/编辑>着色器材质>着色器>”新建着色器”>点击/编辑:
上面的代码呈现如下所示的渐变. Viewport 我们渲染到的, 我们有一个可以应用于球体的独特图像.shader_type canvas_item;void fragment() { COLOR = vec4(UV.x, UV.y, 0.5, 1.0);}
应用纹理
网格实例>几何体实例>几何体>材质覆盖>”新建空间材质”: Mesh Instance 并添加一个 SpatialMaterial 给它. 不需要一个特殊的 Shader Material (尽管这对于更高级的效果来说是个好主意, 比如上面例子中的大气). 网格实例>几何体实例>几何体>材质覆盖>”点击/编辑”: SpatialMaterial , 向下滚动到 “Albedo” 部分, 点击 “Texture” 属性旁边, 添加一个Albedo Texture. 这里我们将应用制作的纹理. 选择 “New ViewportTexture” 然后, 从弹出的菜单中, 选择我们先前渲染的视图. 现在, 您的球体应使用我们渲染到视区的颜色进行着色. 注意到在纹理环绕的地方形成的丑陋缝隙吗?这是因为我们是根据UV坐标来选取颜色的, 而UV坐标并不会环绕纹理. 这是二维地图投影中的一个典型问题. 游戏开发人员通常有一个二维贴图, 他们想投射到一个球体上, 但是当它环绕时, 将有接缝. 这个问题有一个优雅的解决方法, 我们将在下一节中说明.使行星纹理
Viewport 它在球体上神奇地出现了. 但是我们的纹理坐标会产生一个丑陋的缝隙. 那么我们如何以一种很好的方式获得围绕球体的一系列坐标? 一种解决方案是使用在纹理域上重复的函数.sin和cos是两个这样的函数. 让我们将它们应用于纹理, 看看会发生什么.
SpatialMaterial 中把纹理映射到球体的方式造成的. 它使用了一种叫做 “等角投影” 的投影技术, 将球面图形转化为一个二维平面. 注解 如果你对技术方面的一些额外信息感兴趣,我们将从球面坐标转换为直角坐标。球面坐标映射的是球体的经度和纬度,而直角坐标则是从球体中心到点的一个向量。 对于每个像素, 我们将计算它在球体上的三维位置. 由此, 我们将使用3D噪波来确定一个颜色值. 通过计算3D噪波, 我们解决了两极的挤压问题. 要理解为什么, 想象一下在球体表面而不是在二维平面上计算噪声. 当你跨越球体表面进行计算时, 你永远不会碰到边缘, 因此你永远不会在极点上产生接缝或夹点. 下面的代码将 “UVs” 转换为笛卡尔坐标.COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
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许可下发布的.noised, 将以下内容添加到fragment函数中:
注解 为了突出显示纹理, 我们将材质设置为无阴影. 你现在可以看到, 尽管这看起来完全不像所承诺的球体, 但噪音确实无缝地包裹着球体. 对此, 让我们进入一些更丰富多彩的东西.float n = noise(unit * 5.0);COLOR.xyz = vec3(n * 0.5 + 0.5);
着色这个星球
现在来制作行星的颜色. 虽然有很多方法可以做到这一点, 但目前, 我们将使用水和陆地之间的梯度.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之间平滑变化. 所以我们把它映射到mix预期的0-1范围内. 现在你可以看到, 颜色在蓝色和红色之间变化.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之间平滑地混合. 所以在这一行中, 当n小于-0.1时,smoothstep返回0, 当n高于0时, 它返回1.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;
制作海洋
COLOR的alpha通道并将其用作粗糙度图来实现.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. 这将指示渲染器使用我们输出的COLOR的alpha通道作为Roughness值. Viewport 的默认背景是不透明的, 所以 Viewport Texture 的alpha通道是1, 导致行星纹理的颜色略微变淡, 并且到处的Roughness值都是1. 为了纠正这个问题, 我们转到 Viewport 并启用 “Transparent Bg” 属性. 由于现在是在另一个透明物体上渲染, 要启用blend_premul_alpha:render_mode blend_premul_alpha;
alpha值, 然后将它们正确地混合在一起. 通常情况下, 当在一个透明的颜色上混合另一个颜色时, 即使背景的alpha为0(如本例), 也会出现奇怪的颜色渗漏问题. 设置blend_premul_alpha可以解决这个问题. OmniLight , 这样你就可以移动它, 看到海洋上反射的效果. Viewport 生成的程序式行星。
