2.7 Gaia综合地形系统:程序化景观生成与生态化布局实践

2.7.1 一体化地形生态系统构建理念

在三维游戏开发、虚拟仿真和数字孪生领域,地形的创建从来不是孤立的地表隆起与凹陷,而是一个包含地质基底、土壤覆盖、植被群落和人工结构的完整生态系统构建过程。传统的地形制作流程通常被分割为高度图编辑、纹理绘制、植被散布等多个独立环节,由不同专业人员使用不同工具完成,这一方面导致工作流断裂、迭代成本高昂,另一方面难以保证各个元素之间的自然协调与生态一致性。

Gaia插件正是为解决这一系统性难题而设计的Unity全流程地形解决方案。它的核心设计理念是"一体化生态生成",将地形的地质形成、地表纹理、植被分布乃至天气光照等环境要素纳入一个统一、连贯、参数化的工作框架中。Gaia将现实世界的地貌演变原理抽象为可交互操作的计算机算法,允许开发者从宏观的地形构架开始,逐步细化到每一片草叶的分布,整个过程中所有元素都基于统一的规则和遮罩系统相互关联、相互影响。

从技术哲学角度看,Gaia代表着从"手工雕刻"到"生态培育"的地形创作范式转变。它不再将地形视为静态的几何模型,而是看作一个动态的、具有生长逻辑的有机体。开发者扮演的是"环境设计师"而非"模型雕刻师"的角色:通过设定基础地质参数(如板块强度、侵蚀周期)、定义生物群落规则(如温度带、湿度带)和配置物种竞争关系,然后让系统基于这些规则"生长"出自然协调的场景。这种方法的优势在于,它生成的地形不仅在视觉上符合自然规律,在空间逻辑上也保持了生态合理性——树木会出现在合适的土壤和坡度上,岩石会在侵蚀严重的山脊裸露,不同植被会根据海拔和湿度自然过渡。

Gaia的系统价值体现在三个维度:在效率层面,它通过自动化流程将数周的手工工作压缩到数小时;在质量层面,它通过程序化规则保证了场景元素之间的自然协调,避免了人工布景常出现的重复模式或不合理分布;在一致性层面,它维护了从宏观地形到微观细节的统一风格指南,确保大规模场景的风格连贯性。对于开放世界游戏、虚拟现实环境和大型仿真项目,这种一体化解决方案提供了不可替代的生产力优势。

2.7.2 Gaia核心系统架构与功能原理

Gaia的架构设计遵循"分层生成、规则驱动"的原则,其功能系统可划分为四个相互关联又相对独立的子系统:地形生成系统、纹理绘制系统、资源散布系统和环境光照系统。每个子系统都基于一套可扩展的规则引擎,并通过统一的遮罩框架进行通信与协作。

地形生成系统是Gaia的基石,它采用了一种名为"图章(Stamping)“的独特地形构建方法。与传统的高度图绘制或噪声生成不同,图章系统允许开发者使用预定义或自定义的灰度图(称为"图章”)作为地形"画笔",像盖印章一样在地形上叠加不同的地貌特征。每个图章代表一种基本的地貌单元,如山峰、山谷、山脊、高原、河床等。系统通过多层图章的叠加、混合与侵蚀模拟,构建出复杂的地质结构。

图章系统的核心技术优势在于它的直观性与可控性。开发者可以从图章库中选择一个山峰图章,设置其大小、旋转和强度,然后"盖"在指定位置,一个符合该图章形状的山峰就会在地形上生成。多个图章可以按特定顺序叠加,模拟真实的地质沉积过程:首先用基础地形图章确定大陆板块,然后用山脉图章添加主要山系,再用侵蚀图章雕刻河谷,最后用细节图章添加小山丘和碎石地带。每个图章都可以设置影响范围、衰减曲线和混合模式,提供了艺术家级的控制精度。

从算法实现角度看,图章叠加本质上是多层灰度图的混合运算。Gaia采用了智能混合算法,能够根据图章类型自动选择合适的混合方式:对于基础地形使用加法混合,对于侵蚀特征使用减法混合,对于细节纹理使用屏幕混合等。更重要的是,Gaia记录了每个图章的应用历史和参数,允许非破坏性编辑——任何时候都可以返回调整某个图章的参数或位置,系统会自动重新计算所有后续图章的影响,而不需要从头开始。

纹理绘制系统负责在地形表面生成符合地质规律的材质分布。与传统的地形纹理绘制工具不同,Gaia的纹理系统是"基于规则"而非"基于笔刷"的。系统通过一组称为"纹理生成器(Texture Spawners)"的规则模块,根据地形的高度、坡度、朝向等固有属性,自动分配不同的纹理材质。

一个典型的纹理规则可能是:“在海拔0-100米、坡度小于30度的区域使用沙地纹理;在海拔100-500米、北向坡度小于45度的区域使用草地纹理;在海拔500米以上或坡度大于45度的区域使用岩石纹理”。这些规则可以多层叠加,并支持复杂的遮罩混合。例如,可以在河流附近添加湿润土壤纹理,并在其与草地纹理之间创建平滑过渡。

Gaia纹理系统的先进之处在于其动态纹理技术。它不仅仅是在地形表面贴上静态图片,而是根据地形属性实时混合多种纹理层,并自动生成过渡区域。系统使用Unity的标准地形材质系统或第三方着色器(如CTS),支持法线贴图、高光贴图、遮挡贴图等PBR工作流,确保视觉质量达到3A游戏标准。此外,纹理系统还会自动生成纹理遮罩,供植被散布系统使用,确保植被类型与地面材质相匹配。

