public static CustomModelColors GetCustomColors() { if (_defaultColors == null) { // Make a default entry if none was ever set. _defaultColors = new CustomModelColors(); } return(_defaultColors); }
public static void SetCustomColors(CustomModelColors c) { _defaultColors = c; }
/// <summary> /// Gets the texture maps for the model /// </summary> /// <returns>The texture maps in byte arrays inside a ModelTextureData class</returns> public static async Task <ModelTextureData> GetModelMaps(Tex tex, XivMtrl mtrl, CustomModelColors colors = null) { // Use static values as needed. if (colors == null) { colors = GetCustomColors(); } var shaderInfo = mtrl.GetShaderInfo(); var mtrlMaps = mtrl.GetAllMapInfos(); var texMapData = await GetTexMapData(tex, mtrl); var dimensions = await EqualizeTextureSizes(texMapData); var diffuseMap = new List <byte>(); var normalMap = new List <byte>(); var specularMap = new List <byte>(); var emissiveMap = new List <byte>(); var alphaMap = new List <byte>(); var diffuseColorList = new List <Color>(); var specularColorList = new List <Color>(); var emissiveColorList = new List <Color>(); byte[] diffusePixels = null, specularPixels = null, normalPixels = null; if (texMapData.Normal != null) { normalPixels = texMapData.Normal.Data; } if (texMapData.Diffuse != null) { diffusePixels = texMapData.Diffuse.Data; } if (texMapData.Specular != null) { specularPixels = texMapData.Specular.Data; } if (normalPixels == null && diffusePixels == null) { // This material doesn't actually have any readable data. var empty = new ModelTextureData { Width = 0, Height = 0, Normal = new byte[0], Diffuse = new byte[0], Specular = new byte[0], Emissive = new byte[0], Alpha = new byte[0], MaterialPath = mtrl.MTRLPath.Substring(mtrl.MTRLPath.LastIndexOf('/')) }; return(empty); } var dataLength = normalPixels != null ? normalPixels.Length : diffusePixels.Length; await Task.Run(() => { for (var i = 3; i < dataLength; i += 4) { // Load the individual pixels into memory. Color baseNormalColor = new Color(127, 127, 255, 255); Color baseDiffuseColor = new Color(255, 255, 255, 255); Color baseSpecularColor = new Color(255, 255, 255, 255); if (normalPixels != null) { baseNormalColor = new Color(normalPixels[i - 3], normalPixels[i - 2], normalPixels[i - 1], normalPixels[i]); } if (diffusePixels != null) { baseDiffuseColor = new Color(diffusePixels[i - 3], diffusePixels[i - 2], diffusePixels[i - 1], diffusePixels[i]); } if (specularPixels != null) { baseSpecularColor = new Color(specularPixels[i - 3], specularPixels[i - 2], specularPixels[i - 1], specularPixels[i]); } byte colorsetValue = baseNormalColor.A; // Calculate real colors from the inputs and shader. Color normalColor, diffuseColor, specularColor; byte opacity; ComputeShaderColors(colors, shaderInfo, baseNormalColor, baseDiffuseColor, baseSpecularColor, out normalColor, out diffuseColor, out specularColor, out opacity); Color alphaColor = new Color(opacity, opacity, opacity, opacity); // Apply colorset if needed. (This could really be baked into ComputeShaderColors) Color emissiveColor = new Color(0, 0, 0, 0); if (mtrl.ColorSetData.Count > 0) { var cs = texMapData.ColorSet.Data; Color finalDiffuseColor, finalSpecularColor; ComputeColorsetBlending(mtrl, colorsetValue, cs, diffuseColor, specularColor, out finalDiffuseColor, out finalSpecularColor, out emissiveColor); diffuseColor = finalDiffuseColor; specularColor = finalSpecularColor; } // White out the opacity channels where appropriate. diffuseColor.A = opacity; specularColor.A = 255; normalColor.A = 255; diffuseMap.AddRange(BitConverter.GetBytes(diffuseColor.ToRgba())); specularMap.AddRange(BitConverter.GetBytes(specularColor.ToRgba())); emissiveMap.AddRange(BitConverter.GetBytes(emissiveColor.ToRgba())); alphaMap.AddRange(BitConverter.GetBytes(alphaColor.ToRgba())); normalMap.AddRange(BitConverter.GetBytes(normalColor.ToRgba())); } }); var modelTextureData = new ModelTextureData { Width = dimensions.Width, Height = dimensions.Height, Normal = normalMap.ToArray(), Diffuse = diffuseMap.ToArray(), Specular = specularMap.ToArray(), Emissive = emissiveMap.ToArray(), Alpha = alphaMap.ToArray(), MaterialPath = mtrl.MTRLPath.Substring(mtrl.MTRLPath.LastIndexOf('/')) }; return(modelTextureData); }
private static void ComputeShaderColors(CustomModelColors colors, ShaderInfo info, Color baseNormal, Color baseDiffuse, Color baseSpecular, out Color newNormal, out Color newDiffuse, out Color newSpecular, out byte opacity) { // This is basically codifying this document: https://docs.google.com/spreadsheets/d/1kIKvVsW3fOnVeTi9iZlBDqJo6GWVn6K6BCUIRldEjhw/edit#gid=2112506802 opacity = 255; newNormal = baseNormal; newDiffuse = baseDiffuse; newSpecular = baseSpecular; // This var is technically defined in the Shaders parameters. // But we can use a constant copy of it for now, since it's largely non-changeable. const float PlayerColorMultiplier = 1.4f; const float BrightPlayerColorMultiplier = 3.0f; if (info.Shader == MtrlShader.Standard || info.Shader == MtrlShader.Glass) { // Common // Base color here is diffuse if we have one... if (info.Preset == MtrlShaderPreset.DiffuseSpecular) { // Has a raw diffuse. newDiffuse = baseDiffuse; newDiffuse.A = baseNormal.B; // Has a raw specular. newSpecular = baseSpecular; } else if (info.Preset == MtrlShaderPreset.DiffuseMulti) { // Has a raw diffuse. newDiffuse = baseDiffuse; // But we also have to modulate that diffuse color by the multi red channel. newDiffuse = MultiplyColor(newDiffuse, baseSpecular.R); newDiffuse.A = baseNormal.B; // Uses multi green/blue in some fashion. // We'll just show green for now. newSpecular = new Color(baseSpecular.G, baseSpecular.G, baseSpecular.G, (byte)255); } else { // Uses multi channel Red as a base/ao map. newDiffuse = new Color(baseSpecular.R, baseSpecular.R, baseSpecular.R, (byte)255); newDiffuse.A = baseNormal.B; // Uses multi green/blue in some fashion. // We'll just show green for now. newSpecular = new Color(baseSpecular.G, baseSpecular.G, baseSpecular.G, (byte)255); } // Normal is the same for all of them. newNormal = new Color(baseNormal.R, baseNormal.G, (byte)255, (byte)255); opacity = baseNormal.B; } else if (info.Shader == MtrlShader.Furniture || info.Shader == MtrlShader.DyeableFurniture) { // Furniture newDiffuse = new Color(baseDiffuse.R, baseDiffuse.G, baseDiffuse.B, (byte)255); if (info.Shader == MtrlShader.DyeableFurniture) { float colorInfluence = ByteToFloat(baseDiffuse.A); Color furnitureColor = MultiplyColor(colors.FurnitureColor, 1.0f); newDiffuse = Blend(baseDiffuse, furnitureColor, colorInfluence); } newSpecular = new Color(baseSpecular.G, baseSpecular.G, baseSpecular.G, (byte)255); newNormal = new Color(baseNormal.R, baseNormal.G, baseNormal.B, (byte)255); // This needs some more research all around } else if (info.Shader == MtrlShader.Skin) { newNormal = new Color(baseNormal.R, baseNormal.G, (byte)255, (byte)255); newSpecular = new Color(baseSpecular.G, baseSpecular.G, baseSpecular.G, (byte)255); opacity = 255; // This is an arbitrary number. There's likely some value in the shader params for skin that // tones down the specularity here, but without it the skin is hyper reflective. newSpecular = MultiplyColor(newSpecular, 0.25f); // New diffuse starts from regular diffuse file. // Then factors in the player's skin color multiplied by the shader value. float skinInfluence = ByteToFloat(baseSpecular.R); var coloredSkin = MultiplyColor(baseDiffuse, colors.SkinColor); newDiffuse = Blend(baseDiffuse, coloredSkin, skinInfluence); if (info.Preset == MtrlShaderPreset.Face) { // Face shaders also allow for lip color. var coloredLip = MultiplyColor(baseDiffuse, colors.LipColor); float lipInfluence = ByteToFloat(baseSpecular.B); newDiffuse = Blend(newDiffuse, coloredLip, lipInfluence); // For lipstick, increase the specular value slightly. float specAmp = 1.0f + (lipInfluence * 0.25f); newSpecular = MultiplyColor(newSpecular, specAmp); // Face shader supports alpha, unlike normal skin textures. opacity = baseNormal.B; } } else if (info.Shader == MtrlShader.Hair) { newNormal = new Color(baseNormal.R, baseNormal.G, (byte)255, (byte)255); newSpecular = new Color(baseSpecular.G, baseSpecular.G, baseSpecular.G, (byte)255); opacity = baseNormal.A; // The influence here determines which base color we use. float influenceStrength = ByteToFloat(baseSpecular.A); // Starting from the original hair color... var baseColor = MultiplyColor(colors.HairColor, 1.0f); // Hair highlight color if available. var targetColor = (Color)(colors.HairHighlightColor != null ? colors.HairHighlightColor : colors.HairColor); // But wait! If we're actually a tattoo preset, that changes instead to tattoo color. if (info.Preset == MtrlShaderPreset.Face) { targetColor = MultiplyColor(colors.TattooColor, 1.0f); } else if (info.Preset == MtrlShaderPreset.FaceBright) { // Multiplier here is 3.0 instead of 1.4 targetColor = MultiplyColor(colors.TattooColor, BrightPlayerColorMultiplier / PlayerColorMultiplier); } // This gets us our actual base color. baseColor = Blend(baseColor, targetColor, influenceStrength); // Now this needs to be straight multiplied with the multi channel red. newDiffuse = MultiplyColor(baseColor, baseSpecular.R); } else if (info.Shader == MtrlShader.Iris) { // Eyes newNormal = new Color(baseNormal.R, baseNormal.G, (byte)255, (byte)255); newSpecular = new Color(baseSpecular.G, baseSpecular.G, baseSpecular.G, (byte)255); opacity = baseNormal.A; // Base color is the selected eye color. var baseColor = colors.EyeColor; // Pretty sure some data is missing in here. // Catchlight is also not factored in atm. // Now this needs to be straight multiplied with the multi channel red. newDiffuse = MultiplyColor(baseColor, baseSpecular.R); } else { // Fall through just shows stuff as is. newNormal = baseNormal; newDiffuse = baseDiffuse; newSpecular = baseSpecular; } // Transparency filtering. if (!info.TransparencyEnabled) { opacity = (byte)(opacity < 128 ? 0 : 255); } }
/// <summary> /// Gets the customized texture map data for a model. /// Null custom model colors uses the defaults at ModelTexture.GetCustomColors(). /// </summary> /// <param name="gameDirectory"></param> /// <param name="mtrl"></param> /// <param name="colors"></param> /// <returns></returns> public static async Task <ModelTextureData> GetModelMaps(DirectoryInfo gameDirectory, XivMtrl mtrl, CustomModelColors colors = null) { var tex = new Tex(gameDirectory); return(await GetModelMaps(tex, mtrl)); }