public static void CompositeMips(Texture2D target, AlloyCustomImportObject source, AlloyTextureColorCache[] mapCache, AlloyTextureColorCache normalCache, int mipLevel) { // Basically a 1:1 port of the original shader // The only point of major difference is the filtering method used; which is a fraction simpler. // This was disabled, since it appears GetPixels results don't appear to be affected by Unity's messing with Linear inputs; the same way they do at runtime. Re-enable if you like. int w = Mathf.Max(2, target.width >> mipLevel); int h = Mathf.Max(2, target.height >> mipLevel); var colors = new Color[w * h]; float rangeX = (1.0f / (mipLevel + 1)) / target.width; float rangeY = (1.0f / (mipLevel + 1)) / target.height; UnityEngine.Profiling.Profiler.BeginSample("Composite mips"); for (int channelIndex = 0; channelIndex < source.PackMode.Channels.Count; channelIndex++) { var channel = source.PackMode.Channels[channelIndex]; var inIndices = channel.InputIndices.ToArray(); var outIndices = channel.OutputIndices.ToArray(); bool hasInputs = inIndices.Length > 0; for (int i = 0; i < outIndices.Length; ++i) { int storeIndex = outIndices[i]; var tex = mapCache[storeIndex]; var channelVal = source.ChannelValues[storeIndex]; if (hasInputs) { int readIndex = inIndices[Mathf.Min(i, inIndices.Length - 1)]; tex.SetActiveChannel(readIndex); } bool doInvert = source.DoInvert[storeIndex]; bool doNormal = channel.UseNormals && !normalCache.EmptyTexture; bool doNative = hasInputs && tex.NativeSize && mipLevel == 0; UnityEngine.Profiling.Profiler.BeginSample("Blit"); for (int x = 0; x < w; ++x) { for (int y = 0; y < h; ++y) { var pixelIndex = x + y * w; var input = 0.0f; if (!hasInputs || tex.EmptyTexture) { input = channelVal; } else if (doNative) { input = tex.ActiveChannel[pixelIndex]; } else { input = tex.GetChannelBilinear((float)x / (w - 1), (float)y / (h - 1), mipLevel, rangeX, rangeY); } if (doInvert) { input = 1.0f - input; } if (doNormal) { Vector3 normal; if (normalCache.NativeSize && mipLevel == 0) { normal = (Vector4)normalCache.Values[pixelIndex]; } else { normal = normalCache.GetPixelNormal((float)x / (w - 1), (float)y / (h - 1), mipLevel, rangeX, rangeY); } normal.x = (normal.x * 2.0f) - 1.0f; normal.y = (normal.y * 2.0f) - 1.0f; normal.z = (normal.z * 2.0f) - 1.0f; // Specular AA for Beckmann roughness. // cf http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf pg92 var variance = 0.0f; var avgNormalLength = normal.magnitude; var applyAA = avgNormalLength < 1.0f; if (applyAA) { float avgNormLen2 = avgNormalLength * avgNormalLength; float kappa = (3.0f * avgNormalLength - avgNormalLength * avgNormLen2) / (1.0f - avgNormLen2); variance = Mathf.Clamp01(1.0f / (2.0f * kappa));// - source.VarianceBias); } if (channel.OutputVariance) { input = variance; } else if (channel.RoughnessCorrect && applyAA) { float a = input * input; a = Mathf.Sqrt(Mathf.Clamp01(a * a + variance)); input = Mathf.Sqrt(a); } } switch (storeIndex) { case 0: colors[pixelIndex].r = input; break; case 1: colors[pixelIndex].g = input; break; case 2: colors[pixelIndex].b = input; break; case 3: colors[pixelIndex].a = input; break; } } } UnityEngine.Profiling.Profiler.EndSample(); } } UnityEngine.Profiling.Profiler.BeginSample("Set pixels"); target.SetPixels(colors, mipLevel); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.EndSample(); }
/// <summary> /// Generates the packed material map for an object /// </summary> public static void GeneratePackedMaterialMap(AlloyCustomImportObject settings, Texture2D target, string filePath) { var size = settings.GetOutputSize(); var normalMap = settings.NormalMapTexture; var useUnityGeneratedMipmaps = normalMap == null; int width = (int)size.x; int height = (int)size.y; int mipmapCount = 1; // When explicitly generating mip levels pick output count based on the largest input texture. if (!useUnityGeneratedMipmaps) { mipmapCount = GetMipmapCount(normalMap); for (int i = 0; i < 4; ++i) { if (settings.SelectedModes[i] != TextureValueChannelMode.Texture || settings.GetTexture(i) == null) { continue; } mipmapCount = Math.Max(mipmapCount, GetMipmapCount(settings.GetTexture(i))); } } // Adjust the dimensions of the output texture if necessary. if (target.width != width || target.height != height) { target.Resize(width, height); } if (!Mathf.IsPowerOfTwo(width) || !Mathf.IsPowerOfTwo(height)) { Debug.LogWarning( "Alloy: Texture resolution is not power of 2; will have issues generating correct mip maps if custom sizing is specified in generated texture platform settings."); } // Get readable input textures. var readableNormal = AlloyTextureReader.GetReadable(normalMap, true); var readableTextures = new Texture2D[settings.TexturesGUID.Length]; for (int i = 0; i < settings.TexturesGUID.Length; ++i) { if (settings.SelectedModes[i] != TextureValueChannelMode.Texture) { continue; } var settingsTex = settings.GetTexture(i); if (settingsTex == null) { readableTextures[i] = null; } else { readableTextures[i] = AlloyTextureReader.GetReadable(settingsTex, false); } } // Use renderer to sample mipmaps. try { var message = string.Format("Packing: \"{0}\"", settings.name); var progress = 1.0f; var bodyText = message; for (int mipLevel = 0; mipLevel < mipmapCount; mipLevel++) { if (mipmapCount > 1) { progress = (float)mipLevel / (mipmapCount - 1); bodyText = string.Format("{0} ({1})", message, progress.ToString("0%")); } EditorUtility.DisplayProgressBar("Building Packed maps...", bodyText, progress); // CPU Method - more reliable/consistent across GPUs, but slower. var normalCache = new AlloyTextureColorCache(readableNormal, target); UnityEngine.Profiling.Profiler.BeginSample("Read"); var texCache = readableTextures.Select(tex => new AlloyTextureColorCache(tex, target)).ToArray(); UnityEngine.Profiling.Profiler.EndSample(); AlloyPackerCompositor.CompositeMips(target, settings, texCache, normalCache, mipLevel); } } finally { EditorUtility.ClearProgressBar(); } // Clean up the readable textures. foreach (var texture in readableTextures) { Object.DestroyImmediate(texture); } Object.DestroyImmediate(readableNormal); // Update the texture's associated settings .asset object. settings.Width = width; settings.Height = height; settings.MaxResolution = 0; EditorUtility.SetDirty(settings); // Update the texture object. target.Apply(useUnityGeneratedMipmaps, false); }
/// <summary> /// Generates the packed material map for an object /// </summary> public static void GeneratePackedMaterialMap(AlloyCustomImportObject settings, Texture2D target, string filePath) { int mipmapCount = 1; Vector2 size = settings.GetOutputSize(); int width = (int)size.x; int height = (int)size.y; // Pick output texture dimensions based on the largest input texture. for (int i = 0; i < 4; ++i) { if (settings.SelectedModes[i] != TextureValueChannelMode.Texture || settings.GetTexture(i) == null) { continue; } mipmapCount = Math.Max(mipmapCount, GetMipmapCount(settings.GetTexture(i))); } bool doMips; if (settings.NormalMapTexture != null) { var tex = settings.NormalMapTexture; var count = GetMipmapCount(tex); mipmapCount = Math.Max(mipmapCount, count); doMips = true; } else { mipmapCount = 1; doMips = false; } if (target.width != width || target.height != height) { target.Resize(width, height); } if (!Mathf.IsPowerOfTwo(width) || !Mathf.IsPowerOfTwo(height)) { Debug.LogWarning( "Alloy: Texture resolution is not power of 2; will have issues generating correct mip maps if custom sizing is specified in generated texture platform settings."); } var readableTextures = new Texture2D[settings.TexturesGUID.Length]; for (int i = 0; i < settings.TexturesGUID.Length; ++i) { if (settings.SelectedModes[i] != TextureValueChannelMode.Texture) { continue; } var settingsTex = settings.GetTexture(i); if (settingsTex == null) { readableTextures[i] = null; } else { readableTextures[i] = AlloyTextureReader.GetReadable(settingsTex, false); } } var normal = AlloyTextureReader.GetReadable(settings.NormalMapTexture, true); try { // Use renderer to sample mipmaps. for (int mipLevel = 0; mipLevel < mipmapCount; mipLevel++) { // CPU Method - more reliable/consistent across GPUs, but slower. if (mipmapCount > 1) { EditorUtility.DisplayProgressBar("Calculating Mip Maps...", "MipLevel " + mipLevel, (float)mipLevel / mipmapCount); } else { EditorUtility.DisplayProgressBar("Calculating Packed map...", "Packing...", 1.0f); } var normalCache = new AlloyTextureColorCache(normal, target); Profiler.BeginSample("Read"); var texCache = readableTextures.Select(tex => new AlloyTextureColorCache(tex, target)).ToArray(); Profiler.EndSample(); AlloyPackerCompositor.CompositeMips(target, settings, texCache, normalCache, mipLevel); } } finally { EditorUtility.ClearProgressBar(); } foreach (var texture in readableTextures) { Object.DestroyImmediate(texture); } Object.DestroyImmediate(normal); int maxResolution = 0; settings.Width = width; settings.Height = height; settings.MaxResolution = maxResolution; EditorUtility.SetDirty(settings); // Tells Unity to save changes to the settings .asset object on disk target.Apply(!doMips, false); }