资源散布系统是Gaia的生态模拟核心,负责在地形上合理分布树木、花草、岩石、建筑物等场景资源。与Unity原生的细节散布系统相比,Gaia的资源散布引入了"模拟生态竞争"的概念。系统将每一种资源类型视为一个"物种",并为每个物种定义其生存偏好:喜阳还是喜阴、适合干燥还是潮湿、偏好平坦还是斜坡、耐寒还是耐热等。

资源散布的过程本质上是一个多物种竞争模拟:系统在地形上随机生成初始散布点,然后根据每个位置的微环境参数(光照、湿度、温度、土壤类型)和物种偏好计算"适宜度分数",只保留适宜度高的散布点。接下来进行竞争阶段:不同物种之间如果距离过近,会根据竞争规则(如大树遮蔽小树、岩石排斥植被)淘汰不占优势的个体。最后进行分布优化,确保散布结果既随机自然又符合生态规律。

这种基于规则的散布方式产生了几个重要优势:一是分布合理,树木不会出现在不可能生长的陡坡上,岩石会自然聚集在易侵蚀区域;二是群落自然,不同植被会形成符合生态学规律的群落结构;三是变化丰富,通过调整规则参数可以快速生成沙漠、森林、草原等不同生物群系;四是维护方便,当地形修改后,只需重新运行散布规则,所有资源会自动重新分布到合适的新位置。

环境光照系统是Gaia的后期处理与氛围营造模块。它提供了一个统一界面来配置场景的环境光照、雾效、天空盒和后期处理效果。与其他子系统一样,光照系统也支持规则驱动:可以设置不同时间段(清晨、正午、黄昏、夜晚)的光照预设,并根据游戏内时间自动切换;可以根据海拔高度调整雾浓度,创造大气透视效果;还可以将天气系统与光照绑定,实现雨天昏暗、晴天明亮的动态变化。

Gaia的各个子系统通过一个统一的"遮罩框架"相互连接。遮罩是一种灰度图,表示某种属性在地形上的分布强度:高度遮罩、坡度遮罩、朝向遮罩、纹理遮罩等。任何子系统都可以生成遮罩供其他系统使用,也可以使用其他系统生成的遮罩作为输入条件。这种数据流通机制创造了强大的工作流:地形生成系统创建的高度图会自动生成高度遮罩,纹理系统可以读取这个遮罩来决定不同海拔的纹理分布,植被系统则可以同时读取高度遮罩和纹理遮罩来决定什么植物长在哪里。整个系统形成一个智能的数据流网络,任何一处的修改都会通过这个网络自动传播,保持整个场景的一致性。

2.7.3 全流程生态化地形创建实践

下面通过一个完整的实践项目,演示如何使用Gaia从零开始创建一个具有生态多样性的山地森林场景,并展示如何通过脚本API实现场景的动态加载和运行时修改。该项目基于Unity 2021.3.8f1c1开发。

第一阶段:项目初始化与地形基础构建

首先在Unity中导入Gaia插件,然后通过Gaia管理器进行初始设置。我们将创建一个2048x2048单位大小的地形,分辨率为513x513。

using UnityEngine;
using Gaia;
using System.Collections;

public class MountainForestSceneBuilder : MonoBehaviour
{
    // Gaia管理器引用
    private GaiaSettings gaiaSettings;
    
    // 地形尺寸参数
    public int terrainWidth = 2048;
    public int terrainHeight = 2048;
    public int terrainResolution = 513;
    
    // 生物群系参数
    public float baseHeight = 50f;
    public float mountainHeight = 400f;
    public float valleyDepth = 20f;
    
    void Start()
    {
        InitializeGaia();
        CreateBaseTerrain();
        GenerateMountainRange();
        ApplyErosion();
    }
    
    /// <summary>
    /// 初始化Gaia系统
    /// </summary>
    private void InitializeGaia()
    {
        // 获取或创建Gaia设置
        gaiaSettings = GaiaSettings.GetOrCreateSettings();
        
        // 配置基础设置
        gaiaSettings.m_currentDefaults.m_terrainSize = new Vector3(terrainWidth, 600, terrainHeight);
        gaiaSettings.m_currentDefaults.m_terrainHeight = 600;
        gaiaSettings.m_currentDefaults.m_baseTerrainMaterial = GaiaConstants.EnvironmentRenderer.BuiltIn;
        
        // 设置资源路径
        gaiaSettings.m_currentResourcesPath = GaiaDirectories.GetSettingsDirectory() + "/Resources";
        
        Debug.Log("Gaia系统初始化完成");
    }
    
    /// <summary>
    /// 创建基础地形
    /// </summary>
    private void CreateBaseTerrain()
    {
        // 通过Gaia API创建地形
        Gaia.TerrainUtilities.CreateTerrain(gaiaSettings.m_currentDefaults);
        
        // 获取创建的地形
        Terrain[] terrains = Terrain.activeTerrains;
        if (terrains.Length == 0)
        {
            Debug.LogError("地形创建失败");
            return;
        }
        
        // 设置基础高度
        SetBaseTerrainHeight(terrains[0], baseHeight);
        
        Debug.Log($"基础地形创建完成,尺寸:{terrainWidth}x{terrainHeight}");
    }
    
    /// <summary>
    /// 设置地形基础高度
    /// </summary>
    private void SetBaseTerrainHeight(Terrain terrain, float height)
    {
        TerrainData terrainData = terrain.terrainData;
        float[,] heights = terrainData.GetHeights(0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution);
        
        float normalizedHeight = height / terrainData.size.y;
        
        // 设置整个地形为统一基础高度
        for (int x = 0; x < terrainData.heightmapResolution; x++)
        {
            for (int y = 0; y < terrainData.heightmapResolution; y++)
            {
                heights[x, y] = normalizedHeight;
            }
        }
        
        terrainData.SetHeights(0, 0, heights);
    }
    
