Skip to content

Commit c378927

Browse files
Merge pull request #374 from Gothic-Unity-Project/stack/07-world-mesh
Stack/07 world mesh
2 parents e5fa207 + c2c1d76 commit c378927

17 files changed

Lines changed: 716 additions & 276 deletions

Assets/Gothic-Core/Scripts/Adapters/Scenes/PreCachingScene.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ private async Task CreateCaches()
150150
await stationaryLightCache.CalculateStationaryLights(world.RootObjects, worldIndex);
151151
watch.LogAndRestart($"{worldName}: Stationary lights calculated.");
152152

153-
await worldChunkCache.CalculateWorldChunks(world, stationaryLightCache.StationaryLightBounds, worldIndex);
153+
await worldChunkCache.CalculateWorldChunks(world, stationaryLightCache.StationaryLightBounds,
154+
worldIndex, textureArrayCache.TextureArrayInformation);
154155
watch.LogAndRestart($"{worldName}: World chunks calculated.");
155156

156157
await _staticCacheService.SaveWorldCache(worldName, worldChunkCache.MergedChunksByLights, stationaryLightCache.StationaryLightInfos);

Assets/Gothic-Core/Scripts/Domain/Meshes/Builder/AbstractMeshBuilder.cs

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using MyBox;
1313
using Reflex.Attributes;
1414
using UnityEngine;
15-
using UnityEngine.Rendering;
1615
using ZenKit;
1716
using Logger = Gothic.Core.Logging.Logger;
1817
using Material = UnityEngine.Material;
@@ -117,7 +116,7 @@ public void SetMdm(string mdmName)
117116

118117
if (Mdm == null)
119118
{
120-
Logger.LogError($"MDH from name >{mdmName}< for object >{RootGo.name}< not found.", LogCat.Mesh);
119+
Logger.LogError($"MDM from name >{mdmName}< for object >{RootGo.name}< not found.", LogCat.Mesh);
121120
}
122121
}
123122

@@ -142,7 +141,7 @@ public void SetMrm(string mrmName)
142141

143142
if (Mrm == null)
144143
{
145-
Logger.LogError($"MDH from name >{mrmName}< for object >{RootGo.name}< not found.", LogCat.Mesh);
144+
Logger.LogError($"MRM from name >{mrmName}< for object >{RootGo.name}< not found.", LogCat.Mesh);
146145
}
147146
}
148147

@@ -392,7 +391,7 @@ protected void PrepareMeshRenderer(Renderer rend, IMultiResolutionMesh mrmData)
392391
if (materialData.Texture.IsEmpty()) // No texture to add.
393392
{
394393
Logger.LogWarning("No texture was set for: " + materialData.Name, LogCat.Mesh);
395-
return;
394+
continue;
396395
}
397396

