static batching for level geometry
This commit is contained in:
10
Source/Game/Level/BrushScript.cs
Normal file
10
Source/Game/Level/BrushScript.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
public class BrushScript : Script
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -333,10 +333,10 @@ namespace Game
|
||||
private void LoadMap(bool forceLoad)
|
||||
{
|
||||
worldSpawnActor = Actor.FindActor("WorldSpawn");
|
||||
LevelScript levelScript = worldSpawnActor?.GetScript<LevelScript>();
|
||||
if (worldSpawnActor != null)
|
||||
{
|
||||
#if FLAX_EDITOR
|
||||
LevelScript levelScript = worldSpawnActor.GetScript<LevelScript>();
|
||||
DateTime timestamp = File.GetLastWriteTime(mapPath);
|
||||
if (timestamp != levelScript.MapTimestamp)
|
||||
{
|
||||
@@ -375,6 +375,7 @@ namespace Game
|
||||
//Console.Print("Map parsing time: " + sw.Elapsed.TotalMilliseconds + "ms");
|
||||
|
||||
bool oneMesh = false;
|
||||
bool useStaticBatching = true;
|
||||
bool convexMesh = true;
|
||||
|
||||
if (worldSpawnActor == null)
|
||||
@@ -382,7 +383,7 @@ namespace Game
|
||||
worldSpawnActor = Actor.AddChild<Actor>();
|
||||
worldSpawnActor.Name = "WorldSpawn";
|
||||
|
||||
LevelScript levelScript = worldSpawnActor.AddScript<LevelScript>();
|
||||
levelScript = worldSpawnActor.AddScript<LevelScript>();
|
||||
#if FLAX_EDITOR
|
||||
levelScript.MapTimestamp = File.GetLastWriteTime(mapPath);
|
||||
#endif
|
||||
@@ -390,6 +391,8 @@ namespace Game
|
||||
//worldSpawnActor.HideFlags |= HideFlags.DontSave;
|
||||
//worldSpawnActor.HideFlags |= HideFlags.DontSelect;
|
||||
}
|
||||
else
|
||||
levelScript = worldSpawnActor.GetScript<LevelScript>();
|
||||
|
||||
if (!oneMesh)
|
||||
{
|
||||
@@ -605,73 +608,238 @@ namespace Game
|
||||
|
||||
// pass 3: static models & collision
|
||||
sw.Restart();
|
||||
brushIndex = 0;
|
||||
foreach (BrushGeometry geom in brushGeometries)
|
||||
|
||||
if (!useStaticBatching)
|
||||
{
|
||||
StaticModel childModel = worldSpawnActor.AddChild<StaticModel>();
|
||||
childModel.Name = "Brush_" + brushIndex;
|
||||
childModel.Model = geom.model;
|
||||
childModel.Position = geom.offset;
|
||||
|
||||
for (int i = 0; i < geom.meshes.Length; i++)
|
||||
childModel.SetMaterial(i, geom.meshes[i].material);
|
||||
|
||||
uint[] indices = new uint[geom.vertices.Length];
|
||||
for (uint i = 0; i < indices.Length; i++)
|
||||
indices[i] = i;
|
||||
|
||||
bool isClipMaterial = false;
|
||||
bool isMissingMaterial = false;
|
||||
if (geom.meshes.Length == 1)
|
||||
brushIndex = 0;
|
||||
foreach (BrushGeometry geom in brushGeometries)
|
||||
{
|
||||
MaterialParameter info = geom.meshes[0].material.GetParameter("IsClipMaterial");
|
||||
if (info != null && (bool)info.Value)
|
||||
StaticModel childModel = worldSpawnActor.AddChild<StaticModel>();
|
||||
childModel.Name = "Brush_" + brushIndex;
|
||||
childModel.Model = geom.model;
|
||||
childModel.Position = geom.offset;
|
||||
|
||||
for (int i = 0; i < geom.meshes.Length; i++)
|
||||
childModel.SetMaterial(i, geom.meshes[i].material);
|
||||
|
||||
BrushScript brushScript = childModel.AddScript<BrushScript>();
|
||||
|
||||
uint[] indices = new uint[geom.vertices.Length];
|
||||
for (uint i = 0; i < indices.Length; i++)
|
||||
indices[i] = i;
|
||||
|
||||
bool isClipMaterial = false;
|
||||
bool isMissingMaterial = false;
|
||||
if (geom.meshes.Length == 1)
|
||||
{
|
||||
MaterialParameter info = geom.meshes[0].material.GetParameter("IsClipMaterial");
|
||||
if (info != null && (bool)info.Value)
|
||||
{
|
||||
var entries = childModel.Entries;
|
||||
entries[0].Visible = false;
|
||||
entries[0].ShadowsMode = ShadowsCastingMode.None;
|
||||
entries[0].ReceiveDecals = false;
|
||||
childModel.Entries = entries;
|
||||
isClipMaterial = true;
|
||||
}
|
||||
|
||||
if (geom.meshes[0].material == missingMaterial)
|
||||
isMissingMaterial = true;
|
||||
}
|
||||
|
||||
/*{
|
||||
var entries = childModel.Entries;
|
||||
entries[0].Visible = false;
|
||||
entries[0].ShadowsMode = ShadowsCastingMode.None;
|
||||
entries[0].ReceiveDecals = false;
|
||||
for (int i=0; i < entries.Length; i++)
|
||||
entries[i].Visible = false;
|
||||
childModel.Entries = entries;
|
||||
isClipMaterial = true;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (geom.meshes[0].material == missingMaterial)
|
||||
isMissingMaterial = true;
|
||||
}
|
||||
if (!isClipMaterial && !isMissingMaterial)
|
||||
sdfModels.Add(geom.model);
|
||||
|
||||
/*{
|
||||
var entries = childModel.Entries;
|
||||
for (int i=0; i < entries.Length; i++)
|
||||
entries[i].Visible = false;
|
||||
childModel.Entries = entries;
|
||||
}*/
|
||||
|
||||
if (!isClipMaterial && !isMissingMaterial)
|
||||
sdfModels.Add(geom.model);
|
||||
|
||||
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
|
||||
if (collisionData.CookCollision(
|
||||
convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, geom.vertices,
|
||||
indices))
|
||||
{
|
||||
bool failed = true;
|
||||
if (convexMesh)
|
||||
{
|
||||
// fallback to triangle mesh
|
||||
failed = collisionData.CookCollision(CollisionDataType.TriangleMesh,
|
||||
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
|
||||
if (collisionData.CookCollision(
|
||||
convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh,
|
||||
geom.vertices,
|
||||
indices);
|
||||
if (!failed)
|
||||
Console.PrintWarning("Hull brush " + brushIndex + " is not convex");
|
||||
indices))
|
||||
{
|
||||
bool failed = true;
|
||||
if (convexMesh)
|
||||
{
|
||||
// fallback to triangle mesh
|
||||
failed = collisionData.CookCollision(CollisionDataType.TriangleMesh,
|
||||
geom.vertices,
|
||||
indices);
|
||||
if (!failed)
|
||||
Console.PrintWarning("Hull brush " + brushIndex + " is not convex");
|
||||
}
|
||||
|
||||
if (failed)
|
||||
throw new Exception("failed to cook final collision");
|
||||
}
|
||||
|
||||
if (failed)
|
||||
throw new Exception("failed to cook final collision");
|
||||
MeshCollider meshCollider = childModel.AddChild<MeshCollider>();
|
||||
meshCollider.CollisionData = collisionData;
|
||||
brushIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// create brush holder actors and collision
|
||||
brushIndex = 0;
|
||||
foreach (BrushGeometry geom in brushGeometries)
|
||||
{
|
||||
Actor childModel = worldSpawnActor.AddChild<Actor>();
|
||||
childModel.Name = "Brush_" + brushIndex;
|
||||
//childModel.Model = geom.model;
|
||||
childModel.Position = geom.offset;
|
||||
|
||||
//for (int i = 0; i < geom.meshes.Length; i++)
|
||||
// childModel.SetMaterial(i, geom.meshes[i].material);
|
||||
|
||||
BrushScript brushScript = childModel.AddScript<BrushScript>();
|
||||
|
||||
uint[] indices = new uint[geom.vertices.Length];
|
||||
for (uint i = 0; i < indices.Length; i++)
|
||||
indices[i] = i;
|
||||
|
||||
bool isClipMaterial = false;
|
||||
bool isMissingMaterial = false;
|
||||
/*if (geom.meshes.Length == 1)
|
||||
{
|
||||
MaterialParameter info = geom.meshes[0].material.GetParameter("IsClipMaterial");
|
||||
if (info != null && (bool)info.Value)
|
||||
{
|
||||
var entries = childModel.Entries;
|
||||
entries[0].Visible = false;
|
||||
entries[0].ShadowsMode = ShadowsCastingMode.None;
|
||||
entries[0].ReceiveDecals = false;
|
||||
childModel.Entries = entries;
|
||||
isClipMaterial = true;
|
||||
}
|
||||
|
||||
if (geom.meshes[0].material == missingMaterial)
|
||||
isMissingMaterial = true;
|
||||
}*/
|
||||
|
||||
/*{
|
||||
var entries = childModel.Entries;
|
||||
for (int i=0; i < entries.Length; i++)
|
||||
entries[i].Visible = false;
|
||||
childModel.Entries = entries;
|
||||
}*/
|
||||
|
||||
//if (!isClipMaterial && !isMissingMaterial)
|
||||
// sdfModels.Add(geom.model);
|
||||
|
||||
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
|
||||
if (collisionData.CookCollision(
|
||||
convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh,
|
||||
geom.vertices,
|
||||
indices))
|
||||
{
|
||||
bool failed = true;
|
||||
if (convexMesh)
|
||||
{
|
||||
// fallback to triangle mesh
|
||||
failed = collisionData.CookCollision(CollisionDataType.TriangleMesh,
|
||||
geom.vertices,
|
||||
indices);
|
||||
if (!failed)
|
||||
Console.PrintWarning("Hull brush " + brushIndex + " is not convex");
|
||||
}
|
||||
|
||||
if (failed)
|
||||
throw new Exception("failed to cook final collision");
|
||||
}
|
||||
|
||||
MeshCollider meshCollider = childModel.AddChild<MeshCollider>();
|
||||
meshCollider.CollisionData = collisionData;
|
||||
brushIndex++;
|
||||
}
|
||||
|
||||
MeshCollider meshCollider = childModel.AddChild<MeshCollider>();
|
||||
meshCollider.CollisionData = collisionData;
|
||||
brushIndex++;
|
||||
// collect batches
|
||||
brushIndex = 0;
|
||||
Dictionary<MaterialBase, List<BrushGeometry>> batches = new Dictionary<MaterialBase, List<BrushGeometry>>();
|
||||
foreach (BrushGeometry geom in brushGeometries)
|
||||
{
|
||||
bool isClipMaterial = false;
|
||||
bool isMissingMaterial = false;
|
||||
if (geom.meshes.Length == 1)
|
||||
{
|
||||
MaterialParameter info = geom.meshes[0].material.GetParameter("IsClipMaterial");
|
||||
if (info != null && (bool)info.Value)
|
||||
isClipMaterial = true;
|
||||
|
||||
if (geom.meshes[0].material == missingMaterial)
|
||||
isMissingMaterial = true;
|
||||
}
|
||||
|
||||
var brushMaterial = geom.meshes[0].material;
|
||||
|
||||
if (!isClipMaterial)
|
||||
{
|
||||
if (!batches.TryGetValue(brushMaterial, out var batchGeometries))
|
||||
{
|
||||
batchGeometries = new List<BrushGeometry>();
|
||||
batches.Add(brushMaterial, batchGeometries);
|
||||
}
|
||||
|
||||
batchGeometries.Add(geom);
|
||||
}
|
||||
|
||||
/*{
|
||||
var entries = childModel.Entries;
|
||||
for (int i=0; i < entries.Length; i++)
|
||||
entries[i].Visible = false;
|
||||
childModel.Entries = entries;
|
||||
}*/
|
||||
|
||||
//if (!isClipMaterial && !isMissingMaterial)
|
||||
// sdfModels.Add(geom.model);
|
||||
|
||||
brushIndex++;
|
||||
}
|
||||
|
||||
foreach (var kvp in batches)
|
||||
{
|
||||
List<Float3> normals = new List<Float3>();
|
||||
List<Float2> uvs = new List<Float2>();
|
||||
List<Float3> vertices = new List<Float3>();
|
||||
List<uint> indices = new List<uint>();
|
||||
|
||||
uint indicesOffset = 0;
|
||||
foreach (BrushGeometry geom in kvp.Value)
|
||||
{
|
||||
for (int i=0; i<geom.meshes[0].vertices.Count; i++)
|
||||
{
|
||||
var v = geom.meshes[0].vertices[i];
|
||||
var n = geom.meshes[0].normals[i];
|
||||
var uv = geom.meshes[0].uvs[i];
|
||||
|
||||
vertices.Add(v + geom.offset);
|
||||
uvs.Add(uv);
|
||||
normals.Add(n);
|
||||
|
||||
indices.Add(indicesOffset);
|
||||
indicesOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
var batchModel = Content.CreateVirtualAsset<Model>();
|
||||
batchModel.SetupLODs(new[] { 1 });
|
||||
batchModel.SetupMaterialSlots(1);
|
||||
|
||||
batchModel.LODs[0].Meshes[0].UpdateMesh(vertices, indices, normals,
|
||||
null, uvs);
|
||||
batchModel.LODs[0].Meshes[0].MaterialSlotIndex = 0;
|
||||
batchModel.MaterialSlots[0].Material = kvp.Key;
|
||||
|
||||
StaticModel childModel = worldSpawnActor.AddChild<StaticModel>();
|
||||
childModel.Name = "Batch_" + kvp.Key.Path;
|
||||
childModel.Model = batchModel;
|
||||
//childModel.Position = geom.offset;
|
||||
}
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
@@ -862,7 +1030,7 @@ namespace Game
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
Console.Print($"Generating level SDF ({sdfModels.Count} models)...");
|
||||
|
||||
Parallel.ForEach(sdfModels, new ParallelOptions() { MaxDegreeOfParallelism = 1 }, model =>
|
||||
Parallel.ForEach(sdfModels/*, new ParallelOptions() { MaxDegreeOfParallelism = 1 }*/, model =>
|
||||
{
|
||||
if (model.WaitForLoaded())
|
||||
throw new Exception("model was not loaded");
|
||||
|
||||
Reference in New Issue
Block a user