    /// <summary>
    /// 生成主要山脉
    /// </summary>
    private void GenerateMountainRange()
    {
        // 使用Gaia的图章系统生成山脉
        // 这里演示通过脚本调用图章生成器
        StartCoroutine(ApplyMountainStamps());
    }
    
    private IEnumerator ApplyMountainStamps()
    {
        Debug.Log("开始生成山脉...");
        
        // 获取地形数据
        Terrain terrain = Terrain.activeTerrain;
        if (terrain == null)
        {
            Debug.LogError("未找到活动地形");
            yield break;
        }
        
        // 模拟应用多个图章
        // 在实际使用中,这里会调用Gaia的图章API
        // 此处为简化演示,直接操作高度图
        
        TerrainData terrainData = terrain.terrainData;
        int resolution = terrainData.heightmapResolution;
        float[,] heights = terrainData.GetHeights(0, 0, resolution, resolution);
        
        // 生成第一条山脉(主山脉)
        Vector2 range1Center = new Vector2(0.3f, 0.5f); // 归一化坐标
        float range1Radius = 0.2f;
        float range1Height = mountainHeight / terrainData.size.y;
        
        // 生成第二条山脉(支脉)
        Vector2 range2Center = new Vector2(0.7f, 0.6f);
        float range2Radius = 0.15f;
        float range2Height = mountainHeight * 0.7f / terrainData.size.y;
        
        // 生成山谷
        Vector2 valleyCenter = new Vector2(0.5f, 0.3f);
        float valleyRadius = 0.15f;
        float valleyHeight = valleyDepth / terrainData.size.y;
        
        // 应用地形特征
        for (int x = 0; x < resolution; x++)
        {
            for (int y = 0; y < resolution; y++)
            {
                // 计算归一化位置
                Vector2 pos = new Vector2((float)x / resolution, (float)y / resolution);
                
                // 第一条山脉
                float distToRange1 = Vector2.Distance(pos, range1Center);
                if (distToRange1 < range1Radius)
                {
                    float falloff = 1 - (distToRange1 / range1Radius);
                    float mountainShape = Mathf.PerlinNoise(x * 0.01f, y * 0.01f) * 0.3f + 0.7f;
                    heights[x, y] += range1Height * falloff * mountainShape;
                }
                
                // 第二条山脉
                float distToRange2 = Vector2.Distance(pos, range2Center);
                if (distToRange2 < range2Radius)
                {
                    float falloff = 1 - (distToRange2 / range2Radius);
                    float mountainShape = Mathf.PerlinNoise(x * 0.015f + 100, y * 0.015f + 100) * 0.4f + 0.6f;
                    heights[x, y] += range2Height * falloff * mountainShape;
                }
                
                // 山谷
                float distToValley = Vector2.Distance(pos, valleyCenter);
                if (distToValley < valleyRadius)
                {
                    float falloff = 1 - (distToValley / valleyRadius);
                    heights[x, y] -= valleyHeight * falloff;
                }
            }
            
            // 每处理10行 yield一次,避免卡顿
            if (x % 10 == 0)
            {
                yield return null;
            }
        }
        
        terrainData.SetHeights(0, 0, heights);
        terrain.Flush();
        
        Debug.Log("山脉生成完成");
    }
    
    /// <summary>
    /// 应用侵蚀效果
    /// </summary>
    private void ApplyErosion()
    {
        StartCoroutine(SimulateErosion());
    }
    
    private IEnumerator SimulateErosion()
    {
        Debug.Log("开始模拟侵蚀效果...");
        
        Terrain terrain = Terrain.activeTerrain;
        TerrainData terrainData = terrain.terrainData;
        int resolution = terrainData.heightmapResolution;
        float[,] heights = terrainData.GetHeights(0, 0, resolution, resolution);
        
        // 简化的热侵蚀模拟
        int iterations = 20;
        float erosionStrength = 0.05f;
        
        for (int iter = 0; iter < iterations; iter++)
        {
            float[,] newHeights = (float[,])heights.Clone();
            
            for (int x = 1; x < resolution - 1; x++)
            {
                for (int y = 1; y < resolution - 1; y++)
                {
                    // 计算高度差(模拟物质流动)
                    float currentHeight = heights[x, y];
                    
                    // 检查四个相邻点
                    float[] neighborHeights = new float[]
                    {
                        heights[x+1, y],
                        heights[x-1, y],
                        heights[x, y+1],
                        heights[x, y-1]
                    };
                    
                    // 找到最低的邻居
                    float minNeighborHeight = Mathf.Min(neighborHeights);
                    
                    // 如果当前点高于邻居,物质向低处流动
                    if (currentHeight > minNeighborHeight)
                    {
                        float flowAmount = (currentHeight - minNeighborHeight) * erosionStrength;
                        newHeights[x, y] -= flowAmount;
                        
                        // 简化:将物质均匀分配到所有更低邻居
                        for (int n = 0; n < 4; n++)
                        {
                            if (neighborHeights[n] == minNeighborHeight)
                            {
                                if (n == 0) newHeights[x+1, y] += flowAmount / 4;
                                else if (n == 1) newHeights[x-1, y] += flowAmount / 4;
                                else if (n == 2) newHeights[x, y+1] += flowAmount / 4;
                                else if (n == 3) newHeights[x, y-1] += flowAmount / 4;
                            }
                        }
                    }
                }
                
                if (x % 20 == 0)
                {
                    yield return null;
                }
            }
            
            heights = newHeights;
            
            // 更新显示进度
            if (iter % 5 == 0)
            {
                Debug.Log($"侵蚀模拟进度:{iter + 1}/{iterations}");
                terrainData.SetHeights(0, 0, heights);
                terrain.Flush();
            }
        }
        
        // 最终更新
        terrainData.SetHeights(0, 0, heights);
        terrain.Flush();
        
        Debug.Log("侵蚀模拟完成");
    }
}

