[Fix] Update Voxel World
This commit is contained in:
@@ -41,6 +41,8 @@ namespace InfiniteWorld.VoxelWorld
|
||||
internal sealed class VoxelWorldAtlas : IDisposable
|
||||
{
|
||||
private const int FallbackTileSize = 32;
|
||||
private const string ShaderName = "Infinite World/VoxelWorld/TextureArrayUnlit";
|
||||
|
||||
private static readonly VoxelSurfaceType[] SurfaceOrder =
|
||||
{
|
||||
VoxelSurfaceType.CliffTop,
|
||||
@@ -49,29 +51,29 @@ namespace InfiniteWorld.VoxelWorld
|
||||
VoxelSurfaceType.WalkableSurface
|
||||
};
|
||||
|
||||
private readonly Dictionary<int, Dictionary<VoxelSurfaceType, Rect>> uvLookup;
|
||||
private readonly Dictionary<int, Dictionary<VoxelSurfaceType, int>> layerLookup;
|
||||
|
||||
public VoxelWorldAtlas(Texture2D texture, Material material, Dictionary<int, Dictionary<VoxelSurfaceType, Rect>> uvRects)
|
||||
private VoxelWorldAtlas(Texture2DArray textureArray, Material material, Dictionary<int, Dictionary<VoxelSurfaceType, int>> layers)
|
||||
{
|
||||
Texture = texture;
|
||||
TextureArray = textureArray;
|
||||
Material = material;
|
||||
uvLookup = uvRects;
|
||||
layerLookup = layers;
|
||||
}
|
||||
|
||||
public Texture2D Texture { get; }
|
||||
public Texture2DArray TextureArray { get; }
|
||||
|
||||
public Material Material { get; }
|
||||
|
||||
public int BiomeCount => uvLookup.Count;
|
||||
public int BiomeCount => layerLookup.Count;
|
||||
|
||||
public Rect GetUvRect(int biomeIndex, VoxelSurfaceType surfaceType)
|
||||
public int GetTextureLayer(int biomeIndex, VoxelSurfaceType surfaceType)
|
||||
{
|
||||
if (uvLookup.TryGetValue(biomeIndex, out Dictionary<VoxelSurfaceType, Rect> biomeRects) && biomeRects.TryGetValue(surfaceType, out Rect rect))
|
||||
if (layerLookup.TryGetValue(biomeIndex, out Dictionary<VoxelSurfaceType, int> biomeLayers) && biomeLayers.TryGetValue(surfaceType, out int layer))
|
||||
{
|
||||
return rect;
|
||||
return layer;
|
||||
}
|
||||
|
||||
return uvLookup[0][surfaceType];
|
||||
return layerLookup[0][surfaceType];
|
||||
}
|
||||
|
||||
public static VoxelWorldAtlas CreateRuntimeAtlas(IReadOnlyList<VoxelBiomeProfile> biomeProfiles)
|
||||
@@ -94,48 +96,60 @@ namespace InfiniteWorld.VoxelWorld
|
||||
}
|
||||
|
||||
int tileSize = DetermineTileSize(sourceBiomes);
|
||||
Texture2D texture = new Texture2D(tileSize * SurfaceOrder.Length, tileSize * sourceBiomes.Count, TextureFormat.RGBA32, false)
|
||||
int layerCount = sourceBiomes.Count * SurfaceOrder.Length;
|
||||
Texture2DArray textureArray = new Texture2DArray(tileSize, tileSize, layerCount, TextureFormat.RGBA32, false, false)
|
||||
{
|
||||
filterMode = FilterMode.Point,
|
||||
wrapMode = TextureWrapMode.Clamp,
|
||||
name = "VoxelWorld_RuntimeAtlas",
|
||||
wrapMode = TextureWrapMode.Repeat,
|
||||
anisoLevel = 1,
|
||||
name = "VoxelWorld_RuntimeTextureArray",
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
|
||||
Fill(texture, new Color(1f, 0f, 1f, 1f));
|
||||
|
||||
Dictionary<int, Dictionary<VoxelSurfaceType, Rect>> uvRects = new Dictionary<int, Dictionary<VoxelSurfaceType, Rect>>(sourceBiomes.Count);
|
||||
Dictionary<int, Dictionary<VoxelSurfaceType, int>> layers = new Dictionary<int, Dictionary<VoxelSurfaceType, int>>(sourceBiomes.Count);
|
||||
int layerIndex = 0;
|
||||
for (int biomeIndex = 0; biomeIndex < sourceBiomes.Count; biomeIndex++)
|
||||
{
|
||||
Dictionary<VoxelSurfaceType, Rect> biomeRects = new Dictionary<VoxelSurfaceType, Rect>(SurfaceOrder.Length);
|
||||
uvRects[biomeIndex] = biomeRects;
|
||||
Dictionary<VoxelSurfaceType, int> biomeLayers = new Dictionary<VoxelSurfaceType, int>(SurfaceOrder.Length);
|
||||
layers[biomeIndex] = biomeLayers;
|
||||
|
||||
for (int surfaceIndex = 0; surfaceIndex < SurfaceOrder.Length; surfaceIndex++)
|
||||
{
|
||||
VoxelSurfaceType surfaceType = SurfaceOrder[surfaceIndex];
|
||||
RectInt rect = new RectInt(surfaceIndex * tileSize, biomeIndex * tileSize, tileSize, tileSize);
|
||||
DrawSurfaceTile(texture, rect, sourceBiomes[biomeIndex], surfaceType, tileSize);
|
||||
biomeRects[surfaceType] = BuildUvRect(rect, texture.width, texture.height);
|
||||
Texture2D tileTexture = BuildSurfaceTexture(sourceBiomes[biomeIndex], surfaceType, tileSize);
|
||||
CopyTextureToArrayLayer(tileTexture, textureArray, layerIndex);
|
||||
biomeLayers[surfaceType] = layerIndex;
|
||||
layerIndex++;
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
UnityEngine.Object.Destroy(tileTexture);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(tileTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture.Apply(false, false);
|
||||
textureArray.Apply(false, false);
|
||||
|
||||
Shader shader = Shader.Find(ShaderName);
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Standard");
|
||||
Debug.LogError($"Shader '{ShaderName}' was not found. Falling back to '{shader?.name ?? "<missing>"}', but voxel texture array sampling will not work until the shader asset is imported correctly.");
|
||||
}
|
||||
|
||||
Shader shader = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard");
|
||||
Material material = new Material(shader)
|
||||
{
|
||||
name = "VoxelWorld_RuntimeMaterial",
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
|
||||
if (material.HasProperty("_BaseMap"))
|
||||
if (material.HasProperty("_TextureArray"))
|
||||
{
|
||||
material.SetTexture("_BaseMap", texture);
|
||||
}
|
||||
|
||||
if (material.HasProperty("_MainTex"))
|
||||
{
|
||||
material.SetTexture("_MainTex", texture);
|
||||
material.SetTexture("_TextureArray", textureArray);
|
||||
}
|
||||
|
||||
if (material.HasProperty("_BaseColor"))
|
||||
@@ -148,19 +162,19 @@ namespace InfiniteWorld.VoxelWorld
|
||||
material.SetColor("_Color", Color.white);
|
||||
}
|
||||
|
||||
return new VoxelWorldAtlas(texture, material, uvRects);
|
||||
return new VoxelWorldAtlas(textureArray, material, layers);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
UnityEngine.Object.Destroy(Texture);
|
||||
UnityEngine.Object.Destroy(TextureArray);
|
||||
UnityEngine.Object.Destroy(Material);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(Texture);
|
||||
UnityEngine.Object.DestroyImmediate(TextureArray);
|
||||
UnityEngine.Object.DestroyImmediate(Material);
|
||||
}
|
||||
}
|
||||
@@ -187,45 +201,66 @@ namespace InfiniteWorld.VoxelWorld
|
||||
return Mathf.Max(FallbackTileSize, tileSize);
|
||||
}
|
||||
|
||||
private static Rect BuildUvRect(RectInt rect, int textureWidth, int textureHeight)
|
||||
private static Texture2D BuildSurfaceTexture(VoxelBiomeProfile biome, VoxelSurfaceType surfaceType, int tileSize)
|
||||
{
|
||||
float paddingX = 0.5f / textureWidth;
|
||||
float paddingY = 0.5f / textureHeight;
|
||||
return new Rect(
|
||||
rect.xMin / (float)textureWidth + paddingX,
|
||||
rect.yMin / (float)textureHeight + paddingY,
|
||||
rect.width / (float)textureWidth - paddingX * 2f,
|
||||
rect.height / (float)textureHeight - paddingY * 2f);
|
||||
}
|
||||
|
||||
private static void DrawSurfaceTile(Texture2D atlas, RectInt targetRect, VoxelBiomeProfile biome, VoxelSurfaceType surfaceType, int tileSize)
|
||||
{
|
||||
Sprite sprite = biome != null ? biome.GetSprite(surfaceType) : null;
|
||||
if (sprite != null && TryBlitSprite(atlas, targetRect, sprite))
|
||||
Texture2D texture = new Texture2D(tileSize, tileSize, TextureFormat.RGBA32, false)
|
||||
{
|
||||
return;
|
||||
filterMode = FilterMode.Point,
|
||||
wrapMode = TextureWrapMode.Repeat,
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
|
||||
Fill(texture, new Color(1f, 0f, 1f, 1f));
|
||||
Sprite sprite = biome != null ? biome.GetSprite(surfaceType) : null;
|
||||
if (sprite == null || !TryBlitSprite(texture, sprite))
|
||||
{
|
||||
DrawFallbackSurface(texture, new RectInt(0, 0, tileSize, tileSize), surfaceType);
|
||||
}
|
||||
|
||||
DrawFallbackSurface(atlas, targetRect, surfaceType);
|
||||
texture.Apply(false, false);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static bool TryBlitSprite(Texture2D atlas, RectInt targetRect, Sprite sprite)
|
||||
private static void CopyTextureToArrayLayer(Texture2D source, Texture2DArray destination, int layerIndex)
|
||||
{
|
||||
bool canUseCopyTexture = SystemInfo.supports2DArrayTextures &&
|
||||
SystemInfo.copyTextureSupport != UnityEngine.Rendering.CopyTextureSupport.None &&
|
||||
source.width == destination.width &&
|
||||
source.height == destination.height;
|
||||
|
||||
if (canUseCopyTexture)
|
||||
{
|
||||
try
|
||||
{
|
||||
Graphics.CopyTexture(source, 0, 0, destination, layerIndex, 0);
|
||||
return;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Fall back to CPU copy when runtime copy is unavailable for this platform/import setup.
|
||||
}
|
||||
}
|
||||
|
||||
destination.SetPixels(source.GetPixels(), layerIndex, 0);
|
||||
}
|
||||
|
||||
private static bool TryBlitSprite(Texture2D target, Sprite sprite)
|
||||
{
|
||||
try
|
||||
{
|
||||
Texture2D source = sprite.texture;
|
||||
Rect spriteRect = sprite.textureRect;
|
||||
|
||||
for (int y = 0; y < targetRect.height; y++)
|
||||
for (int y = 0; y < target.height; y++)
|
||||
{
|
||||
float sampleY = Mathf.Lerp(spriteRect.yMin, spriteRect.yMax - 1f, y / (float)Mathf.Max(1, targetRect.height - 1));
|
||||
float sampleY = Mathf.Lerp(spriteRect.yMin, spriteRect.yMax - 1f, y / (float)Mathf.Max(1, target.height - 1));
|
||||
int sourceY = Mathf.Clamp(Mathf.RoundToInt(sampleY), 0, source.height - 1);
|
||||
|
||||
for (int x = 0; x < targetRect.width; x++)
|
||||
for (int x = 0; x < target.width; x++)
|
||||
{
|
||||
float sampleX = Mathf.Lerp(spriteRect.xMin, spriteRect.xMax - 1f, x / (float)Mathf.Max(1, targetRect.width - 1));
|
||||
float sampleX = Mathf.Lerp(spriteRect.xMin, spriteRect.xMax - 1f, x / (float)Mathf.Max(1, target.width - 1));
|
||||
int sourceX = Mathf.Clamp(Mathf.RoundToInt(sampleX), 0, source.width - 1);
|
||||
atlas.SetPixel(targetRect.x + x, targetRect.y + y, source.GetPixel(sourceX, sourceY));
|
||||
target.SetPixel(x, y, source.GetPixel(sourceX, sourceY));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +268,7 @@ namespace InfiniteWorld.VoxelWorld
|
||||
}
|
||||
catch (UnityException)
|
||||
{
|
||||
Debug.LogWarning($"Sprite '{sprite.name}' texture is not readable. Falling back to generated tile for voxel biome atlas.");
|
||||
Debug.LogWarning($"Sprite '{sprite.name}' texture is not readable. Falling back to generated tile for voxel biome texture array.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user