大家好,OTUS将于6月再次启动Unity游戏开发人员课程。预期课程的开始,我们准备了关于该主题的有趣材料的翻译。
今天,我们将讨论如何在Unity中混合地形网格物体(或其他网格物体)。该指南非常先进,但我尝试将其分解为单独的步骤。假定您具有一般的Unity技能和基本的C#知识。该指南是为放大而设计的,但我认为它也可以应用于着色器图。这是我的第一本指南,因此我希望它会足够清楚。如果您想添加一些内容,请告诉我。我在这里包括了一个入门工具包,其中包含该项目的一些其他着色器,以及用于入门的基本工具包。今天的手册中的所有项目文件都可以以5美元的价格提供给我的顾客,但在不久的将来,所有人都可以使用。→ 入门套件→ 整个项目注意:考虑到本指南的篇幅,我想确保您了解此着色器的优缺点。今天我们将讨论着色器的两个版本。第一个允许您混合一个纹理,因此着色器适用于高分辨率纹理,但事实证明您仅限于一个纹理。第二个着色器允许您混合低分辨率或一种颜色的任意数量的Terranes纹理(或经过某些修改的Terranes网格纹理),并在统一地形和地形网格物体上工作。这些着色器没有正常的全功能混合,这在无样式的游戏中很有用,但是有一种方法可以自己实现。
所以,让我们开始吧。首先,我将提出问题并深入研究解决方案的基础理论。要在人脸和其他网格物体之间进行混合,您需要以某种方式告诉着色器与人脸相交的位置。说起来比做起来容易,但是有办法。它由什么组成?渲染纹理将帮助我们!什么是渲染纹理?
实际上,“ 渲染纹理”是摄影机的位置,该位置保存在资产文件中。游戏中的“ 渲染纹理”最常用于创建视频监控屏幕之类的事情。我们可以使用“ 渲染纹理 ”(Render Texture)将相机视图保存在编辑器中,以便可以将该纹理用作着色器。这很有用,因为我们可以将有关地形的信息(例如高度,法线和颜色)烘焙到纹理中,然后在运行时将其用于在网格和Terran之间进行混合。用于在无标题鹅游戏中在电视上显示鹅图像的纹理渲染客制化
首先,让我们创建一个新的场景和地形。设置一些适合工作的尺寸,例如200x200。现在,您可以根据需要排列地形。然后创建一个新层并将其命名为“地形”,并为该层分配地形。这是必需的,以便相机蒙版可以在“ 渲染纹理”中记录地形。我的杰作terrane项目源文件有一个预制的名为“ BlendBakingCamera”的预制物 -将其拖到舞台上。您将获得一个简单的正交摄影机。在相机上,您需要在新的地形层上放置一个消隐面罩。将相机放置在地形中央,稍微高于地形最高点。然后调整远剪辑平面,以使摄像机看到地面。因此,场景应如下所示:替换着色器
设置好摄像机之后,您需要找到一种记录地形数据的方法。为此,我们需要替换着色器。替换着色器似乎是个可疑的前景;我本人很长一段时间都不了解它是如何工作的。但是实际上,一切都很简单有效。本质上,使用“ 替换着色器”意味着用单个着色器渲染摄像机视场中的每个对象,而不管对象上叠加了哪个着色器。结果,所有对象将使用选定的Replacement Shader(实际上只是一个常规着色器)进行渲染。我们需要混合的着色器是深度着色器。它渲染场景的深度,是创建我们的混合效果的关键组成部分,因为它将相机的深度值写入纹理,以便我们以后可以读取它们。要总体上了解有关此着色器和Replacement Shader的更多信息,建议您阅读Unity中的使东西看起来不错的本手册。深度着色器示例开始烘焙吧
让我们创建一个新类,并将其命名为“ TerrainBlendingBaker”。让我们从为基本地形实现深度蒙版开始。稍后,我们将返回此脚本以添加颜色和法线。定义几个变量。
public Shader depthShader;
public RenderTexture depthTexture;
private Camera cam;
现在,让我们创建一个新方法并将其命名为“ UpdateBakingCamera”。在这种方法中,我们将确定着色器在全局变量中渲染混合可能需要的相机数据。private void UpdateBakingCamera()
{
if (cam == null)
{
cam = GetComponent<Camera>();
}
Shader.SetGlobalFloat("TB_SCALE", GetComponent<Camera>().orthographicSize * 2);
Shader.SetGlobalFloat("TB_OFFSET_X", cam.transform.position.x - cam.orthographicSize);
Shader.SetGlobalFloat("TB_OFFSET_Z", cam.transform.position.z - cam.orthographicSize);
Shader.SetGlobalFloat("TB_OFFSET_Y", cam.transform.position.y - cam.farClipPlane);
Shader.SetGlobalFloat("TB_FARCLIP", cam.farClipPlane);
}
现在让我们将地面的深度烘烤到纹理中。
[ContextMenu("Bake Depth Texture")]
public void BakeTerrainDepth()
{
UpdateBakingCamera();
if (depthShader != null && depthTexture != null)
{
cam.SetReplacementShader(depthShader, "RenderType");
cam.targetTexture = depthTexture;
Shader.SetGlobalTexture("TB_DEPTH", depthTexture);
}
else
{
Debug.Log("You need to assign the depth shader and depth texture in the inspector");
}
}
当我们开始使用着色器时,这些值将变得更加清晰。这是一张小图片,可能会有所启发:我们将把地形转换为深度纹理,以便稍后阅读并了解在哪里进行混合,现在我们已经具备在着色器中创建基本混合效果所需的一切。此时,脚本看起来像这样:pastebin.com/xNusLJfh好吧,现在有了脚本,我们可以将其添加到之前添加的烘焙相机中。初始资产有一个名为“ DepthShader”的着色器(Inresin / Shaders / DepthShader)和一个名为“ DepthTerrainRT”的渲染纹理(Inresin / RenderTextures / DepthTextureRT),您需要将它们放在检查器中的相应字段中。之后,只需在上下文菜单中运行该方法即可在“ 渲染纹理”中烘焙地形深度。着色器
最后,让我们创建一个用于混合的着色器。创建一个新的标准放大着色器并将其打开,将其命名为“ TerrainBlending”。现在我们需要为渲染纹理创建UV 。这将是正在渲染的点与相对于总面积缩放的烘焙相机位置之间的差。这里的三个全局变量是我们刚刚在代码中声明的变量。我们还将worldY设置为局部变量,稍后我们将需要它。
让我们看一下深度纹理,将其指定为全局变量(对于此添加纹理样本节点,将其设置为全局并命名为“ TB_DEPTH”),如果将输出放在debug amplify shader'a field中,我们可以看到会发生什么。使用将要应用我们的新着色器的材质创建一个平面。因此,在着色器中,我们具有有关深度的信息,现在我们需要在y中添加一个偏移量以进行混合。
该块缩放远剪贴平面蒙版的y位置,沿着渲染点的y轴从世界位置减去该值,然后最终将其移动到相机边界框的下侧(相机的y位置减去远剪贴平面)。已经有东西了!我们看到了平面的边缘如何与地形融合。
好的,让我们更加控制混合。
现在我们可以控制混合地形的厚度和面积了。
我想我们甚至可以增加一点噪音。让我们使用世界位置从纹理生成噪声。
噪声纹理位于启动项目的纹理文件夹中,可以在检查器中指定它们,也可以在着色器本身中将其指定为常量。最后是时候添加一些纹理了!首先,让我们使用一些简单的单色纹理,我向带有纹理资源的文件夹中添加了两个纹理。使“ SingleColorGrass”纹理成为地形纹理。然后,在着色器中,您需要创建一个Terrane纹理和一个对象纹理节点。我们将在刚创建的蒙版的红色通道上在它们之间切换。
这是完整的着色器,
添加自定义香椿照明或未照明的模型将为该着色器提供最佳效果。我打开不亮赞助商可以使用完整软件包中的Terrane着色器和未着色的着色器版本。未照明的Terrane和网格物体我还建议为该地形和其他网格物体添加一个三面体着色器。我可以在下一指南中考虑此问题。好吧,我们几乎已经完成了本指南的主题。我添加了一些可以帮助着色器扩展的部分。着色器扩展-正常
我在文件中添加了常规着色器,您可以使用它将法线写入到地面,也可以使用混合。对于我的游戏,我不需要常规的混合,所以我做了一些实验,看起来实验很成功,而且我的想法适用于高度没有高度变化的地形。我在目录中将普通映射着色器与起始集着色器一起包括在内。在这里,您可以看到我对法线贴图的基本实现:
代码与深度图几乎相同,但是这次我们将法线贴图用作替换着色器。我还添加了一组渲染纹理以记录法线(此处可能需要其他配置)。为了使法线正常工作,可能需要进行一些与调整有关的工作,并且还可能存在与低级地形有关的限制(但我没有进行足够的测试来确认这一点)。着色器扩展-所有颜色
我不会深入探讨该主题的细节,因为它超出了我的游戏所需的范围,但是我在编写本指南时就想到了这一点。要添加多种颜色的混合,我们可以选择不发光的地形颜色并将其另存为纹理。在这种情况下,我们受到相当低分辨率的限制,但是当使用单色地形纹理和低分辨率纹理或使用模糊出血时,此方法效果很好。进行较小的调整,它们也可以应用于地形网格物体。这是多色选项的代码:[Header("The following settings are only if using the multi-color terrain shader")]
public Shader unlitTerrainShader;
public RenderTexture surfaceTexture;
public Material unlitTerrainMaterial;
public Terrain yourTerrain;
[ContextMenu("Bake Surface Texture")]
public void BakeTerrainSurface()
{
UpdateBakingCamera();
if (yourTerrain == null)
{
Debug.Log("You need to assign a terrain to capture surface texture");
return;
}
StartCoroutine(BakeColors());
}
IEnumerator BakeColors()
{
Material tempTerrainMaterial = yourTerrain.materialTemplate;
yourTerrain.materialTemplate = unlitTerrainMaterial;
yield return 0;
cam.SetReplacementShader(unlitTerrainShader, "RenderType");
cam.targetTexture = surfaceTexture;
Shader.SetGlobalTexture("TB_SURFACE", surfaceTexture);
yield return 0;
cam.targetTexture = null;
yourTerrain.materialTemplate = tempTerrainMaterial;
yield return null;
}
着色器的唯一变化是,我们使用全局位置定义的地形表面纹理,而不是使用预定义的纹理,该纹理将相对位置用作UV。附加扩展使您可以更舒适地混合纹理。
混合多个纹理
这是带有普通混合着色器的完整多色图形:
结论
恭喜您已经阅读了这一点!正如我所说,这是我的第一本指南,因此,我非常感谢您提供反馈。如果您想支持我创建游戏和教程,请在此处查看或订阅我的Twitter。
了解有关该课程的更多信息