第二阶段:智能纹理系统配置

地形基础构建完成后,接下来配置智能纹理系统。我们将创建三个纹理层:岩石层、土壤层和草地层,并根据地形属性自动分布。

using UnityEngine;
using System.Collections.Generic;

public class TerrainTextureSystem : MonoBehaviour
{
    public Terrain terrain;
    
    // 纹理材质
    public Texture2D rockTexture;
    public Texture2D soilTexture;
    public Texture2D grassTexture;
    
    // 纹理平铺设置
    public float rockTileSize = 30f;
    public float soilTileSize = 20f;
    public float grassTileSize = 10f;
    
    // 分布规则参数
    public float rockStartHeight = 0.6f; // 归一化高度
    public float grassStartHeight = 0.2f;
    public float steepSlopeThreshold = 45f; // 度
    
    void Start()
    {
        if (terrain == null)
        {
            terrain = Terrain.activeTerrain;
        }
        
        SetupTerrainTextures();
        ApplyTextureRules();
    }
    
    /// <summary>
    /// 设置地形纹理层
    /// </summary>
    private void SetupTerrainTextures()
    {
        if (terrain == null || terrain.terrainData == null)
        {
            Debug.LogError("地形或地形数据未找到");
            return;
        }
        
        TerrainData terrainData = terrain.terrainData;
        
        // 获取现有的地形层
        List<TerrainLayer> terrainLayers = new List<TerrainLayer>(terrainData.terrainLayers);
        
        // 清除现有层(可选)
        terrainLayers.Clear();
        
        // 创建岩石层
        TerrainLayer rockLayer = new TerrainLayer();
        rockLayer.diffuseTexture = rockTexture;
        rockLayer.tileSize = new Vector2(rockTileSize, rockTileSize);
        rockLayer.tileOffset = Vector2.zero;
        rockLayer.name = "Rock";
        terrainLayers.Add(rockLayer);
        
        // 创建土壤层
        TerrainLayer soilLayer = new TerrainLayer();
        soilLayer.diffuseTexture = soilTexture;
        soilLayer.tileSize = new Vector2(soilTileSize, soilTileSize);
        soilLayer.tileOffset = Vector2.zero;
        soilLayer.name = "Soil";
        terrainLayers.Add(soilLayer);
        
        // 创建草地层
        TerrainLayer grassLayer = new TerrainLayer();
        grassLayer.diffuseTexture = grassTexture;
        grassLayer.tileSize = new Vector2(grassTileSize, grassTileSize);
        grassLayer.tileOffset = Vector2.zero;
        grassLayer.name = "Grass";
        terrainLayers.Add(grassLayer);
        
        // 应用层设置
        terrainData.terrainLayers = terrainLayers.ToArray();
        
        // 初始化AlphaMap
        int alphaMapResolution = terrainData.alphamapResolution;
        float[,,] alphaMaps = new float[alphaMapResolution, alphaMapResolution, terrainLayers.Count];
        terrainData.SetAlphamaps(0, 0, alphaMaps);
        
        Debug.Log($"纹理层设置完成,共{terrainLayers.Count}层");
    }
    
    /// <summary>
    /// 应用纹理分布规则
    /// </summary>
    private void ApplyTextureRules()
    {
        StartCoroutine(GenerateTextureDistribution());
    }
    
    private IEnumerator GenerateTextureDistribution()
    {
        Debug.Log("开始生成纹理分布...");
        
        TerrainData terrainData = terrain.terrainData;
        int resolution = terrainData.alphamapResolution;
        int layerCount = terrainData.terrainLayers.Length;
        
        float[,,] alphaMaps = terrainData.GetAlphamaps(0, 0, resolution, resolution);
        
        for (int x = 0; x < resolution; x++)
        {
            for (int y = 0; y < resolution; y++)
            {
                // 计算归一化地形坐标
                float normX = (float)x / (resolution - 1);
                float normY = (float)y / (resolution - 1);
                
                // 获取当前位置的高度和坡度
                float height = terrainData.GetHeight(Mathf.RoundToInt(normY * terrainData.heightmapResolution),
                                                    Mathf.RoundToInt(normX * terrainData.heightmapResolution));
                float normalizedHeight = height / terrainData.size.y;
                
                Vector3 normal = terrainData.GetInterpolatedNormal(normX, normY);
                float slope = Vector3.Angle(normal, Vector3.up);
                float normalizedSlope = slope / 90f; // 归一化到0-1范围
                
                // 初始化各层权重
                float rockWeight = 0f;
                float soilWeight = 0f;
                float grassWeight = 0f;
                
                // 规则1:高海拔或陡坡处使用岩石
                if (normalizedHeight > rockStartHeight || slope > steepSlopeThreshold)
                {
                    float heightFactor = Mathf.Clamp01((normalizedHeight - rockStartHeight) / (1 - rockStartHeight));
                    float slopeFactor = Mathf.Clamp01(slope / steepSlopeThreshold);
                    rockWeight = Mathf.Max(heightFactor, slopeFactor);
                }
                
                // 规则2:中等海拔缓坡使用土壤
                if (normalizedHeight > grassStartHeight && normalizedHeight < rockStartHeight && slope < steepSlopeThreshold)
                {
                    float heightFactor = 1 - Mathf.Abs(normalizedHeight - (grassStartHeight + rockStartHeight) / 2) / ((rockStartHeight - grassStartHeight) / 2);
                    float slopeFactor = 1 - normalizedSlope;
                    soilWeight = heightFactor * slopeFactor;
                }
                
                // 规则3:低海拔缓坡使用草地
                if (normalizedHeight <= grassStartHeight && slope < steepSlopeThreshold * 0.7f)
                {
                    float heightFactor = 1 - normalizedHeight / grassStartHeight;
                    float slopeFactor = 1 - normalizedSlope / 0.7f;
                    grassWeight = heightFactor * slopeFactor;
                }
                
                // 规则4:确保权重总和为1
                float totalWeight = rockWeight + soilWeight + grassWeight;
                if (totalWeight > 0)
                {
                    rockWeight /= totalWeight;
                    soilWeight /= totalWeight;
                    grassWeight /= totalWeight;
                }
                else
                {
                    // 默认使用草地
                    grassWeight = 1f;
                }
                
                // 应用权重
                alphaMaps[x, y, 0] = rockWeight;    // 岩石层
                alphaMaps[x, y, 1] = soilWeight;    // 土壤层
                alphaMaps[x, y, 2] = grassWeight;   // 草地层
            }
            
            // 每处理10行 yield一次
            if (x % 10 == 0)
            {
                yield return null;
            }
        }
        
        // 应用AlphaMap
        terrainData.SetAlphamaps(0, 0, alphaMaps);
        
        // 平滑过渡
        SmoothTextureTransitions(3);
        
        Debug.Log("纹理分布生成完成");
    }
    
