Unity5.6大规模地形—地形资源构建(3)

来源:互联网 发布:淘宝上出现奇怪的字 编辑:程序博客网 时间:2024/04/20 00:50

在上一篇http://blog.csdn.net/fcauto2012/article/details/72377565中,我们主要讨论了如何给地形资源TerrainData类型添加从WorldMachine中导出的贴图。事实上,这种一块地形使用整张贴图的效果并不是很理想,地形尺寸比较大的时候不能很好地控制细节效果。一种更加常见的做法是使用多种贴图材质,应用splatmap进行混合,本文将讨论如何从WorldMachine中导出splatmap,并在Unity中加以使用。另外,我们将会对World Machine进行一个简单介绍。

1. World Machine简介

个人认为World Machine是一款非常优秀的地形生成软件,不仅能够实现一些真实地形的特效,而且熟练掌握后开发速度会非常快,简直是游戏基础地形制作的利器!也许你从Asset Store上下载过各种Perlin Noise、Erosion的插件,应用Unity自带的Terrain引擎不断地刷啊刷~~但是在World Machine里,这些特性不仅全部可以实现,而且你将采用的是这样的方式:


每一个复杂的功能都被封装成了一个模块,是不是觉得有些像MATLAB/Simulink?在WM里制作地形感觉不太像是在做美工,更像是在编程。下面是我按照YouTube上一个教程制作的地形:


是的,WM不仅可以生成高度,还可以添加颜色!对于开发者,可能更加关注如何导出到引擎中,WM支持各种格式的heightmap和texture导出,支持分块导出(tile)。这样一款专业的地形制作软件,并不像Photoshop或者3dmax那样“体型庞大”,安装完毕也仅仅占用不到40MB空间而已,对配置也没啥要求(如果配置比较好在最终build时会快些)。总之,我向大家强烈推荐这个软件,目前官网上只提供beta版本不支持高分辨率也不支持分块导出,大家可以到我的下载频道获取professional版,还有一些Youtube上的教程会很有帮助。那么下面我们来讨论如何在WM中制作splatmap。

2. Splatmap!

Splatmap的概念在上一篇有介绍,当时由于没有应用,理解得并不是很好。splatmap(和alphamap、splatalpha我感觉是一个意思)相当于一个“mask”,它使用RGBA中的几个或所有通道,来控制不同纹理的分布,达到多个纹理在同一地形上的混合。那么首先我们要决定RGBA各个通道所对应的区域,一般来讲,可以根据高度、坡度等特征来分配。对于上述地形,我在WM中进行了如下设计:


我对RGB三个通道使用了坡度和高度,而对A(alpha)通道使用了高度选择。在WM中只需这样拖动几个模块进行简单设置,如果在Unity中就免不了要写许多代码或者下载一些插件了。导出得到的splatmap如图:

  

实际结果应该是右边那幅,因为WM的SelectHeight模块比较奇葩的特性,alpha通道太过耀眼了...其实导入到Unity中之后更像左边这张没有alpha通道的。下面我们就为RGBA四个通道分别分配一张纹理,这里就先用Standard Assets里面的了:


R和G通道选择的基本是山地,我想让它们带有不同的岩石效果;而B通道基本是平地,我要让它带有草地的效果;而A通道是河流和湖泊,以后要为它们制作水效果,这里先用泥土效果的纹理代替。然而到了这里我们发现Unity Editor其实并没有设置splatmap的功能,因此我们又要自行拓展了~WM官网有一个不错的工具,链接戳这里http://www.world-machine.com/learn.php?page=workflow&workflow=wfunity,但如果使用5.0以上版本需要进行一点修改,看这里https://www.reddit.com/r/Unity3D/comments/601tm/importing_world_machine_splatmap_into_unity_55/。下面上我自己写的C#代码:

