/// <summary> /// Gets terrain albedo texture array containing each terrain tile in a seperate array slice. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>Texture2DArray or null</returns> public Texture2DArray GetTerrainAlbedoTextureArray( int archive, bool stayReadable = false, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); int numSlices = 0; if (textureFile.RecordCount == 56) { numSlices = textureFile.RecordCount; } else { return(null); } Texture2DArray textureArray; if (TextureReplacement.CustomTextureExist(archive, 0, 0)) { GetTextureResults results = new GetTextureResults(); TextureReplacement.LoadCustomTextureResults(archive, 0, 0, ref results, ref DaggerfallUnity.Instance.MaterialReader.GenerateNormals); textureArray = new Texture2DArray(results.albedoMap.width, results.albedoMap.height, numSlices, ParseTextureFormat(nonAlphaFormat), MipMaps); } else { textureArray = new Texture2DArray(textureFile.GetWidth(0), textureFile.GetWidth(1), numSlices, ParseTextureFormat(nonAlphaFormat), MipMaps); } // Rollout tiles into texture array for (int record = 0; record < textureFile.RecordCount; record++) { // Create base image with gutter DFSize sz; Color32[] albedo = textureFile.GetColor32(record, 0, -1, 0, out sz); // Import custom texture(s) GetTextureResults resultsTile = new GetTextureResults(); if (TextureReplacement.CustomTextureExist(archive, record, 0)) { TextureReplacement.LoadCustomTextureResults(archive, record, 0, ref resultsTile, ref DaggerfallUnity.Instance.MaterialReader.GenerateNormals); albedo = resultsTile.albedoMap.GetPixels32(); } // Insert into texture array textureArray.SetPixels32(albedo, record, 0); } textureArray.Apply(true, !stayReadable); // Change settings for these textures textureArray.wrapMode = TextureWrapMode.Clamp; textureArray.anisoLevel = 8; return(textureArray); }
/// <summary> /// Gets Unity albedo texture from Daggerfall texture with minimum options. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="record">Record index.</param> /// <param name="frame">Frame index.</param> /// <param name="alphaIndex">Index to receive transparent alpha.</param> /// <returns>Texture2D or null.</returns> public Texture2D GetTexture2D( int archive, int record, int frame = 0, int alphaIndex = 0) { GetTextureSettings settings = new GetTextureSettings(); settings.archive = archive; settings.record = record; settings.frame = frame; settings.alphaIndex = alphaIndex; GetTextureResults results = GetTexture2D(settings); return results.albedoMap; }
/// <summary> /// TEMP: Creates super-atlas populated with all archives in array. /// Currently does not support animated textures, normal map, or emission map. /// TODO: Integrate this feature fully with material system. /// </summary> /// <param name="archives">Archive array.</param> /// <param name="borderSize">Number of pixels border to add around image.</param> /// <param name="dilate">Blend texture into surrounding empty pixels. Requires border.</param> /// <param name="maxAtlasSize">Maximum atlas size.</param> /// <param name="alphaTextureFormat">Alpha TextureFormat.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>TextureAtlasBuilder.</returns> public TextureAtlasBuilder CreateTextureAtlasBuilder( int[] archives, int borderSize = 0, bool dilate = false, int maxAtlasSize = 2048, SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { // Iterate archives TextureFile textureFile = new TextureFile(); TextureAtlasBuilder builder = new TextureAtlasBuilder(); GetTextureSettings settings = TextureReader.CreateTextureSettings(0, 0, 0, 0, borderSize, dilate, maxAtlasSize); settings.stayReadable = true; for (int i = 0; i < archives.Length; i++) { // Load texture file settings.archive = archives[i]; textureFile.Load(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); // Add all records for this archive - single frame only for (int record = 0; record < textureFile.RecordCount; record++) { settings.record = record; GetTextureResults results = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat); DFSize size = textureFile.GetSize(record); DFSize scale = textureFile.GetScale(record); builder.AddTextureItem( results.albedoMap, settings.archive, settings.record, 0, 1, new Vector2(size.Width, size.Height), new Vector2(scale.Width, scale.Height)); } } // Apply the builder builder.Rebuild(borderSize); return(builder); }
///// <summary> ///// TEMP: Creates super-atlas populated with all archives in array. ///// Currently does not support animated textures, normal map, or emission map. ///// TODO: Integrate this feature fully with material system. ///// </summary> ///// <param name="archives">Archive array.</param> ///// <param name="borderSize">Number of pixels border to add around image.</param> ///// <param name="dilate">Blend texture into surrounding empty pixels. Requires border.</param> ///// <param name="maxAtlasSize">Maximum atlas size.</param> ///// <param name="alphaTextureFormat">Alpha TextureFormat.</param> ///// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> ///// <returns>TextureAtlasBuilder.</returns> //public TextureAtlasBuilder CreateTextureAtlasBuilder( // int[] archives, // int borderSize = 0, // bool dilate = false, // int maxAtlasSize = 2048, // SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32, // SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) //{ // // Iterate archives // TextureFile textureFile = new TextureFile(); // TextureAtlasBuilder builder = new TextureAtlasBuilder(); // GetTextureSettings settings = TextureReader.CreateTextureSettings(0, 0, 0, 0, borderSize, dilate, maxAtlasSize); // settings.stayReadable = true; // for (int i = 0; i < archives.Length; i++) // { // // Load texture file // settings.archive = archives[i]; // textureFile.Load(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); // // Add all records for this archive - single frame only // for (int record = 0; record < textureFile.RecordCount; record++) // { // settings.record = record; // GetTextureResults results = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat); // DFSize size = textureFile.GetSize(record); // DFSize scale = textureFile.GetScale(record); // builder.AddTextureItem( // results.albedoMap, // settings.archive, // settings.record, // 0, 1, // new Vector2(size.Width, size.Height), // new Vector2(scale.Width, scale.Height)); // } // } // // Apply the builder // builder.Rebuild(borderSize); // return builder; //} /// <summary> /// Gets specially packed tileset atlas for terrains. /// This needs to be improved to create each mip level manually to further reduce artifacts. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns></returns> public GetTextureResults GetTerrainTilesetTexture( int archive, bool stayReadable = false, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { const int atlasDim = 2048; const int gutterSize = 32; GetTextureResults results = new GetTextureResults(); // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); if (textureFile.RecordCount != 56) { return(results); } // Rollout tiles into atlas. // This is somewhat inefficient, but fortunately atlases only // need to be generated once and can be prepared offline where // startup time is critical. int x = 0, y = 0; Color32[] atlasColors = new Color32[atlasDim * atlasDim]; for (int record = 0; record < textureFile.RecordCount; record++) { // Create base image with gutter DFSize sz; Color32[] albedo = textureFile.GetColor32(record, 0, -1, gutterSize, out sz); // Wrap and clamp textures based on tile switch (record) { // Textures that do not tile in any direction case 5: case 7: case 10: case 12: case 15: case 17: case 20: case 22: case 25: case 27: case 30: case 32: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 55: ImageProcessing.ClampBorder(ref albedo, sz, gutterSize); break; // Textures that clamp horizontally and tile vertically case 6: case 11: case 16: case 21: case 26: case 31: ImageProcessing.WrapBorder(ref albedo, sz, gutterSize, false); ImageProcessing.ClampBorder(ref albedo, sz, gutterSize, true, false); break; // Textures that tile in all directions default: ImageProcessing.WrapBorder(ref albedo, sz, gutterSize); break; } // Create variants Color32[] rotate = ImageProcessing.RotateColors(ref albedo, sz.Width, sz.Height); Color32[] flip = ImageProcessing.FlipColors(ref albedo, sz.Width, sz.Height); Color32[] rotateFlip = ImageProcessing.RotateColors(ref flip, sz.Width, sz.Height); // Insert into atlas ImageProcessing.InsertColors(ref albedo, ref atlasColors, x, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref rotate, ref atlasColors, x + sz.Width, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref flip, ref atlasColors, x + sz.Width * 2, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref rotateFlip, ref atlasColors, x + sz.Width * 3, y, sz.Width, sz.Height, atlasDim, atlasDim); // Increment position x += sz.Width * 4; if (x >= atlasDim) { y += sz.Height; x = 0; } } // Create Texture2D Texture2D albedoAtlas = new Texture2D(atlasDim, atlasDim, ParseTextureFormat(nonAlphaFormat), MipMaps); albedoAtlas.SetPixels32(atlasColors); albedoAtlas.Apply(true, !stayReadable); // Change settings for these textures albedoAtlas.wrapMode = TextureWrapMode.Clamp; albedoAtlas.anisoLevel = 8; // Store results results.albedoMap = albedoAtlas; return(results); }
/// <summary> /// Gets Texture2D atlas from Daggerfall texture archive. /// Every record and frame in the archive will be added to atlas. /// An array of rects will be returned with sub-texture rect for each record index and frame. /// Currently supports one archive per atlas. Super-atlas packing (multiple archives) is in the works. /// </summary> /// <param name="settings">Get texture settings.</param> /// <param name="alphaTextureFormat">Alpha TextureFormat.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>GetTextureResults.</returns> public GetTextureResults GetTexture2DAtlas( GetTextureSettings settings, SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { GetTextureResults results = new GetTextureResults(); // Individual textures must remain readable to pack into atlas bool stayReadable = settings.stayReadable; settings.stayReadable = true; // Assign texture file TextureFile textureFile; if (settings.textureFile == null) { textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); settings.textureFile = textureFile; } else { textureFile = settings.textureFile; } // Create lists results.atlasSizes = new List <Vector2>(textureFile.RecordCount); results.atlasScales = new List <Vector2>(textureFile.RecordCount); results.atlasOffsets = new List <Vector2>(textureFile.RecordCount); results.atlasFrameCounts = new List <int>(textureFile.RecordCount); // Read every texture in archive bool hasNormalMaps = false; bool hasEmissionMaps = false; bool hasAnimation = false; bool allowImport = (settings.atlasMaxSize == 4096); List <Texture2D> albedoTextures = new List <Texture2D>(); List <Texture2D> normalTextures = new List <Texture2D>(); List <Texture2D> emissionTextures = new List <Texture2D>(); List <RecordIndex> indices = new List <RecordIndex>(); for (int record = 0; record < textureFile.RecordCount; record++) { // Get record index and frame count settings.record = record; int frames = textureFile.GetFrameCount(record); if (frames > 1) { hasAnimation = true; } // Get record information DFSize size = textureFile.GetSize(record); DFSize scale = textureFile.GetScale(record); DFPosition offset = textureFile.GetOffset(record); RecordIndex ri = new RecordIndex() { startIndex = albedoTextures.Count, frameCount = frames, width = size.Width, height = size.Height, }; indices.Add(ri); for (int frame = 0; frame < frames; frame++) { settings.frame = frame; GetTextureResults nextTextureResults = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat, allowImport); albedoTextures.Add(nextTextureResults.albedoMap); if (nextTextureResults.normalMap != null) { normalTextures.Add(nextTextureResults.normalMap); hasNormalMaps = true; } if (nextTextureResults.emissionMap != null) { emissionTextures.Add(nextTextureResults.emissionMap); hasEmissionMaps = true; } } results.atlasSizes.Add(new Vector2(size.Width, size.Height)); results.atlasScales.Add(new Vector2(scale.Width, scale.Height)); results.atlasOffsets.Add(new Vector2(offset.X, offset.Y)); results.atlasFrameCounts.Add(frames); results.textureFile = textureFile; } // Pack albedo textures into atlas and get our rects Texture2D atlasAlbedoMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps); Rect[] rects = atlasAlbedoMap.PackTextures(albedoTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); // Pack normal textures into atlas Texture2D atlasNormalMap = null; if (hasNormalMaps) { // Normals must be ARGB32 atlasNormalMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, TextureFormat.ARGB32, MipMaps); atlasNormalMap.PackTextures(normalTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); } // Pack emission textures into atlas // TODO: Change this as packing not consistent Texture2D atlasEmissionMap = null; if (hasEmissionMaps) { // Repacking to ensure correct mix of lit and unlit atlasEmissionMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps); atlasEmissionMap.PackTextures(emissionTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); } // Add to results if (results.atlasRects == null) { results.atlasRects = new List <Rect>(rects.Length); } if (results.atlasIndices == null) { results.atlasIndices = new List <RecordIndex>(indices.Count); } results.atlasRects.AddRange(rects); results.atlasIndices.AddRange(indices); // Shrink UV rect to compensate for internal border float ru = 1f / atlasAlbedoMap.width; float rv = 1f / atlasAlbedoMap.height; int finalBorder = settings.borderSize + settings.atlasShrinkUVs; for (int i = 0; i < results.atlasRects.Count; i++) { Rect rct = results.atlasRects[i]; rct.xMin += finalBorder * ru; rct.xMax -= finalBorder * ru; rct.yMin += finalBorder * rv; rct.yMax -= finalBorder * rv; results.atlasRects[i] = rct; } // Store results results.albedoMap = atlasAlbedoMap; results.normalMap = atlasNormalMap; results.emissionMap = atlasEmissionMap; results.isAtlasAnimated = hasAnimation; results.isEmissive = hasEmissionMaps; return(results); }
/// <summary> /// Gets Unity textures from Daggerfall texture with all options. /// Returns all supported texture maps for Standard shader in one call. /// </summary> /// <param name="settings">Get texture settings.</param> /// <param name="alphaTextureFormat">Alpha TextureFormat.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <param name="allowImport">Import texture from disk if present.</param> /// <returns>GetTextureResults.</returns> public GetTextureResults GetTexture2D( GetTextureSettings settings, SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24, bool allowImport = true) { GetTextureResults results = new GetTextureResults(); // Check if window or auto-emissive bool isWindow = ClimateSwaps.IsExteriorWindow(settings.archive, settings.record); bool isEmissive = (settings.autoEmission) ? IsEmissive(settings.archive, settings.record) : false; // Override readable flag when user has set preference in material reader if (DaggerfallUnity.Instance.MaterialReader.ReadableTextures) { settings.stayReadable = true; } // Assign texture file TextureFile textureFile; if (settings.textureFile == null) { textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); } else { textureFile = settings.textureFile; } // Get starting DFBitmap and albedo Color32 array DFSize sz; DFBitmap srcBitmap = textureFile.GetDFBitmap(settings.record, settings.frame); Color32[] albedoColors = textureFile.GetColor32(srcBitmap, settings.alphaIndex, settings.borderSize, out sz); // Sharpen source image if (settings.sharpen) { albedoColors = ImageProcessing.Sharpen(ref albedoColors, sz.Width, sz.Height); } // Dilate edges if (settings.borderSize > 0 && settings.dilate && !settings.copyToOppositeBorder) { ImageProcessing.DilateColors(ref albedoColors, sz); } // Copy to opposite border if (settings.borderSize > 0 && settings.copyToOppositeBorder) { ImageProcessing.WrapBorder(ref albedoColors, sz, settings.borderSize); } // Set albedo texture Texture2D albedoMap = null; if (allowImport && TextureReplacement.CustomTextureExist(settings.archive, settings.record, settings.frame)) { // Import albedo texture albedoMap = TextureReplacement.LoadCustomTexture(settings.archive, settings.record, settings.frame); } else { // Create albedo texture if (settings.alphaIndex < 0) { albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(nonAlphaFormat), MipMaps); } else { albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); } albedoMap.SetPixels32(albedoColors); albedoMap.Apply(true, !settings.stayReadable); } // Set normal texture Texture2D normalMap = null; if (allowImport && TextureReplacement.CustomNormalExist(settings.archive, settings.record, settings.frame)) { // Always import normal if present on disk normalMap = TextureReplacement.LoadCustomNormal(settings.archive, settings.record, settings.frame); } else if (settings.createNormalMap && textureFile.SolidType == TextureFile.SolidTypes.None) { // Create normal texture - must be ARGB32 // Normal maps are bypassed for solid-colour textures Color32[] normalColors; normalColors = ImageProcessing.GetBumpMap(ref albedoColors, sz.Width, sz.Height); normalColors = ImageProcessing.ConvertBumpToNormals(ref normalColors, sz.Width, sz.Height, settings.normalStrength); normalMap = new Texture2D(sz.Width, sz.Height, TextureFormat.ARGB32, MipMaps); normalMap.SetPixels32(normalColors); normalMap.Apply(true, !settings.stayReadable); } // Import emission map or create basic emissive texture Texture2D emissionMap = null; bool resultEmissive = false; if (allowImport && TextureReplacement.CustomEmissionExist(settings.archive, settings.record, settings.frame)) { // Always import emission if present on disk emissionMap = TextureReplacement.LoadCustomEmission(settings.archive, settings.record, settings.frame); resultEmissive = true; } else { if (settings.createEmissionMap || (settings.autoEmission && isEmissive) && !isWindow) { // Just reuse albedo map for basic colour emission emissionMap = albedoMap; resultEmissive = true; } // Windows need special handling as only glass parts are emissive if ((settings.createEmissionMap || settings.autoEmissionForWindows) && isWindow) { // Create custom emission texture for glass area of windows Color32[] emissionColors = textureFile.GetWindowColors32(srcBitmap); emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); emissionMap.SetPixels32(emissionColors); emissionMap.Apply(true, !settings.stayReadable); resultEmissive = true; } // Lights need special handling as this archive contains a mix of emissive and non-emissive flats // This can cause problems with atlas packing due to mismatch between albedo and emissive texture counts if ((settings.createEmissionMap || settings.autoEmission) && settings.archive == LightsTextureArchive) { // For the unlit flats we create a null-emissive black texture if (!isEmissive) { Color32[] emissionColors = new Color32[sz.Width * sz.Height]; emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); emissionMap.SetPixels32(emissionColors); emissionMap.Apply(true, !settings.stayReadable); resultEmissive = true; } } } // Shrink UV rect to compensate for internal border float ru = 1f / sz.Width; float rv = 1f / sz.Height; results.singleRect = new Rect( settings.borderSize * ru, settings.borderSize * rv, (sz.Width - settings.borderSize * 2) * ru, (sz.Height - settings.borderSize * 2) * rv); // Store results results.albedoMap = albedoMap; results.normalMap = normalMap; results.emissionMap = emissionMap; results.isWindow = isWindow; results.isEmissive = resultEmissive; results.textureFile = textureFile; return(results); }
/// <summary> /// Gets Texture2D atlas from Daggerfall texture archive. /// Every record and frame in the archive will be added to atlas. /// An array of rects will be returned with sub-texture rect for each record index and frame. /// Currently supports one archive per atlas. Super-atlas packing (multiple archives) is in the works. /// </summary> /// <param name="settings">Get texture settings.</param> /// <param name="alphaTextureFormat">Alpha TextureFormat.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>GetTextureResults.</returns> public GetTextureResults GetTexture2DAtlas( GetTextureSettings settings, SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { GetTextureResults results = new GetTextureResults(); // Individual textures must remain readable to pack into atlas bool stayReadable = settings.stayReadable; settings.stayReadable = true; // Assign texture file TextureFile textureFile; if (settings.textureFile == null) { textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); settings.textureFile = textureFile; } else textureFile = settings.textureFile; // Create lists results.atlasSizes = new List<Vector2>(textureFile.RecordCount); results.atlasScales = new List<Vector2>(textureFile.RecordCount); results.atlasOffsets = new List<Vector2>(textureFile.RecordCount); results.atlasFrameCounts = new List<int>(textureFile.RecordCount); // Read every texture in archive bool hasNormalMaps = false; bool hasEmissionMaps = false; bool hasAnimation = false; List<Texture2D> albedoTextures = new List<Texture2D>(); List<Texture2D> normalTextures = new List<Texture2D>(); List<Texture2D> emissionTextures = new List<Texture2D>(); List<RecordIndex> indices = new List<RecordIndex>(); for (int record = 0; record < textureFile.RecordCount; record++) { // Get record index and frame count settings.record = record; int frames = textureFile.GetFrameCount(record); if (frames > 1) hasAnimation = true; // Get record information DFSize size = textureFile.GetSize(record); DFSize scale = textureFile.GetScale(record); DFPosition offset = textureFile.GetOffset(record); RecordIndex ri = new RecordIndex() { startIndex = albedoTextures.Count, frameCount = frames, width = size.Width, height = size.Height, }; indices.Add(ri); for (int frame = 0; frame < frames; frame++) { settings.frame = frame; GetTextureResults nextTextureResults = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat); albedoTextures.Add(nextTextureResults.albedoMap); if (nextTextureResults.normalMap != null) { normalTextures.Add(nextTextureResults.normalMap); hasNormalMaps = true; } if (nextTextureResults.emissionMap != null) { emissionTextures.Add(nextTextureResults.emissionMap); hasEmissionMaps = true; } } results.atlasSizes.Add(new Vector2(size.Width, size.Height)); results.atlasScales.Add(new Vector2(scale.Width, scale.Height)); results.atlasOffsets.Add(new Vector2(offset.X, offset.Y)); results.atlasFrameCounts.Add(frames); results.textureFile = textureFile; } // Pack albedo textures into atlas and get our rects Texture2D atlasAlbedoMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps); Rect[] rects = atlasAlbedoMap.PackTextures(albedoTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); // Pack normal textures into atlas Texture2D atlasNormalMap = null; if (hasNormalMaps) { // Normals must be ARGB32 atlasNormalMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, TextureFormat.ARGB32, MipMaps); atlasNormalMap.PackTextures(normalTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); } // Pack emission textures into atlas // TODO: Change this as packing not consistent Texture2D atlasEmissionMap = null; if (hasEmissionMaps) { // Repacking to ensure correct mix of lit and unlit atlasEmissionMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps); atlasEmissionMap.PackTextures(emissionTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); } // Add to results if (results.atlasRects == null) results.atlasRects = new List<Rect>(rects.Length); if (results.atlasIndices == null) results.atlasIndices = new List<RecordIndex>(indices.Count); results.atlasRects.AddRange(rects); results.atlasIndices.AddRange(indices); // Shrink UV rect to compensate for internal border float ru = 1f / atlasAlbedoMap.width; float rv = 1f / atlasAlbedoMap.height; int finalBorder = settings.borderSize + settings.atlasShrinkUVs; for (int i = 0; i < results.atlasRects.Count; i++) { Rect rct = results.atlasRects[i]; rct.xMin += finalBorder * ru; rct.xMax -= finalBorder * ru; rct.yMin += finalBorder * rv; rct.yMax -= finalBorder * rv; results.atlasRects[i] = rct; } // Store results results.albedoMap = atlasAlbedoMap; results.normalMap = atlasNormalMap; results.emissionMap = atlasEmissionMap; results.isAtlasAnimated = hasAnimation; results.isEmissive = hasEmissionMaps; return results; }
/// <summary> /// Gets Unity textures from Daggerfall texture with all options. /// Returns all supported texture maps for Standard shader in one call. /// </summary> /// <param name="settings">Get texture settings.</param> /// <param name="alphaTextureFormat">Alpha TextureFormat.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>GetTextureResults.</returns> public GetTextureResults GetTexture2D( GetTextureSettings settings, SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { GetTextureResults results = new GetTextureResults(); // Check if window or auto-emissive bool isWindow = ClimateSwaps.IsExteriorWindow(settings.archive, settings.record); bool isEmissive = (settings.autoEmission) ? IsEmissive(settings.archive, settings.record) : false; // Assign texture file TextureFile textureFile; if (settings.textureFile == null) textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); else textureFile = settings.textureFile; // Get starting DFBitmap and albedo Color32 array DFSize sz; DFBitmap srcBitmap = textureFile.GetDFBitmap(settings.record, settings.frame); Color32[] albedoColors = textureFile.GetColor32(srcBitmap, settings.alphaIndex, settings.borderSize, out sz); // Sharpen source image if (settings.sharpen) albedoColors = ImageProcessing.Sharpen(ref albedoColors, sz.Width, sz.Height); // Dilate edges if (settings.borderSize > 0 && settings.dilate && !settings.copyToOppositeBorder) ImageProcessing.DilateColors(ref albedoColors, sz); // Copy to opposite border if (settings.borderSize > 0 && settings.copyToOppositeBorder) ImageProcessing.WrapBorder(ref albedoColors, sz, settings.borderSize); // Create albedo texture Texture2D albedoMap = null; if (settings.alphaIndex < 0) albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(nonAlphaFormat), MipMaps); else albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); albedoMap.SetPixels32(albedoColors); albedoMap.Apply(true, !settings.stayReadable); // Create normal texture - must be ARGB32 // Normal maps are bypassed for solid-colour textures Texture2D normalMap = null; if (settings.createNormalMap && textureFile.SolidType == TextureFile.SolidTypes.None) { Color32[] normalColors; normalColors = ImageProcessing.GetBumpMap(ref albedoColors, sz.Width, sz.Height); normalColors = ImageProcessing.ConvertBumpToNormals(ref normalColors, sz.Width, sz.Height, settings.normalStrength); normalMap = new Texture2D(sz.Width, sz.Height, TextureFormat.ARGB32, MipMaps); normalMap.SetPixels32(normalColors); normalMap.Apply(true, !settings.stayReadable); } // Create basic emissive texture Texture2D emissionMap = null; bool resultEmissive = false; if (settings.createEmissionMap || (settings.autoEmission && isEmissive) && !isWindow) { // Just reuse albedo map for basic colour emission emissionMap = albedoMap; resultEmissive = true; } // Windows need special handling as only glass parts are emissive if ((settings.createEmissionMap || settings.autoEmissionForWindows) && isWindow) { // Create custom emission texture for glass area of windows Color32[] emissionColors = textureFile.GetWindowColors32(srcBitmap); emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); emissionMap.SetPixels32(emissionColors); emissionMap.Apply(true, !settings.stayReadable); resultEmissive = true; } // Lights need special handling as this archive contains a mix of emissive and non-emissive flats // This can cause problems with atlas packing due to mismatch between albedo and emissive texture counts if ((settings.createEmissionMap || settings.autoEmission) && settings.archive == LightsTextureArchive) { // For the unlit flats we create a null-emissive black texture if (!isEmissive) { Color32[] emissionColors = new Color32[sz.Width * sz.Height]; emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); emissionMap.SetPixels32(emissionColors); emissionMap.Apply(true, !settings.stayReadable); resultEmissive = true; } } // Shrink UV rect to compensate for internal border float ru = 1f / sz.Width; float rv = 1f / sz.Height; results.singleRect = new Rect( settings.borderSize * ru, settings.borderSize * rv, (sz.Width - settings.borderSize * 2) * ru, (sz.Height - settings.borderSize * 2) * rv); // Store results results.albedoMap = albedoMap; results.normalMap = normalMap; results.emissionMap = emissionMap; results.isWindow = isWindow; results.isEmissive = resultEmissive; results.textureFile = textureFile; return results; }
/// <summary> /// Gets specially packed tileset atlas for terrains. /// This needs to be improved to create each mip level manually to further reduce artifacts. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns></returns> public GetTextureResults GetTerrainTilesetTexture( int archive, bool stayReadable = false, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { const int atlasDim = 2048; const int gutterSize = 32; GetTextureResults results = new GetTextureResults(); // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); if (textureFile.RecordCount != 56) return results; // Rollout tiles into atlas. // This is somewhat inefficient, but fortunately atlases only // need to be generated once and can be prepared offline where // startup time is critical. int x = 0, y = 0; Color32[] atlasColors = new Color32[atlasDim * atlasDim]; for (int record = 0; record < textureFile.RecordCount; record++) { // Create base image with gutter DFSize sz; Color32[] albedo = textureFile.GetColor32(record, 0, -1, gutterSize, out sz); // Wrap and clamp textures based on tile switch (record) { // Textures that do not tile in any direction case 5: case 7: case 10: case 12: case 15: case 17: case 20: case 22: case 25: case 27: case 30: case 32: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 55: ImageProcessing.ClampBorder(ref albedo, sz, gutterSize); break; // Textures that clamp horizontally and tile vertically case 6: case 11: case 16: case 21: case 26: case 31: ImageProcessing.WrapBorder(ref albedo, sz, gutterSize, false); ImageProcessing.ClampBorder(ref albedo, sz, gutterSize, true, false); break; // Textures that tile in all directions default: ImageProcessing.WrapBorder(ref albedo, sz, gutterSize); break; } // Create variants Color32[] rotate = ImageProcessing.RotateColors(ref albedo, sz.Width, sz.Height); Color32[] flip = ImageProcessing.FlipColors(ref albedo, sz.Width, sz.Height); Color32[] rotateFlip = ImageProcessing.RotateColors(ref flip, sz.Width, sz.Height); // Insert into atlas ImageProcessing.InsertColors(ref albedo, ref atlasColors, x, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref rotate, ref atlasColors, x + sz.Width, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref flip, ref atlasColors, x + sz.Width * 2, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref rotateFlip, ref atlasColors, x + sz.Width * 3, y, sz.Width, sz.Height, atlasDim, atlasDim); // Increment position x += sz.Width * 4; if (x >= atlasDim) { y += sz.Height; x = 0; } } // Create Texture2D Texture2D albedoAtlas = new Texture2D(atlasDim, atlasDim, ParseTextureFormat(nonAlphaFormat), MipMaps); albedoAtlas.SetPixels32(atlasColors); albedoAtlas.Apply(true, !stayReadable); // Change settings for these textures albedoAtlas.wrapMode = TextureWrapMode.Clamp; albedoAtlas.anisoLevel = 8; // Store results results.albedoMap = albedoAtlas; return results; }
/// <summary> /// Import texture(s) used on models. /// </summary> /// <param name="archive">Archive index</param> /// <param name="record">Record index</param> /// <param name="frame">Texture frame</param> /// <param name="results">Texture Results</param> /// <param name="GenerateNormals">Will create normal map</param> static public void LoadCustomTextureResults(int archive, int record, int frame, ref GetTextureResults results, ref bool GenerateNormals) { // Main texture if (CustomTextureExist(archive, record, frame)) { results.albedoMap = LoadCustomTexture(archive, record, frame); } // Normal map if (CustomNormalExist(archive, record, frame)) { results.normalMap = LoadCustomNormal(archive, record, frame); GenerateNormals = true; } // Emission map // windowed walls use a custom emission map or stick with vanilla // non-window use the main texture as emission, unless a custom map is provided if (results.isEmissive) { if (CustomEmissionExist(archive, record, frame)) { // Import emission texture results.emissionMap = LoadCustomEmission(archive, record, frame); } else if (!results.isWindow && CustomTextureExist(archive, record, frame)) { // Reuse albedo map for basic colour emission results.emissionMap = results.albedoMap; } } else if (CustomEmissionExist(archive, record, frame)) { // Force emission map results.emissionMap = LoadCustomEmission(archive, record, frame); results.isEmissive = true; results.isWindow = false; } }