    /// <summary>
    /// 平滑纹理过渡
    /// </summary>
    private void SmoothTextureTransitions(int smoothingPasses)
    {
        TerrainData terrainData = terrain.terrainData;
        int resolution = terrainData.alphamapResolution;
        int layerCount = terrainData.terrainLayers.Length;
        
        for (int pass = 0; pass < smoothingPasses; pass++)
        {
            float[,,] alphaMaps = terrainData.GetAlphamaps(0, 0, resolution, resolution);
            float[,,] smoothedMaps = (float[,,])alphaMaps.Clone();
            
            for (int x = 1; x < resolution - 1; x++)
            {
                for (int y = 1; y < resolution - 1; y++)
                {
                    for (int layer = 0; layer < layerCount; layer++)
                    {
                        // 3x3平均滤波
                        float sum = 0f;
                        for (int dx = -1; dx <= 1; dx++)
                        {
                            for (int dy = -1; dy <= 1; dy++)
                            {
                                sum += alphaMaps[x + dx, y + dy, layer];
                            }
                        }
                        smoothedMaps[x, y, layer] = sum / 9f;
                    }
                }
            }
            
            terrainData.SetAlphamaps(0, 0, smoothedMaps);
        }
    }
}

第三阶段:生态化植被散布系统实现

最后,实现基于生态规则的植被散布系统,模拟森林群落的自然分布。

using UnityEngine;
using System.Collections.Generic;

public class EcologicalVegetationSystem : MonoBehaviour
{
    public Terrain terrain;
    
    // 植被预制体
    public GameObject[] treePrefabs;
    public GameObject[] rockPrefabs;
    public GameObject[] bushPrefabs;
    public GameObject[] grassPrefabs;
    
    // 散布参数
    public int maxTrees = 5000;
    public int maxRocks = 1000;
    public int maxBushes = 3000;
    public int maxGrassPatches = 10000;
    
    // 生态规则参数
    public float treeMinSlope = 0f;
    public float treeMaxSlope = 40f;
    public float treeMinHeight = 0.1f;
    public float treeMaxHeight = 0.7f;
    public float treeSunPreference = 0.7f; // 0-1,越大越喜阳
    
    public float rockMinSlope = 20f;
    public float rockMaxSlope = 70f;
    
    public float bushMaxSlope = 30f;
    public float bushMoisturePreference = 0.6f;
    
    // 竞争参数
    public float minTreeDistance = 3f;
    public float minRockDistance = 1f;
    
    // 生成的结果存储
    private List<GameObject> spawnedVegetation = new List<GameObject>();
    
    void Start()
    {
        if (terrain == null)
        {
            terrain = Terrain.activeTerrain;
        }
        
        ClearExistingVegetation();
        GenerateEcosystem();
    }
    
    /// <summary>
    /// 清除现有植被
    /// </summary>
    public void ClearExistingVegetation()
    {
        foreach (GameObject obj in spawnedVegetation)
        {
            if (obj != null)
            {
                Destroy(obj);
            }
        }
        spawnedVegetation.Clear();
        
        Debug.Log("现有植被已清除");
    }
    
    /// <summary>
    /// 生成完整生态系统
    /// </summary>
    public void GenerateEcosystem()
    {
        StartCoroutine(GenerateEcosystemCoroutine());
    }
    
    private IEnumerator GenerateEcosystemCoroutine()
    {
        Debug.Log("开始生成生态系统...");
        
        // 分步骤生成不同植被类型
        yield return StartCoroutine(SpawnTrees());
        yield return StartCoroutine(SpawnRocks());
        yield return StartCoroutine(SpawnBushes());
        yield return StartCoroutine(SpawnGrass());
        
        Debug.Log($"生态系统生成完成,共生成{spawnedVegetation.Count}个植被对象");
    }
    
