static batching for level geometry

This commit is contained in:
2022-06-15 20:53:28 +03:00
parent 5d0b1ea27d
commit 97e74be0eb
2 changed files with 236 additions and 58 deletions

View File

@@ -0,0 +1,10 @@
using System;
using FlaxEngine;
namespace Game
{
[ExecuteInEditMode]
public class BrushScript : Script
{
}
}

View File

@@ -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");