398397
Texture texture;
@@ -447,6 +446,9 @@ protected void PrepareMeshRenderer(Renderer rend, IMultiResolutionMesh mrmData)
447446
protected void PrepareMeshFilter(MeshFilter meshFilter, IMultiResolutionMesh mrmData, Renderer meshRenderer, int meshIndex, List<System.Numerics.Vector3> calculatedVertices = null)
448447
{
449448
// ISoftSkinMeshes will be prepared before reaching this method. This is due to NPC armors having dedicated offsets per item.
449+
// A non-null calculatedVertices is also our soft-skin signal: only that overload passes it, and its bone
450+
// weights are filled afterwards in the un-welded 3-per-triangle order (see the ISoftSkinMesh overload).
451+
var isSoftSkin = calculatedVertices != null;
450452
calculatedVertices ??= mrmData.Positions;
451453

452454
var subMeshPerTextureFormat = new Dictionary<TextureCacheService.TextureArrayTypes, int>();
@@ -471,12 +473,22 @@ protected void PrepareMeshFilter(MeshFilter meshFilter, IMultiResolutionMesh mrm
471473

472474
int triangleCount = mrmData.SubMeshes.Sum(i => i.Triangles.Count);
473475
int vertexCount = triangleCount * 3;
474-
int index = 0;
475476
var preparedVertices = new List<Vector3>(vertexCount);
476477
var preparedUVs = new List<Vector4>(vertexCount);
477478
var normals = new List<Vector3>(vertexCount);
478479
var preparedTriangles = new List<List<int>>();
479480

481+
// Weld identical corners to a single Unity vertex instead of emitting 3 fresh vertices per triangle.
482+
// This removes the ~3x vertex bloat (and the vertex-shader + bandwidth cost it carries; on Quest the
483+
// stationary lighting is computed per-vertex). Keying on the full (position, normal, uv) tuple is
484+
// visually lossless: hard edges and UV seams keep their own vertices, only true duplicates collapse.
485+
// Disabled for soft-skin (bone weights rely on the expanded order) and morph meshes (morph animation
486+
// maps source positions to specific Unity vertices).
487+
var weldVertices = !isSoftSkin && Mmb == null;
488+
var weldMap = weldVertices
489+
? new Dictionary<(Vector3 pos, Vector3 normal, Vector4 uv), int>(vertexCount)
490+
: null;
491+
480492
foreach (var subMesh in mrmData.SubMeshes)
481493
{
482494
// When using the texture array, get the index of the array of the matching texture format. Build sub meshes for each texture format, i.e. separating opaque and alpha cutout textures.
@@ -498,33 +510,46 @@ protected void PrepareMeshFilter(MeshFilter meshFilter, IMultiResolutionMesh mrm
498510
preparedTriangles.Add(new List<int>());
499511
}
500512

501-
for (var i = 0; i < subMesh.Triangles.Count; i++)
513+
// Determine which triangle list to use
514+
var triangleList = UseTextureArray
515+
? preparedTriangles[subMeshPerTextureFormat[textureArrayType]]
516+
: preparedTriangles[^1];
517+
518+
void AddWedgeVertex(MeshWedge wedge)
502519
{
503-
// One triangle is made of 3 elements for Unity. We therefore need to prepare 3 elements within one loop.
504-
MeshWedge[] wedges =
505-
{
506-
subMesh.Wedges[subMesh.Triangles[i].Wedge2], subMesh.Wedges[subMesh.Triangles[i].Wedge1],
507-
subMesh.Wedges[subMesh.Triangles[i].Wedge0]
508-
};
520+
var rawPosition = calculatedVertices[wedge.Index];
521+
var position = new Vector3(rawPosition.X / 100f, rawPosition.Y / 100f, rawPosition.Z / 100f);
522+
var normal = new Vector3(wedge.Normal.X, wedge.Normal.Y, wedge.Normal.Z);
523+
var uv = new Vector4(wedge.Texture.X * textureScale.x, wedge.Texture.Y * textureScale.y,
524+
textureArrayIndex, maxMipLevel);
509525

510-
for (var w = 0; w < wedges.Length; w++)
526+
if (weldVertices && weldMap.TryGetValue((position, normal, uv), out var existingIndex))
511527
{
512-
preparedVertices.Add(calculatedVertices[wedges[w].Index].ToUnityVector());
513-
if (UseTextureArray)
514-
{
515-
preparedTriangles[subMeshPerTextureFormat[textureArrayType]].Add(index++);
516-
}
517-
else
518-
{
519-
preparedTriangles[preparedTriangles.Count - 1].Add(index++);
520-
}
521-
522-
normals.Add(wedges[w].Normal.ToUnityVector());
523-
var uv = Vector2.Scale(textureScale, wedges[w].Texture.ToUnityVector());
524-
preparedUVs.Add(new Vector4(uv.x, uv.y, textureArrayIndex, maxMipLevel));
525-
526-
CreateMorphMeshEntry(wedges[w].Index, preparedVertices.Count);
528+
triangleList.Add(existingIndex);
529+
return;
527530
}
531+
532+
var vertexIndex = preparedVertices.Count;
533+
preparedVertices.Add(position);
534+
normals.Add(normal);
535+
preparedUVs.Add(uv);
536+
triangleList.Add(vertexIndex);
537+
538+
if (weldVertices)
539+
weldMap[(position, normal, uv)] = vertexIndex;
540+
else
541+
CreateMorphMeshEntry(wedge.Index, preparedVertices.Count);
542+
}
543+
544+
var wedges = subMesh.Wedges;
545+
var triangles = subMesh.Triangles;
546+
for (var ti = 0; ti < triangles.Count; ti++)
547+
{
548+
var triangle = triangles[ti];
549+
// The wedge order is reversed to flip the triangle winding for Unity's coordinate system.
550+
AddWedgeVertex(wedges[triangle.Wedge2]);
551+
AddWedgeVertex(wedges[triangle.Wedge1]);
552+
AddWedgeVertex(wedges[triangle.Wedge0]);
528553
}
529554
}
530555

@@ -543,6 +568,13 @@ protected void PrepareMeshFilter(MeshFilter meshFilter, IMultiResolutionMesh mrm
543568

544569
CreateMorphMeshEnd(preparedVertices);
545570

571+
// Reorder the (now welded) index/vertex buffers for post-transform vertex-cache locality. Only useful
572+
// once vertices are shared, and it reorders the vertex buffer - so it is strictly gated to welded meshes
573+
// (never morph: their external positionIndex->vertex mapping would be invalidated; never soft-skin).
574+
// Built once per unique mesh (cached by name), so the cost amortizes across all instances.
575+
if (weldVertices)
576+
mesh.Optimize();
577+
546578
_multiTypeCacheService.Meshes.Add($"{MeshName}_{meshIndex}", mesh);
547579
}
548580

@@ -736,22 +768,18 @@ private Material GetDefaultMaterial(TextureCacheService.TextureArrayTypes textur
736768
{
737769
if (UseTextureArray)
738770
{
739-
Shader shader;
740771
switch (textureType)
741772
{
742773
case TextureCacheService.TextureArrayTypes.Opaque:
743-
shader = Constants.ShaderWorldLit;
744-
break;
774+
return new Material(Constants.ShaderWorldLit);
745775
case TextureCacheService.TextureArrayTypes.Transparent:
746776
// Cutout for e.g. bushes.
747-
shader = Constants.ShaderLitAlphaToCoverage;
748-
break;
777+
return new Material(Constants.ShaderLitAlphaToCoverage);
778+
case TextureCacheService.TextureArrayTypes.Water:
779+
return GetWaterMaterial();
749780
default:
750781
throw new ArgumentOutOfRangeException(nameof(textureType), textureType, null);
751782
}
752-
753-
var material = new Material(shader);
754-
return material;
755783
}
756784
else
757785
{
@@ -761,10 +789,8 @@ private Material GetDefaultMaterial(TextureCacheService.TextureArrayTypes textur
761789

762790
protected Material GetWaterMaterial()
763791
{
764-
var material = new Material(Constants.ShaderWater);
765-
// Manually correct the render queue for alpha test, as Unity doesn't want to do it from the shader's render queue tag.
766-
material.renderQueue = (int)RenderQueue.Transparent;
767-
return material;
792+
// The render queue is defined by the water shader's "Queue" tag.
793+
return new Material(Constants.ShaderWater);
768794
}
769795

770796
protected void SetPosAndRot(GameObject obj, Matrix4x4 matrix)

0 commit comments

Comments
 (0)