    /// <summary>
    /// 生成树木(主要植被)
    /// </summary>
    private IEnumerator SpawnTrees()
    {
        Debug.Log("开始生成树木...");
        
        TerrainData terrainData = terrain.terrainData;
        Vector3 terrainSize = terrainData.size;
        Vector3 terrainPos = terrain.transform.position;
        
        List<Vector3> treePositions = new List<Vector3>();
        int attempts = 0;
        int maxAttempts = maxTrees * 10; // 防止无限循环
        
        while (treePositions.Count < maxTrees && attempts < maxAttempts)
        {
            attempts++;
            
            // 生成随机位置
            float x = Random.Range(0f, 1f);
            float z = Random.Range(0f, 1f);
            
            // 计算世界坐标
            Vector3 worldPos = new Vector3(
                terrainPos.x + x * terrainSize.x,
                0,
                terrainPos.z + z * terrainSize.z
            );
            
            // 获取地形高度
            worldPos.y = terrain.SampleHeight(worldPos) + terrainPos.y;
            
            // 计算归一化高度
            float normalizedHeight = worldPos.y / (terrainPos.y + terrainSize.y);
            
            // 获取坡度
            Vector3 normal = terrainData.GetInterpolatedNormal(x, z);
            float slope = Vector3.Angle(normal, Vector3.up);
            
            // 获取朝向(用于计算日照)
            float sunExposure = CalculateSunExposure(normal);
            
            // 应用生态规则
            bool suitableForTrees = 
                slope >= treeMinSlope && slope <= treeMaxSlope &&
                normalizedHeight >= treeMinHeight && normalizedHeight <= treeMaxHeight &&
                sunExposure >= treeSunPreference;
            
            if (!suitableForTrees)
            {
                continue;
            }
            
            // 检查与其他树木的距离
            bool tooClose = false;
            foreach (Vector3 existingPos in treePositions)
            {
                if (Vector3.Distance(worldPos, existingPos) < minTreeDistance)
                {
                    tooClose = true;
                    break;
                }
            }
            
            if (tooClose)
            {
                continue;
            }
            
            // 通过所有检查,生成树木
            GameObject treePrefab = treePrefabs[Random.Range(0, treePrefabs.Length)];
            GameObject tree = Instantiate(treePrefab, worldPos, Quaternion.identity, terrain.transform);
            
            // 随机旋转
            tree.transform.Rotate(0, Random.Range(0, 360), 0);
            
            // 随机缩放(80%-120%)
            float scale = Random.Range(0.8f, 1.2f);
            tree.transform.localScale = new Vector3(scale, scale, scale);
            
            // 调整高度到地面
            RaycastHit hit;
            if (Physics.Raycast(tree.transform.position + Vector3.up * 10, Vector3.down, out hit, 20))
            {
                tree.transform.position = hit.point;
            }
            
            treePositions.Add(worldPos);
            spawnedVegetation.Add(tree);
            
            // 每生成10棵树 yield一次
            if (treePositions.Count % 10 == 0)
            {
                yield return null;
            }
        }
        
        Debug.Log($"树木生成完成,共{treePositions.Count}棵");
    }
    
    /// <summary>
    /// 生成岩石
    /// </summary>
    private IEnumerator SpawnRocks()
    {
        Debug.Log("开始生成岩石...");
        
        TerrainData terrainData = terrain.terrainData;
        Vector3 terrainSize = terrainData.size;
        Vector3 terrainPos = terrain.transform.position;
        
        List<Vector3> rockPositions = new List<Vector3>();
        int attempts = 0;
        
        while (rockPositions.Count < maxRocks && attempts < maxRocks * 10)
        {
            attempts++;
            
            float x = Random.Range(0f, 1f);
            float z = Random.Range(0f, 1f);
            
            Vector3 worldPos = new Vector3(
                terrainPos.x + x * terrainSize.x,
                0,
                terrainPos.z + z * terrainSize.z
            );
            
            worldPos.y = terrain.SampleHeight(worldPos) + terrainPos.y;
            
            // 获取坡度和高度
            Vector3 normal = terrainData.GetInterpolatedNormal(x, z);
            float slope = Vector3.Angle(normal, Vector3.up);
            float normalizedHeight = worldPos.y / (terrainPos.y + terrainSize.y);
            
            // 岩石生态规则:主要出现在陡坡和高海拔处
            bool suitableForRocks = 
                slope >= rockMinSlope && slope <= rockMaxSlope &&
                normalizedHeight > treeMinHeight;
            
            if (!suitableForRocks)
            {
                continue;
            }
            
            // 检查距离
            bool tooClose = false;
            foreach (Vector3 existingPos in rockPositions)
            {
                if (Vector3.Distance(worldPos, existingPos) < minRockDistance)
                {
                    tooClose = true;
                    break;
                }
            }
            
            if (tooClose)
            {
                continue;
            }
            
            // 生成岩石
            GameObject rockPrefab = rockPrefabs[Random.Range(0, rockPrefabs.Length)];
            GameObject rock = Instantiate(rockPrefab, worldPos, Quaternion.identity, terrain.transform);
            
            // 随机旋转和缩放
            rock.transform.Rotate(Random.Range(0, 360), Random.Range(0, 360), Random.Range(0, 360));
            float scale = Random.Range(0.5f, 1.5f);
            rock.transform.localScale = new Vector3(scale, scale, scale);
            
            // 贴合地面
            RaycastHit hit;
            if (Physics.Raycast(rock.transform.position + Vector3.up * 5, Vector3.down, out hit, 10))
            {
                rock.transform.position = hit.point;
                // 使岩石与地面法线对齐
                rock.transform.up = hit.normal;
            }
            
            rockPositions.Add(worldPos);
            spawnedVegetation.Add(rock);
            
            if (rockPositions.Count % 20 == 0)
            {
                yield return null;
            }
        }
        
        Debug.Log($"岩石生成完成,共{rockPositions.Count}块");
    }
    
    /// <summary>
    /// 生成灌木
    /// </summary>
    private IEnumerator SpawnBushes()
    {
        Debug.Log("开始生成灌木...");
        
        // 灌木生成逻辑与树木类似,但有不同的生态规则
        // 这里简化为在适合树木的区域生成,但密度更高
        yield return null;
    }
    