using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEditor;using System.IO;using System;public class ImportTerrain : MonoBehaviour{    [MenuItem("AssetDatabase/ImportTerrain")]    static void GenerateTerrain()    {        string terrainDataPath = "Assets/MyTerrain/MyTerrain.asset"; //路径        //创建TerrainData对象        TerrainData terrainData = (TerrainData)AssetDatabase.LoadAssetAtPath(            terrainDataPath, typeof(TerrainData));        if (!terrainData)        {            terrainData = new TerrainData();            AssetDatabase.CreateAsset(terrainData, terrainDataPath);        }                //地形设置        terrainData.heightmapResolution = 1025; //要先设置这个参数,再设置terrainSize        Vector3 terrainSize = new Vector3(2400, 400, 2400); //地形的大小,最高、最低点的差值        terrainData.size = terrainSize;        float[,] height = new float[1025, 1025]; //地形高度数组        //读取高度文件        FileInfo hmFile = new FileInfo(@"Assets/MyTerrain/output.r16");        FileStream hmFs = hmFile.OpenRead();        const int BYTESIZE = 1025 * 1025 * 2;        byte[] hmB = new byte[BYTESIZE];        hmFs.Read(hmB, 0, BYTESIZE); //读取.r16字节流        hmFs.Close();        //赋值        int i = 0;        for (int x = 0; x < 1025; x++)        {            for (int y = 0; y < 1025; y++)            {                //注意两点:                //Windows字节序                //右手系转换到左手系                height[1024-x, y] = (hmB[i++] + hmB[i++] * 256.0f) / 65535.0f;            }        }        terrainData.SetHeights(0, 0, height);                 //texture!        //加贴图        Vector2 texSize = new Vector2(80, 80);        string terrainTexPath = "Assets/Standard Assets/Environment/TerrainAssets/SurfaceTextures/";        //Cliff        string tex0Path = terrainTexPath + "CliffAlbedoSpecular.psd";        Texture2D tex0 = AssetDatabase.LoadAssetAtPath(tex0Path, typeof(Texture2D)) as Texture2D;        SplatPrototype splat0 = new SplatPrototype();        splat0.texture = tex0;        splat0.tileSize = texSize;        //Rock        string tex1Path = terrainTexPath + "GrassRockyAlbedo.psd";        Texture2D tex1 = AssetDatabase.LoadAssetAtPath(tex1Path, typeof(Texture2D)) as Texture2D;        SplatPrototype splat1 = new SplatPrototype();        splat1.texture = tex1;        splat1.tileSize = texSize;        //Plane        string tex2Path = terrainTexPath + "GrassHillAlbedo.psd";        Texture2D tex2 = AssetDatabase.LoadAssetAtPath(tex2Path, typeof(Texture2D)) as Texture2D;        SplatPrototype splat2 = new SplatPrototype();        splat2.texture = tex2;        splat2.tileSize = texSize;        //River        string tex3Path = terrainTexPath + "MudRockyAlbedoSpecular.bmp";        Texture2D tex3 = AssetDatabase.LoadAssetAtPath(tex3Path, typeof(Texture2D)) as Texture2D;        SplatPrototype splat3 = new SplatPrototype();        splat3.texture = tex3;        splat3.tileSize = texSize;                terrainData.splatPrototypes = new SplatPrototype[] { splat0, splat1, splat2, splat3 };        terrainData.RefreshPrototypes();        //alphamap设置        terrainData.alphamapResolution = 1024;        string splatmapPath = "Assets/MyTerrain/splatA.png";        Texture2D splatmap = AssetDatabase.LoadAssetAtPath(splatmapPath, typeof(Texture2D)) as Texture2D;        float[, ,] alpha = new float[1024, 1024, 4];        for (int x = 0; x < 1024; x++)        {            for (int y = 0; y < 1024; y++)            {                alpha[y, x, 0] = splatmap.GetPixel(x, y).r;                alpha[y, x, 1] = splatmap.GetPixel(x, y).g;                alpha[y, x, 2] = splatmap.GetPixel(x, y).b;                alpha[y, x, 3] = splatmap.GetPixel(x, y).a;            }        }        terrainData.SetAlphamaps(0, 0, alpha); //一切都是为了这个方法...    }}
和之前的一样,也是Editor脚本,创建一个AssetDataBase->ImportTerrain的选项,而且//texture!注释之前的代码基本都是重复的,我们重点从这里开始看。在上一篇里,我们介绍了TerrainData中SplatPrototype这一重要成员,所有应用的贴图都要放在这里,不同的是这里我们放了4张texture,分别对应RGBA四个通道。设置alphamap的核心方法是TerrainData的SetAlphamaps方法,关键参数是alpha三维数组。它的前两维表示了splatmap的大小,最后一维表示layer数目,也就是所使用的splatmap的通道数,这里我们使用RGBA所以应为4。由于Unity脚本并未提供更加简便的方式,这里我们只能一个像素一个像素地设置,还好Texture2D类型为我们直接提供了获取RGBA值的方法,需要知道每一个值都是0~1的float类型(0代表黑色,1代表白色)。这里要注意的是,SplatPrototype中贴图的数目需要和alpha的通道数对应,如果前者比后者少,那么程序会报错。

效果如下:




原创粉丝点击