/** * Update the LOD min/max distances for an object. * * @param isSpeedTree * If this is set to true, then we will check if we have a billboard renderer to an LOD * and we will skip it in case that we have one */ public static void UpdateDistancesLOD(FoliageTypeLODTree[] treeLods, LOD[] groupLods, float maxDistance, bool isSpeedTree) { if (groupLods != null && groupLods.Length > 0) { FoliageLog.Assert(groupLods.Length >= treeLods.Length, "Must have same or more lods than the tree lods!"); for (int i = 0; i < treeLods.Length; i++) { // If we are a speedtree check if we have a billboard renderer if (isSpeedTree && groupLods[i].renderers[0].GetComponent <BillboardRenderer>() != null) { continue; } FoliageTypeLODTree lodTree = treeLods[i]; LOD lodGroupCurrent = groupLods[i]; lodTree.m_EndDistance = ((1.0f - lodGroupCurrent.screenRelativeTransitionHeight) * maxDistance); } } else { treeLods[0].m_EndDistance = maxDistance; } }
private static void ExtractFromRootObject(FoliagePainterEditTime painter, GameObject root, bool disable, bool delete) { FoliageLog.Assert(root.transform.parent == null, "Must have root object!"); List <GameObject> foliageInstances = new List <GameObject>(); RecursivelyExtract(root, foliageInstances); int extracted = 0; // Process them and add them for (int i = foliageInstances.Count - 1; i >= 0; i--) { GameObject proto = foliageInstances[i]; GameObject protoPrefab = PrefabUtility.GetPrefabParent(proto) as GameObject; if (painter.HasFoliageType(protoPrefab) == false) { continue; } int hash = painter.GetFoliageTypeHash(protoPrefab); FoliageInstance instance = new FoliageInstance(); // Populate the data // Get the world data Vector3 worldPosition = proto.transform.position; Vector3 worldScale = proto.transform.localScale; Quaternion worldRotation = proto.transform.rotation; instance.m_Position = worldPosition; instance.m_Scale = worldScale; instance.m_Rotation = worldRotation; // Add the foliage instance painter.AddFoliageInstance(hash, instance, root.name); extracted++; // Auto disable if (disable) { proto.SetActive(false); } // Auto delete if (delete) { GameObject.DestroyImmediate(proto); } } FoliageLog.i("Extracted objects: " + extracted + " from: " + root.name); }
public static float GetMaxDistance(EFoliageType type) { switch (type) { case EFoliageType.OTHER_GRASS: case EFoliageType.SPEEDTREE_GRASS: return(FOLIAGE_MAX_GRASS_DISTANCE); case EFoliageType.OTHER_TREE: case EFoliageType.SPEEDTREE_TREE: case EFoliageType.SPEEDTREE_TREE_BILLBOARD: return(FOLIAGE_MAX_TREE_DISTANCE); default: FoliageLog.Assert(false, "Wrong type!"); return(FOLIAGE_MAX_GRASS_DISTANCE); } }
public static float ClampDistance(EFoliageType type, float maxViewDistance) { switch (type) { case EFoliageType.OTHER_GRASS: case EFoliageType.SPEEDTREE_GRASS: return(Mathf.Clamp(maxViewDistance, 0, FOLIAGE_MAX_GRASS_DISTANCE)); case EFoliageType.OTHER_TREE: case EFoliageType.SPEEDTREE_TREE: case EFoliageType.SPEEDTREE_TREE_BILLBOARD: return(Mathf.Clamp(maxViewDistance, 0, FOLIAGE_MAX_TREE_DISTANCE)); default: FoliageLog.Assert(false, "Wrong type!"); return(Mathf.Clamp(maxViewDistance, 0, FOLIAGE_MAX_GRASS_DISTANCE)); } }
/** To be used at runtime. Will create all we need for SpeedTree wind and other data */ public static void BuildDataRuntime(FoliagePainter painter, FoliageType type, Transform attachmentPoint) { FoliageLog.Assert(type.IsRuntimeInitialized == false, "Runtime data already initialized!"); // Everyone has MPB's type.m_RuntimeData.m_TypeMPB = new MaterialPropertyBlock(); if (type.IsSpeedTreeType) { // Create the glued mesh var speedData = type.m_RuntimeData.m_SpeedTreeData; FoliageLog.Assert(speedData != null, "Speed data must already be partly generated if we have a SpeedTree!"); // Get the lod and instnatiade the one with the least instructions LOD[] lods = type.m_Prefab.GetComponent <LODGroup>().GetLODs(); for (int i = lods.Length - 1; i >= 0; i--) { if (lods[i].renderers[0].GetComponent <BillboardRenderer>() != null) { continue; } else { // Init the object with the lowest possible LOD speedData.m_SpeedTreeWindObject = GameObject.Instantiate(lods[i].renderers[0].gameObject, attachmentPoint); break; } } // Set the data speedData.m_SpeedTreeWindObjectMesh = speedData.m_SpeedTreeWindObject.GetComponentInChildren <MeshRenderer>(); // Set the NULL invisible shader Shader nullShader = painter.GetShaderNull(); FoliageLog.Assert(nullShader, "Null shader not found! Make sure it exists and that it compiled!"); // Set the invisible null shader, we only need the wind Material[] mats = speedData.m_SpeedTreeWindObjectMesh.materials; for (int i = 0; i < mats.Length; i++) { mats[i].shader = nullShader; } // Attach the wind object. Ensures that we are enabled. speedData.m_SpeedTreeWindObject.AddComponent <FoliageWindTreeWind>(); speedData.m_SpeedTreeWindObjectMesh.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; speedData.m_SpeedTreeWindObject.transform.SetParent(attachmentPoint, false); speedData.m_SpeedTreeWindObject.transform.localPosition = new Vector3(0, 0, 0); MeshFilter m = speedData.m_SpeedTreeWindObject.GetComponentInChildren <MeshFilter>(); Bounds b = m.mesh.bounds; b.Expand(4.5f); m.mesh.bounds = b; speedData.m_SpeedTreeWindObject.GetComponentInChildren <MeshFilter>().mesh = m.mesh; } // Not used here, since at edit time we already created the materials /* * if (type.Type == EFoliageType.SPEEDTREE_GRASS) * { * Shader shader = painter.GetShaderGrass(); * FoliageLog.Assert(shader, "Could not find shader: Critias/SpeedTree_Grass! Make sure that it is added to the project and that it compiled!"); * * FoliageTypeLODGrass lodGrass = type.m_RuntimeData.m_LODDataGrass; * * // Override the material at runtime * // lodGrass.m_Material = new Material(lodGrass.m_Material); * // lodGrass.m_Material.shader = shader; * * // Enable it for instancing * // lodGrass.m_Material.enableInstancing = true; * } * else if(type.Type == EFoliageType.SPEEDTREE_TREE || type.Type == EFoliageType.SPEEDTREE_TREE_BILLBOARD) * { * Shader shader = painter.GetShaderTreeMaster(); * FoliageLog.Assert(shader, "Could not find shader: Critias/SpeedTree_Master! Make sure that it is added to the project and that it compiled!"); * * FoliageTypeLODTree[] lodTree = type.m_RuntimeData.m_LODDataTree; * * for(int i = 0; i < lodTree.Length; i++) * { * FoliageTypeLODTree tree = lodTree[i]; * * Material[] mats = tree.m_Materials; * * for(int m = 0; m < mats.Length; m++) * { * // Set the new material * //mats[m] = new Material(mats[m]); * //mats[m].shader = shader; * * // Enable instancing * //mats[m].enableInstancing = true; * } * * tree.m_Materials = mats; * } * } */ // We did initialize type.IsRuntimeInitialized = true; }
/** To be used at edit-time */ public static void BuildDataPartialEditTime(FoliagePainter painter, FoliageType type) { GameObject prefab = type.m_Prefab; // Update the type type.Type = type.Type; FoliageLog.Assert(prefab != null, "Null foliage prefab!"); if (type.m_RuntimeData == null) { type.m_RuntimeData = new FoliageTypeRuntimeData(); } FoliageTypeRuntimeData runtime = type.m_RuntimeData; List <Material> checkMaterials = new List <Material>(); // Init the SpeedTree data if (type.IsSpeedTreeType) { if (runtime.m_SpeedTreeData == null) { runtime.m_SpeedTreeData = new FoliageTypeSpeedTreeData(); } } if (type.IsGrassType) { // Build the data universally for all grass types if (runtime.m_LODDataGrass == null) { runtime.m_LODDataGrass = new FoliageTypeLODGrass(); } runtime.m_LODDataGrass.m_Mesh = prefab.GetComponentInChildren <MeshFilter>().sharedMesh; runtime.m_LODDataGrass.m_Material = prefab.GetComponentInChildren <MeshRenderer>().sharedMaterial; checkMaterials.Add(runtime.m_LODDataGrass.m_Material); FoliageLog.Assert(runtime.m_LODDataGrass.m_Mesh != null, "Could not find mesh for type: " + prefab.name + ". Make sure that is has at least one mesh and one material"); FoliageLog.Assert(runtime.m_LODDataGrass.m_Material != null, "Could not find material for type: " + prefab.name + ". Make sure that is has at least one mesh and one material"); } else { LODGroup group = prefab.GetComponent <LODGroup>(); if (group == null) { FoliageLog.w("Detected tree: " + prefab.name + " without a lod group. Are you sure that you require a tree for this?"); if (runtime.m_LODDataTree == null || runtime.m_LODDataTree.Length == 0) { runtime.m_LODDataTree = new FoliageTypeLODTree[1]; } runtime.m_LODDataTree[0] = new FoliageTypeLODTree(); runtime.m_LODDataTree[0].m_Mesh = prefab.GetComponentInChildren <MeshFilter>().sharedMesh; runtime.m_LODDataTree[0].m_Materials = prefab.GetComponentInChildren <MeshRenderer>().sharedMaterials; runtime.m_LODDataTree[0].m_EndDistance = type.m_RenderInfo.m_MaxDistance; checkMaterials.AddRange(runtime.m_LODDataTree[0].m_Materials); } else { List <FoliageTypeLODTree> treeLods = new List <FoliageTypeLODTree>(group.lodCount); LOD[] lods = group.GetLODs(); for (int i = 0; i < group.lodCount; i++) { if (lods[i].renderers[0].gameObject.GetComponent <BillboardRenderer>() != null) { // Extract the billboard data var speedData = runtime.m_SpeedTreeData; FoliageWindTreeUtilities.ExtractBillboardData(lods[i].renderers[0].gameObject.GetComponent <BillboardRenderer>(), speedData); continue; } FoliageTypeLODTree treeLod = new FoliageTypeLODTree(); MeshRenderer rend = lods[i].renderers[0].gameObject.GetComponent <MeshRenderer>(); MeshFilter filter = lods[i].renderers[0].gameObject.GetComponent <MeshFilter>(); treeLod.m_Mesh = filter.sharedMesh; treeLod.m_Materials = rend.sharedMaterials; checkMaterials.AddRange(rend.sharedMaterials); treeLods.Add(treeLod); } runtime.m_LODDataTree = treeLods.ToArray(); // Update the LOD distances UpdateDistancesLOD(runtime.m_LODDataTree, lods, type.m_RenderInfo.m_MaxDistance, type.IsSpeedTreeType); } } if (checkMaterials.Count > 0) { if (type.IsSpeedTreeType) { if (type.m_RenderInfo.m_Hue == new Color(0, 0, 0, 0)) { type.m_RenderInfo.m_Hue = checkMaterials[0].GetColor("_HueVariation"); } if (type.m_RenderInfo.m_Color == new Color(0, 0, 0, 0)) { type.m_RenderInfo.m_Color = checkMaterials[0].GetColor("_Color"); } } for (int i = 0; i < checkMaterials.Count; i++) { if (checkMaterials[i].enableInstancing == false) { checkMaterials[i].enableInstancing = true; FoliageLog.w("Material: [" + checkMaterials[i].name + "] did not had instancing enabled! We enabled it!"); } } } // Moved the build at partial edit-time if (type.Type == EFoliageType.SPEEDTREE_GRASS) { Shader shader = painter.GetShaderGrass(); FoliageLog.Assert(shader, "Could not find shader: Critias/SpeedTree_Grass! Make sure that it is added to the project and that it compiled!"); FoliageTypeLODGrass lodGrass = type.m_RuntimeData.m_LODDataGrass; // Override the material at runtime lodGrass.m_Material = new Material(lodGrass.m_Material); lodGrass.m_Material.shader = shader; // Enable it for instancing lodGrass.m_Material.enableInstancing = true; } else if (type.Type == EFoliageType.SPEEDTREE_TREE || type.Type == EFoliageType.SPEEDTREE_TREE_BILLBOARD) { Shader shader = painter.GetShaderTreeMaster(); FoliageLog.Assert(shader, "Could not find shader: Critias/SpeedTree_Master! Make sure that it is added to the project and that it compiled!"); FoliageTypeLODTree[] lodTree = type.m_RuntimeData.m_LODDataTree; for (int i = 0; i < lodTree.Length; i++) { FoliageTypeLODTree tree = lodTree[i]; Material[] mats = tree.m_Materials; for (int m = 0; m < mats.Length; m++) { // Set the new material mats[m] = new Material(mats[m]); mats[m].shader = shader; // Enable instancing mats[m].enableInstancing = true; } tree.m_Materials = mats; } } // Set the materials the values for enabling the bend stuff if we have it if (type.IsGrassType) { if (type.m_EnableBend) { type.m_RuntimeData.m_LODDataGrass.m_Material.EnableKeyword("CRITIAS_DISTANCE_BEND"); } else { type.m_RuntimeData.m_LODDataGrass.m_Material.DisableKeyword("CRITIAS_DISTANCE_BEND"); } } }
void Update() { #if UNITY_EDITOR FoliageLog.Assert(m_FoliageData != null); #endif // Camera used for culling m_CurrentFrameCameraCull = m_Settings.m_UsedCameraCulling; // Camera used for drawing m_CurrentFrameCameraDraw = m_Settings.m_UsedCameraDrawing; m_CurrentFrameLayer = LayerMask.NameToLayer(m_Settings.m_UsedLayer); // Extract the planes ExtractPlanes(m_CameraPlanes, m_CurrentFrameCameraCull.projectionMatrix * m_CurrentFrameCameraCull.worldToCameraMatrix); // Set the position m_CurrentFrameCameraPosition = m_CurrentFrameCameraCull.transform.position; // Set the bend position m_CurrentFrameBendPosition = m_Settings.m_BendTransform != null ? m_Settings.m_BendTransform.position : m_CurrentFrameCameraPosition; // Current cell position currentCell.Set(m_CurrentFrameCameraPosition); m_CurrentFrameAllowIndirect = m_Settings.m_AllowDrawInstancedIndirect; m_DrawStats.Reset(); // Copy the wind for SpeedTree types for (int i = 0; i < m_FoliageTypesArray.Length; i++) { if (m_FoliageTypesArray[i].IsSpeedTreeType) { m_FoliageTypesArray[i].CopyBlock(); } } bool applyShadowCorrection = m_Settings.m_ApplyShadowPoppingCorrection; float shadowCorrectionDistanceSqr = m_Settings.m_ShadowPoppingCorrection * m_Settings.m_ShadowPoppingCorrection; // We iterate only as many cells as we need FoliageCell.IterateNeighboring(currentCell, m_CellNeighborCount, (int hash) => { FoliageCellDataRuntime data; if (m_FoliageData.m_FoliageData.TryGetValue(hash, out data)) { // If it is within distance and in the frustum float distanceSqr = data.m_Bounds.SqrDistance(m_CurrentFrameCameraPosition); // Check for the maximum distance if (distanceSqr <= m_MaxDistanceAllSqr && GeometryUtility.TestPlanesAABB(m_CameraPlanes, data.m_Bounds)) { // Process the big cells if we are withing the tree distance if (distanceSqr <= m_MaxDistanceTreeSqr) { ProcessCellTree(data, distanceSqr, applyShadowCorrection, shadowCorrectionDistanceSqr, false); } // Process subdivided cells only if we have instancing enabled and only if it is within the distance proximity for grass if (distanceSqr <= m_MaxDistanceGrassSqr && m_Settings.m_DrawInstanced) { ProcessCellGrass(data); } m_DrawStats.m_ProcessedCells++; } else if (distanceSqr <= shadowCorrectionDistanceSqr) { ProcessCellTree(data, distanceSqr, applyShadowCorrection, shadowCorrectionDistanceSqr, true); } } }); #if UNITY_EDITOR if (Time.frameCount % 300 == 0) { FoliageLog.i(string.Format("Proc cells:{0} Proc subdiv cells: {1} Proc tree instances: {2} Proc draw calls: {3}", m_DrawStats.m_ProcessedCells, m_DrawStats.m_ProcessedCellsSubdiv, m_DrawStats.m_ProcessedInstances, m_DrawStats.m_ProcessedDrawCalls)); } #endif }
private static void ExtractDetailsFromTerrain(FoliagePainter painter, Terrain terrain, List <FoliageDetailExtracterMapping> mappings, bool disable, bool delete) { string label = FoliageGlobals.LABEL_TERRAIN_DETAILS_EXTRACTED + terrain.name; FoliagePainterEditTime edit = painter.GetEditTime; int detailMapSizeW = terrain.terrainData.detailWidth; int detailMapSizeH = terrain.terrainData.detailHeight; float patchSizeW = terrain.terrainData.size.x / detailMapSizeW; float patchSizeH = terrain.terrainData.size.z / detailMapSizeH; int extracted = 0; // Extract the types for all the mapping for (int m = 0; m < mappings.Count; m++) { int layer = mappings[m].m_DetailLayer; int mapping = mappings[m].m_FoliageTypeHash; float density = mappings[m].m_ExtractedDensity; FoliageLog.Assert(painter.HasFoliageType(mapping), "Must have foliage hash!!"); // Get the foliage type FoliageType type = painter.GetFoliageTypeByHash(mapping); DetailPrototype proto = terrain.terrainData.detailPrototypes[layer]; // If we should align to the surface bool followTerrainNormal = type.m_PaintInfo.m_SurfaceAlign; Vector2 alignPercentage = type.m_PaintInfo.m_SurfaceAlignInfluence; // Get the terrain data int[,] data = terrain.terrainData.GetDetailLayer(0, 0, detailMapSizeW, detailMapSizeH, layer); // Iterate data for (int i = 0; i < detailMapSizeH; i++) { for (int j = 0; j < detailMapSizeW; j++) { // j,i not i,j int count = data[j, i]; if (count > 0) { // Minimum 1 never 0 count = Mathf.Clamp(Mathf.CeilToInt(count * density), 1, count + 1); // Map from local space cell space to local terrain space Vector2 cellMin = new Vector2(patchSizeW * i, patchSizeH * j); Vector2 cellMax = cellMin + new Vector2(patchSizeW, patchSizeH); for (int d = 0; d < count; d++) { Vector3 randomInCell; randomInCell.x = Random.Range(cellMin.x, cellMax.x); randomInCell.z = Random.Range(cellMin.y, cellMax.y); randomInCell.y = 0; randomInCell = FoliageTerrainUtilities.TerrainLocalToTerrainNormalizedPos(randomInCell, terrain); float y = FoliageTerrainUtilities.TerrainHeight(randomInCell, terrain); // Build the rotation Quaternion rotation = Quaternion.identity; if (followTerrainNormal) { Quaternion slopeOrientation = Quaternion.LookRotation(FoliageTerrainUtilities.TerrainNormal(randomInCell, terrain)) * Quaternion.Euler(90, 0, 0); // How much we orient towards the slope rotation = Quaternion.Slerp(rotation, slopeOrientation, Random.Range(alignPercentage.x, alignPercentage.y)); } // Rotate around the Y axis rotation *= Quaternion.Euler(0, Random.Range(0, 360), 0); // Random in cell in world position randomInCell = FoliageTerrainUtilities.TerrainNormalizedToWorldPos(randomInCell, terrain); randomInCell.y = y; Vector3 scale; float x = Random.Range(proto.minWidth, proto.maxWidth); scale.x = x; scale.z = x; scale.y = Random.Range(proto.minHeight, proto.maxHeight); // Construct a foliage instance based on the foliage type data FoliageInstance instance = new FoliageInstance(); // Build the foliage data based on the type instance.m_Position = randomInCell; instance.m_Rotation = rotation; instance.m_Scale = scale; // Instantiate at a random pos in that cell edit.AddFoliageInstance(mapping, instance, label); extracted++; } } // If we should delete if (delete) { data[j, i] = 0; } } } // End detail array iteration // If we deleted set the new detail layer data if (delete) { terrain.terrainData.SetDetailLayer(0, 0, layer, data); } } // End types iteration // If we should disable the trees and foliage draw if (disable) { terrain.drawTreesAndFoliage = false; } // If we deleted anything we need to save if (delete) { UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(terrain.gameObject.scene); } FoliageLog.i("Extracted details: " + extracted + " from: " + terrain.name); }