    /// <summary>
    /// 生成草地
    /// </summary>
    private IEnumerator SpawnGrass()
    {
        Debug.Log("开始生成草地...");
        
        // 使用Unity的地形细节系统生成草地
        // 这里可以调用Gaia的细节散布API
        yield return null;
    }
    
    /// <summary>
    /// 计算日照程度
    /// </summary>
    private float CalculateSunExposure(Vector3 normal)
    {
        // 假设太阳方向为正上方
        Vector3 sunDirection = Vector3.down;
        float exposure = Vector3.Dot(normal, -sunDirection);
        
        // 归一化到0-1范围
        exposure = (exposure + 1) / 2;
        
        return exposure;
    }
    
    /// <summary>
    /// 计算湿度(简化版:基于高度和坡度)
    /// </summary>
    private float CalculateMoisture(float normalizedHeight, float slope)
    {
        // 低处更湿润
        float heightFactor = 1 - normalizedHeight;
        
        // 缓坡更湿润
        float slopeFactor = 1 - Mathf.Clamp01(slope / 60f);
        
        // 河谷处更湿润(这里简化处理)
        float moisture = heightFactor * 0.7f + slopeFactor * 0.3f;
        
        return Mathf.Clamp01(moisture);
    }
    
    /// <summary>
    /// 动态更新植被(例如季节变化)
    /// </summary>
    public void UpdateVegetationForSeason(string season)
    {
        foreach (GameObject plant in spawnedVegetation)
        {
            if (plant == null)
            {
                continue;
            }
            
            // 根据季节调整植物状态
            // 这里可以调整颜色、大小、枯萎状态等
            Renderer renderer = plant.GetComponent<Renderer>();
            if (renderer != null)
            {
                Color seasonalColor = GetSeasonalColor(season);
                renderer.material.color = Color.Lerp(renderer.material.color, seasonalColor, 0.1f);
            }
        }
        
        Debug.Log($"植被已更新为{season}季节效果");
    }
    
    private Color GetSeasonalColor(string season)
    {
        switch (season.ToLower())
        {
            case "spring":
                return new Color(0.6f, 0.8f, 0.4f); // 嫩绿色
            case "summer":
                return new Color(0.4f, 0.6f, 0.2f); // 深绿色
            case "autumn":
                return new Color(0.8f, 0.6f, 0.2f); // 黄橙色
            case "winter":
                return new Color(0.7f, 0.7f, 0.7f); // 灰白色
            default:
                return Color.white;
        }
    }
}

第四阶段:性能优化与项目集成

在大型项目中,直接实例化大量植被对象会导致性能问题。Gaia提供了优化方案,包括使用Unity的细节渲染系统(Detail System)和树系统(Tree System),以及LOD(细节层次)管理。

using UnityEngine;
using System.Collections;

public class GaiaPerformanceOptimizer : MonoBehaviour
{
    public Terrain terrain;
    
    // LOD设置
    public int detailDistance = 200;      // 细节渲染距离
    public int treeDistance = 500;        // 树渲染距离
    public int maxMeshTrees = 50;         // 最大网格树数量
    
    // 分块加载设置
    public bool useChunkLoading = true;
    public float chunkSize = 500f;
    public Transform playerTransform;
    public float loadDistance = 1000f;
    public float unloadDistance = 1200f;
    
    private Dictionary<Vector2Int, GameObject> loadedChunks = new Dictionary<Vector2Int, GameObject>();
    
    void Start()
    {
        if (terrain == null)
        {
            terrain = Terrain.activeTerrain;
        }
        
        OptimizeTerrainRendering();
        
        if (useChunkLoading && playerTransform != null)
        {
            StartCoroutine(DynamicChunkLoading());
        }
    }
    
    /// <summary>
    /// 优化地形渲染设置
    /// </summary>
    private void OptimizeTerrainRendering()
    {
        if (terrain == null)
        {
            return;
        }
        
        // 设置渲染距离
        terrain.detailObjectDistance = detailDistance;
        terrain.treeDistance = treeDistance;
        terrain.treeBillboardDistance = treeDistance * 0.7f;
        
        // 设置最大网格树数量
        terrain.treeMaximumFullLODCount = maxMeshTrees;
        
        // 调整细节密度
        terrain.detailObjectDensity = 0.8f;
        
        // 设置阴影
        terrain.treeCrossFadeLength = 50f;
        terrain.treeMaximumFullLODCount = maxMeshTrees;
        
        Debug.Log("地形渲染优化完成");
    }
    
    /// <summary>
    /// 动态分块加载
    /// </summary>
    private IEnumerator DynamicChunkLoading()
    {
        Debug.Log("启动动态分块加载系统");
        
        while (true)
        {
            if (playerTransform == null)
            {
                yield return new WaitForSeconds(1f);
                continue;
            }
            
            // 计算玩家所在区块
            Vector3 playerPos = playerTransform.position;
            Vector2Int currentChunk = GetChunkCoordinate(playerPos);
            
            // 加载玩家周围的区块
            LoadChunksAround(currentChunk);
            
            // 卸载远处的区块
            UnloadDistantChunks(currentChunk);
            
            yield return new WaitForSeconds(0.5f); // 每0.5秒检查一次
        }
    }
    
    /// <summary>
    /// 获取位置对应的区块坐标
    /// </summary>
    private Vector2Int GetChunkCoordinate(Vector3 position)
    {
        int chunkX = Mathf.FloorToInt(position.x / chunkSize);
        int chunkZ = Mathf.FloorToInt(position.z / chunkSize);
        
        return new Vector2Int(chunkX, chunkZ);
    }
    
    /// <summary>
    /// 加载玩家周围的区块
    /// </summary>
    private void LoadChunksAround(Vector2Int centerChunk)
    {
        int loadRadius = Mathf.CeilToInt(loadDistance / chunkSize);
        
        for (int dx = -loadRadius; dx <= loadRadius; dx++)
        {
            for (int dz = -loadRadius; dz <= loadRadius; dz++)
            {
                Vector2Int chunkCoord = new Vector2Int(centerChunk.x + dx, centerChunk.y + dz);
                
                // 计算区块中心位置
                Vector3 chunkCenter = new Vector3(
                    chunkCoord.x * chunkSize + chunkSize / 2,
                    0,
                    chunkCoord.y * chunkSize + chunkSize / 2
                );
                
                // 检查距离
                float distance = Vector3.Distance(playerTransform.position, chunkCenter);
                if (distance <= loadDistance && !loadedChunks.ContainsKey(chunkCoord))
                {
                    LoadChunk(chunkCoord);
                }
            }
        }
    }
    
    /// <summary>
    /// 加载单个区块
    /// </summary>
    private void LoadChunk(Vector2Int chunkCoord)
    {
        // 创建区块容器
        GameObject chunk = new GameObject($"Chunk_{chunkCoord.x}_{chunkCoord.y}");
        chunk.transform.position = new Vector3(chunkCoord.x * chunkSize, 0, chunkCoord.y * chunkSize);
        chunk.transform.parent = transform;
        
        // 这里应该加载该区块的地形和植被
        // 实际项目中,这里会调用Gaia的API加载或生成该区块的内容
        
        loadedChunks.Add(chunkCoord, chunk);
        
        Debug.Log($"加载区块: {chunkCoord}");
    }
    
    /// <summary>
    /// 卸载远处的区块
    /// </summary>
    private void UnloadDistantChunks(Vector2Int centerChunk)
    {
        List<Vector2Int> chunksToUnload = new List<Vector2Int>();
        
        foreach (var kvp in loadedChunks)
        {
            Vector2Int chunkCoord = kvp.Key;
            GameObject chunk = kvp.Value;
            
            // 计算区块中心
            Vector3 chunkCenter = new Vector3(
                chunkCoord.x * chunkSize + chunkSize / 2,
                0,
                chunkCoord.y * chunkSize + chunkSize / 2
            );
            
            // 检查距离
            float distance = Vector3.Distance(playerTransform.position, chunkCenter);
            if (distance > unloadDistance)
            {
                chunksToUnload.Add(chunkCoord);
            }
        }
        
        // 卸载区块
        foreach (Vector2Int chunkCoord in chunksToUnload)
        {
            GameObject chunk = loadedChunks[chunkCoord];
            Destroy(chunk);
            loadedChunks.Remove(chunkCoord);
            
            Debug.Log($"卸载区块: {chunkCoord}");
        }
    }
    
    /// <summary>
    /// 根据平台调整画质设置
    /// </summary>
    public void AdjustQualityForPlatform(RuntimePlatform platform)
    {
        switch (platform)
        {
            case RuntimePlatform.Android:
            case RuntimePlatform.IPhonePlayer:
                // 移动端:降低设置
                detailDistance = 50;
                treeDistance = 100;
                maxMeshTrees = 10;
                terrain.detailObjectDensity = 0.3f;
                break;
                
            case RuntimePlatform.WindowsPlayer:
            case RuntimePlatform.OSXPlayer:
                // PC端:中等设置
                detailDistance = 150;
                treeDistance = 300;
                maxMeshTrees = 30;
                terrain.detailObjectDensity = 0.6f;
                break;
                
            case RuntimePlatform.PS4:
            case RuntimePlatform.XboxOne:
                // 主机端:较高设置
                detailDistance = 200;
                treeDistance = 400;
                maxMeshTrees = 50;
                terrain.detailObjectDensity = 0.8f;
                break;
                
            default:
                // 其他平台:默认设置
                detailDistance = 100;
                treeDistance = 200;
                maxMeshTrees = 20;
                terrain.detailObjectDensity = 0.5f;
                break;
        }
        
        OptimizeTerrainRendering();
        Debug.Log($"已根据平台{platform}调整画质设置");
    }
}

通过以上完整的实现流程,我们展示了如何使用Gaia插件和自定义脚本创建一个完整的生态化地形系统。从地形基础构建、智能纹理分布、生态化植被散布到性能优化,每个阶段都体现了程序化生成和规则驱动的工作流优势。

在实际项目应用中,Gaia的价值更加明显。对于需要大量不同环境的游戏,可以创建多个生物群系模板(如森林、沙漠、雪地、沼泽等),每个模板包含完整的地形、纹理和植被规则。通过组合这些模板,可以快速生成多样化的游戏世界。对于动态变化的场景,可以通过脚本实时调整生态参数,模拟季节变化、气候变化或玩家对环境的影响。

Gaia的另一个强大功能是它的扩展性。开发者可以创建自定义的图章、纹理生成器和资源散布规则,将项目特定的美术资源和游戏规则集成到Gaia的工作流中。通过Gaia提供的API,还可以将地形生成过程集成到自动化构建管线中,实现批量生成和测试。

性能方面,除了上述的优化策略,Gaia还支持地形分块、流式加载和LOD系统,可以处理超大规模的地形场景。通过合理的细节管理和渲染优化,即使在移动设备上也能运行复杂的地形场景。

总之,Gaia为Unity开发者提供了一个强大、灵活且高效的全流程地形解决方案。它不仅是一个工具集,更是一种工作流哲学,引导开发者以生态系统的视角思考和构建虚拟环境。通过掌握Gaia的核心概念和编程接口,开发者能够将地形创作从繁琐的手工劳动转变为创造性的设计过程,专注于构建更加丰富、生动和交互性强的虚拟世界。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