Unity - gamma space下还原linear space效果

文章目录

  • 环境
  • 目的
  • 环境
  • 问题
  • 实践结果
  • 处理要点
    • 处理细节
      • 【OnPostProcessTexture 实现 sRGB 2 Linear 编码】 - 预处理
      • 【封装个简单的 *.cginc】 - shader runtime
      • 【shader需要gamma space下还原记得 #define _RECOVERY_LINEAR_IN_GAMMA】
      • 【颜色参数应用前 和 颜色贴图采样后】
      • 【灯光颜色】
      • 【F0应用(绝缘体正对视角下的反射率)】
      • 【BRDF BRDF1_Unity_PBS 不适用gamma调整】
      • 【自发光颜色处理】
      • 【雾效颜色】
      • 【FBO的color处理Linear 2 sRGB的后处理】
      • 【预处理阶段处理所有材质里面的所有 color 遍历处理(工具化,注意:可能不便于维护)】
    • Cubemap texture to linear
  • Project
  • References


环境

unity : 2023.3.37f1
pipeline : BRP


之前写过一篇: Gamma Correction/Gamma校正/灰度校正/亮度校正 - 部分 DCC 中的线性工作流配置,此文,自己修修改改不下于 50次,因为以前很多概念模糊

如果发现还有错误,请指出来,谢谢


目的

处理H5使用 WEB GL 1.0 的问题
因为项目要移植到 H5 WEB GL 1.0 的 graphics api
而因为我们之前的项目配置,使用的是 color space : linear
然后在H5平台中,如果使用 linear 的话,unity 会提示,只支持 WEB GL 2.0
而WEB GL 2.0 无论是 unity,微信,还是 浏览器,都是处于 BETA 阶段
甚至iOS或是 mac 下,直接不支持 (因为苹果要推他的 web metal,但是以前的 web gl 1.0 他是支持的)

因此为了设备兼容率,我们只能将 linear 转为 gamma

但是颜色空间不同的话,其实最大差异就是 sRGB 贴图颜色 和 最后后处理的 gamma校正 的处理
还有比较隐蔽的一些颜色相关的常量值 (比如PBR中的 绝缘体的 F0 常量值,等等)
还有灯光颜色,材质颜色,等


环境

unity : 2020.3.37f1
pipeline : BRP


问题

正常 Linear space 和 gamma space 下渲染差异如下:
在这里插入图片描述
在这里插入图片描述


实践结果

如下图,我目前对比了 linear 和 gamma 下的渲染区别
并且在 gamma space 下,尽可能的还原了 linear space 的效果
其中人物的衣服渲染算是还原了
这里头还有很多需要还原的:

  • skybox (cubemap ,这类 cube 还不能通过 SetPixels 设置值,会有报错)
  • 皮肤
  • 后处理的所有颜色

请添加图片描述

下面是又是后续处理了皮肤
还有头发之后的 (头发没有处理完整,因为使用 ASE 连连看练出来的,使用 surface shader,虽然可以生成一下 vert, frag 的方式在修改,但是我懒得去修改了,这样就是 PBR 的 BRDF 里面的部分曲线是不一样的,所以可以看到头发有一些差异)

(剩下一些: cubemap 的贴图部分没有没有还原,这部分后续再想想方案)
请添加图片描述


处理要点

  1. 所有颜色贴图 (注意不是数据贴图)的 RGB 通道需要处理 预处理的 sRGB 2 Linear - 性能高一些,毕竟是预处理
  2. 或者是不在预处理阶段,而是改在: shader sample 后的 pow(tex_color, 2.2) - 会比较浪费性能,但是如果为了快速出效果,或是验证,这是不二之选
  3. 所有shading时,材质 (shahder program)传入的颜色相关参数都需要在 shading 前做 pow(color, 2.2)
  4. 也可以在预处理阶段处理所有材质里面的所有 color 遍历处理(工具化)
  5. 所有shading 结束后,增加一个 postprocess 后处理,将屏幕的所有颜色处理 Linear 2 sRGB

处理细节

【OnPostProcessTexture 实现 sRGB 2 Linear 编码】 - 预处理

在 AssetPostProcessor 中的 OnPostProcessTexture 回调用处理 Texture2D 的资源
其中 Texture2D 只包含, Texture2D, Sprite 的回调处理

注意:如果是 Cubemap 的纹理,unity是不会回调进这个函数的
而且 cubemap 的问题,我一直没想好怎么处理

还要注意,如果实现了预处理贴图,就不要在 shader runtime 对 sample 后的颜色贴图做 sRGB 2 Linear 了

    private static void GammaSpace_Non_HDR_TexPP_Handler(Texture2D texture)
    {
        for (int mipmapIDX = 0; mipmapIDX < texture.mipmapCount; mipmapIDX++)
        {
            Color[] c = texture.GetPixels(mipmapIDX);

            for (int i = 0; i < c.Length; i++)
            {
                c[i] = c[i].linear;
            }
            texture.SetPixels(c, mipmapIDX);
        }
    }
    private static bool NeedToRemoveGammaCorrect(string assetPath)
    {
        TextureImporter ti = AssetImporter.GetAtPath(assetPath) as TextureImporter;
        return NeedToRemoveGammaCorrect(ti);
    }
    // jave.lin : 是否需要删除 gamma correct
    private static bool NeedToRemoveGammaCorrect(TextureImporter ti)
    {
        if (ti == null) return false;
        // jave.lin : 没开启
        if (PlayerPrefs.GetInt("Enabled_GammaSpaceTexPP", 0) == 0) return false;

        // jave.lin : linear color space 下不处理,gamma color space 下才处理
        if (QualitySettings.activeColorSpace == ColorSpace.Linear) return false;

        // jave.lin : 原来 linear 下,不是 sRGB 不用处理
        if (ti.sRGBTexture == false) return false;
        return true;
    }
    private void OnPostprocessTexture(Texture2D texture)
    {
        Debug.Log($"OnPostprocessTexture.assetPath:{assetPath}");

        if (NeedToRemoveGammaCorrect(assetPath))
        {
            GammaSpace_Non_HDR_TexPP_Handler(texture);
        }
    }

代码太多,我只罗列出关键要修改的 PBR 着色的地方要修改的地方


【封装个简单的 *.cginc】 - shader runtime

注意:如果使用了 OnPostProcessTexture 实现 sRGB 2 Linear 编码 的预处理,就不要处理 shader runtime 里面的 sample 后的 COLOR_TRANS 或是 CHANGED_COLOR 处理

#ifndef __CUSTOM_COLOR_SPACE_VARS_H__
#define __CUSTOM_COLOR_SPACE_VARS_H__

// jave.lin 2024/01/17
// custom the color space const & vars

#define unity_ColorSpaceGrey1 fixed4(0.214041144, 0.214041144, 0.214041144, 0.5)
#define unity_ColorSpaceDouble1 fixed4(4.59479380, 4.59479380, 4.59479380, 2.0)
#define unity_ColorSpaceDielectricSpec1 half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%)
#define unity_ColorSpaceLuminance1 half4(0.0396819152, 0.458021790, 0.00609653955, 1.0) // Legacy: alpha is set to 1.0 to specify linear mode

#if defined(UNITY_COLORSPACE_GAMMA) && defined(_RECOVERY_LINEAR_IN_GAMMA)
// jave.lin : force using linear effect
#define __FORCE_LINEAR_EFFECT__
#endif

#ifdef __FORCE_LINEAR_EFFECT__
    // sRGB to Linear    
    #define COLOR_TRANS(col) pow(col, 2.2)
    #define CHANGED_COLOR(col) (col = pow(col, 2.2));
    // const defines
    #define GREY_COLOR (unity_ColorSpaceGrey1)
    #define DOUBLE_COLOR (unity_ColorSpaceDouble1)
    #define DIELECTRIC_SPEC_COLOR (unity_ColorSpaceDielectricSpec1)
    #define LUMINANCE_COLOR (unity_ColorSpaceLuminance1)
#else
    // sRGB to Linear  
    #define COLOR_TRANS(col) (col)
    #define CHANGED_COLOR(col) 
    // const defines - gamma space
    #define GREY_COLOR (unity_ColorSpaceGrey)
    #define DOUBLE_COLOR (unity_ColorSpaceDouble)
    #define DIELECTRIC_SPEC_COLOR (unity_ColorSpaceDielectricSpec)
    #define LUMINANCE_COLOR (unity_ColorSpaceLuminance)
#endif

#endif

【shader需要gamma space下还原记得 #define _RECOVERY_LINEAR_IN_GAMMA】

`#define _RECOVERY_LINEAR_IN_GAMMA`

【颜色参数应用前 和 颜色贴图采样后】

half4 Albedo1(float4 texcoords)
{
    //return _Color * tex2D(_MainTex, texcoords.xy);
    //return _Color * tex2Dbias(_MainTex, float4(texcoords.xy, 0.0, UNITY_ACCESS_INSTANCED_PROP(Props, _MainTex_mipmapBias)));
    half4 __color = _Color; // jave.lin : if this color is HDR color, unnesscessory to do sRGB to Linear
    half4 __tex_color = tex2D(_MainTex, texcoords.xy);
    //CHANGED_COLOR(__color.rgb)
    CHANGED_COLOR(__tex_color.rgb)
    return __color * __tex_color;
}

【灯光颜色】

    UnityLight mainLight = MainLight();
    CHANGED_COLOR(mainLight.color.rgb) // jave.lin : gamma correct light color

【F0应用(绝缘体正对视角下的反射率)】

使用我们自己定义的 DIELECTRIC_SPEC_COLOR

inline half OneMinusReflectivityFromMetallic1(half metallic)
{
    // We'll need oneMinusReflectivity, so
    //   1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
    // store (1-dielectricSpec) in DIELECTRIC_SPEC_COLOR.a, then
    //   1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
    //                  = alpha - metallic * alpha
    half oneMinusDielectricSpec = DIELECTRIC_SPEC_COLOR.a;
    return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}

inline half3 DiffuseAndSpecularFromMetallic1(half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
{
    specColor = lerp(DIELECTRIC_SPEC_COLOR.rgb, albedo, metallic);
    oneMinusReflectivity = OneMinusReflectivityFromMetallic1(metallic);
    return albedo * oneMinusReflectivity;
}

FragmentCommonData1 MetallicSetup1(half3 albedo, fixed2 metallicGloss)
{
    half metallic = metallicGloss.x;
    half smoothness = metallicGloss.y; // this is 1 minus the square root of real roughness m.

    half oneMinusReflectivity;
    half3 specColor;
    // half3 diffColor = DiffuseAndSpecularFromMetallic(Albedo(i_tex), metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
    half3 diffColor = DiffuseAndSpecularFromMetallic1(albedo, metallic, /*out*/specColor, /*out*/oneMinusReflectivity);

    FragmentCommonData1 o = (FragmentCommonData1) 0;
    o.diffColor = diffColor;
    o.specColor = specColor;
    o.oneMinusReflectivity = oneMinusReflectivity;
    o.smoothness = smoothness;
    return o;
}

【BRDF BRDF1_Unity_PBS 不适用gamma调整】

注释掉下面代码

//#ifdef UNITY_COLORSPACE_GAMMA
//        specularTerm = sqrt(max(1e-4h, specularTerm)); // jave.lin : if you want to recovery linear result in gamma space, don't do this one
//#endif

【自发光颜色处理】

    // jave.lin : emission
    half3 emission_col = Emission(i.tex.xy);
    CHANGED_COLOR(emission_col.rgb)
    c.rgb += emission_col.rgb;

【雾效颜色】

    CHANGED_COLOR(unity_FogColor.rgb)
    UNITY_EXTRACT_FOG_FROM_EYE_VEC(i);
    UNITY_APPLY_FOG(_unity_fogCoord, c.rgb);
    return OutputForward(c, s.alpha);

【FBO的color处理Linear 2 sRGB的后处理】

csharp monobehaviour 如下

// jave.lin : 2024/01/08
// testing linear to gamma (linear to srgb)

using UnityEngine;

[ExecuteInEditMode]
public class LinearToGammaPP : MonoBehaviour
{
    public Color backgroundColor;
    public Shader shader;
    private Material material;
    private Camera cam;

    private bool InLinearColorSpace()
    {
        return QualitySettings.activeColorSpace == ColorSpace.Linear;
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(cam == null) cam = GetComponent<Camera>();

        cam.backgroundColor = InLinearColorSpace() ? backgroundColor : backgroundColor.linear;

        if (InLinearColorSpace())
        {
            Graphics.Blit(source, destination);
            return;
        }

        if (material == null)
        {
            material = new Material(shader);
        }
        Graphics.Blit(source, destination, material);
    }

    private void OnDestroy()
    {
        if(material != null)
        {
            if (Application.isPlaying)
                Object.Destroy(material);
            else
                Object.DestroyImmediate(material);
        }
    }
}

shader 如下

// jave.lin 2024/01/08 postprocess for linear 2 sRGB

Shader "Hidden/LinearToGamma"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                #if defined(UNITY_COLORSPACE_GAMMA)
                col.rgb = pow(col.rgb, 1.0/2.2);
                //col.rgb = pow(col.rgb, 2.2);
                #endif
                return col;
            }
            ENDCG
        }
    }
}

【预处理阶段处理所有材质里面的所有 color 遍历处理(工具化,注意:可能不便于维护)】

处理要点里面,我提过:“也可以在预处理阶段处理所有材质里面的所有 color 遍历处理(工具化)”

但是要注意:如果shadering 里面对上线材质传递的颜色参数,二次修改为 pow(color, 2.2) 的值,这种方式,虽然渲染能成功,但是对于要记住 shader 中,哪些颜色参数是预处理过的,是需要维护成本的

因此不建议使用,但是如果你想要优化极致性能,那么可以考虑使用这种方式,代码如下:

// jave.lin 2024/01/08
// 將所有的SRGB color 转到 linear 下

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class MaterialSrgbToLinearTool
{
    //[MenuItem("Tools/TestBuildAB")]
    //public static void TestBuildAB()
    //{
    //    var bundles = new AssetBundleBuild[1];
    //    bundles[0] = new AssetBundleBuild
    //    {
    //        assetBundleName = "sg_noise_ccs_124.jpg",
    //        assetNames = new string[] { "Assets/Art/Effects/Textures/Textures/sg_noise_ccs_124.jpg" }
    //    };

    //    BuildPipeline.BuildAssetBundles("Bundle", bundles, BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.Android);
    //}

    //public static void SelectedSrgbImageToLinearImage()
    //{
    //    foreach (string guid in Selection.assetGUIDs)
    //    {
    //        string assetPath = AssetDatabase.GUIDToAssetPath(guid);
    //        TextureImporter ti = AssetImporter.GetAtPath(assetPath) as TextureImporter;
    //        if (ti == null)
    //            continue;
    //        if (ti.sRGBTexture == false)
    //            continue;

    //    }
    //}
    public const string TransformedLabel = "AllColorProp_Has_sRGB2Linear";
    public static List<string> label_list_helper = new List<string>();
    private static bool HandleSingleMatRes_sRGB2Linear(Material mat)
    {
        try
        {
            // method 1 : iterating all color props values
            //SerializedObject serializedObject = new SerializedObject(mat);
            //SerializedProperty prop = serializedObject.GetIterator();
            //while (prop.NextVisible(true))
            //{
            //    if (prop.propertyType == SerializedPropertyType.Color)
            //    {
            //        Color colorValue = prop.colorValue;
            //        Debug.Log("Color property " + prop.name + " value: " + colorValue);
            //    }
            //}

            // method 2 : iterating m_SavedProperties/m_Colors props values
            var so = new SerializedObject(mat);
            var sp = so.FindProperty("m_SavedProperties");
            for (int j = 0; j < sp.FindPropertyRelative("m_Colors").arraySize; j++)
            {
                var elementProp = sp.FindPropertyRelative("m_Colors").GetArrayElementAtIndex(j);
                var fistElement = elementProp.FindPropertyRelative("first");
                var secondElement = elementProp.FindPropertyRelative("second");
                //Debug.Log($"{fistElement.stringValue}, r:{secondElement.colorValue.r},g:{secondElement.colorValue.g},b:{secondElement.colorValue.b},a:{secondElement.colorValue.a}");
                var col = secondElement.colorValue;
                float maxComponent = Mathf.Max(col.r, col.g, col.b);
                if (maxComponent > 1.0f)
                {
                    // hdr
                    //Debug.Log($"maxComponent: {maxComponent}");
                    Debug.Log($"{fistElement.stringValue} is HDR color.");
                    float npot = Mathf.Max(Mathf.NextPowerOfTwo((int)maxComponent), 1.0f);
                    //Debug.Log($"npot: {npot}");
                    Color linearColor = new Color(col.r / npot, col.g / npot, col.b / npot, col.a).linear;
                    //Debug.Log($"linearColor: {linearColor}");
                    secondElement.colorValue = linearColor * new Color(npot, npot, npot, 1.0f);
                    //Debug.Log($"finalColor: {secondElement.colorValue}");
                }
                else
                {
                    // ldr
                    secondElement.colorValue = secondElement.colorValue.linear;
                }
            }
            so.ApplyModifiedPropertiesWithoutUndo();
            return true;
        }
        catch (System.Exception er)
        {
            Debug.LogError(er);
            return false;
        }
    }
    private static bool HandleSingleMatRes_Recovery_sRGB2Linear(Material mat)
    {
        try
        {
            // method 1 : iterating all color props values
            //SerializedObject serializedObject = new SerializedObject(mat);
            //SerializedProperty prop = serializedObject.GetIterator();
            //while (prop.NextVisible(true))
            //{
            //    if (prop.propertyType == SerializedPropertyType.Color)
            //    {
            //        Color colorValue = prop.colorValue;
            //        Debug.Log("Color property " + prop.name + " value: " + colorValue);
            //    }
            //}

            // method 2 : iterating m_SavedProperties/m_Colors props values
            var so = new SerializedObject(mat);
            var sp = so.FindProperty("m_SavedProperties");
            for (int j = 0; j < sp.FindPropertyRelative("m_Colors").arraySize; j++)
            {
                var elementProp = sp.FindPropertyRelative("m_Colors").GetArrayElementAtIndex(j);
                var fistElement = elementProp.FindPropertyRelative("first");
                var secondElement = elementProp.FindPropertyRelative("second");
                //Debug.Log($"{fistElement.stringValue}, r:{secondElement.colorValue.r},g:{secondElement.colorValue.g},b:{secondElement.colorValue.b},a:{secondElement.colorValue.a}");
                var col = secondElement.colorValue;
                float maxComponent = Mathf.Max(col.r, col.g, col.b);
                if (maxComponent > 1.0f)
                {
                    // hdr
                    //Debug.Log($"maxComponent: {maxComponent}");
                    Debug.Log($"{fistElement.stringValue} is HDR color.");
                    float npot = Mathf.Max(Mathf.NextPowerOfTwo((int)maxComponent), 1.0f);
                    //Debug.Log($"npot: {npot}");
                    Color linearColor = new Color(col.r / npot, col.g / npot, col.b / npot, col.a).gamma;
                    //Debug.Log($"linearColor: {linearColor}");
                    secondElement.colorValue = linearColor * new Color(npot, npot, npot, 1.0f);
                    //Debug.Log($"finalColor: {secondElement.colorValue}");
                }
                else
                {
                    // ldr
                    secondElement.colorValue = secondElement.colorValue.gamma;
                }
            }
            so.ApplyModifiedPropertiesWithoutUndo();
            return true;
        }
        catch (System.Exception er)
        {
            Debug.LogError(er);
            return false;
        }
    }
    public static bool AddLabel(AssetImporter ai, string adding_label)
    {
        var assetPasth = ai.assetPath;
        GUID guid = new GUID(AssetDatabase.AssetPathToGUID(assetPasth));
        var labels = AssetDatabase.GetLabels(guid);
        label_list_helper.Clear();
        label_list_helper.AddRange(labels);
        if (!label_list_helper.Contains(adding_label))
        {
            label_list_helper.Add(adding_label);
            AssetDatabase.SetLabels(ai, label_list_helper.ToArray());
            return true;
        }
        return false;
    }
    public static bool RemoveLabel(AssetImporter ai, string removing_label)
    {
        var assetPasth = ai.assetPath;
        GUID guid = new GUID(AssetDatabase.AssetPathToGUID(assetPasth));
        var labels = AssetDatabase.GetLabels(guid);
        label_list_helper.Clear();
        label_list_helper.AddRange(labels);
        if (label_list_helper.Remove(removing_label))
        {
            label_list_helper.Sort();
            AssetDatabase.SetLabels(ai, label_list_helper.ToArray());
            return true;
        }
        return false;
    }

    [MenuItem("Tools/Materials/sRGB2LinearAllMatColorProps")]
    public static void sRGB2LinearAllMatColorProps()
    {
        try
        {
            var guids = AssetDatabase.FindAssets("t:Material");
            for (int i = 0; i < guids.Length; i++)
            {
                var guid = guids[i];

                var cancacle = EditorUtility.DisplayCancelableProgressBar(
                    "Transforming Material Color Props : sRGB to Linear",
                    $"{i + 1}/{guids.Length}",
                    (float)(i + 1) / guids.Length);

                if (cancacle)
                {
                    Debug.Log($"Transforming Material Color Props : sRGB to Linear is cancacled! Handled : {i}/{guids.Length}");
                    break;
                }

                var assetPath = AssetDatabase.GUIDToAssetPath(guid);
                //if (assetPath != "Assets/Art/Effects/Materials/New/UI_sg_kapaizhujiemian_tianfui_02.mat")
                //    continue;
                AssetImporter ai = AssetImporter.GetAtPath(assetPath);
                var labels = AssetDatabase.GetLabels(ai);
                if (System.Array.IndexOf(labels, TransformedLabel) >= 0)
                {
                    continue;
                }

                var mat = AssetDatabase.LoadAssetAtPath<Material>(assetPath);
                if (mat == null) continue;

                Debug.Log($"Transforming Material Color Props, mat path : {assetPath}");

                HandleSingleMatRes_sRGB2Linear(mat);

                if (AddLabel(ai, TransformedLabel))
                {
                    Debug.Log($"Tranforming Material Color Props, mat path : {assetPath}, added the Label : {TransformedLabel}");
                }
                else
                {
                    Debug.LogWarning($"Tranforming Material Color Props, mat path : {assetPath}, alreading exsit the Label : {TransformedLabel}");
                }
            }
            Debug.Log($"Transforming Material Color Props : sRGB to Linear is completed!");
        }
        catch (System.Exception er)
        {
            Debug.LogError(er);
        }
        finally
        {
            EditorUtility.ClearProgressBar();
        }
    }

    [MenuItem("Tools/Materials/Recovery_sRGB2LinearAllMatColorProps")]
    public static void Recovery_sRGB2LinearAllMatColorProps()
    {
        try
        {
            var guids = AssetDatabase.FindAssets("t:Material");
            for (int i = 0; i < guids.Length; i++)
            {
                var guid = guids[i];

                var cancacle = EditorUtility.DisplayCancelableProgressBar(
                    "Transforming Material Color Props : sRGB to Linear",
                    $"{i + 1}/{guids.Length}",
                    (float)(i + 1) / guids.Length);

                if (cancacle)
                {
                    Debug.Log($"Transforming Material Color Props : sRGB to Linear is cancacled! Handled : {i}/{guids.Length}");
                    break;
                }

                var assetPath = AssetDatabase.GUIDToAssetPath(guid);
                //if (assetPath != "Assets/Art/Effects/Materials/New/UI_sg_kapaizhujiemian_tianfui_02.mat")
                //    continue;
                AssetImporter ai = AssetImporter.GetAtPath(assetPath);
                var labels = AssetDatabase.GetLabels(ai);
                if (System.Array.IndexOf(labels, TransformedLabel) == -1)
                {
                    continue;
                }

                var mat = AssetDatabase.LoadAssetAtPath<Material>(assetPath);
                if (mat == null) continue;

                Debug.Log($"Recoverying Material Color Props, mat path : {assetPath}");

                HandleSingleMatRes_Recovery_sRGB2Linear(mat);

                if (RemoveLabel(ai, TransformedLabel))
                {
                    Debug.Log($"Recoverying Material Color Props, mat path : {assetPath}, has remove Label : {TransformedLabel}");
                }
                else
                {
                    Debug.LogWarning($"Recoverying Material Color Props, mat path : {assetPath}, not found the Label : {TransformedLabel}");
                }
            }
            Debug.Log($"Transforming Material Color Props : sRGB to Linear is completed!");
        }
        catch (System.Exception er)
        {
            Debug.LogError(er);
        }
        finally
        {
            EditorUtility.ClearProgressBar();
        }
    }
}


Cubemap texture to linear

其实就是要对一些 HDR 贴图做 sRGB to Linear 的处理
HDR color 我们知道是: HDR_COLOR = color_normalized * pow(2, intensity)

因此我们只要算出 NextPowerOfTwo 就可以还原出 color_normalizedpow(2, intensity) ,就可以重新编码颜色

但是 HDR texture 的话,我们也尝试这种编码处理方式,但是会有 Unsupported GraphicsFormat(130) for SetPixel operations. 的错误,如下图:
在这里插入图片描述

CSHARP 代码中,我们看到代码没什么问题,但是 unity Cubemap 中不提供正确的 API 调用

    // jave.lin : 处理 HDR 的纹理
    // Cubemap.SetPixels 有异常: Unsupported GraphicsFormat(130) for SetPixel operations.
    // 通过 baidu, google 搜索得知,可以通过 un-compressed 格式 (比如:RGB(A)16,24,32,64)来避免这个问题
    // 但是会导致贴图内存增加很多(谨慎使用),因此只能代码中处理这部分的srgb to linear
    private static void GammaSpace_HDR_TexPP_Handler(Cubemap cubemap)
    {
        var max_val = -1f;
        for (int faceIDX = 0; faceIDX < CubemapFaceIterateArray.Length; faceIDX++)
        {
            var face = CubemapFaceIterateArray[faceIDX];
            // jave.lin : 获取第 0 层 mipmap 的 max value
            Color[] colos_mipmap0 = cubemap.GetPixels(face, 0);
            for (int i = 0; i < colos_mipmap0.Length; i++)
            {
                var c = colos_mipmap0[i];

                var temp_max_val = Mathf.Max(c.r, c.g, c.b);
                if (temp_max_val > max_val)
                {
                    max_val = temp_max_val;
                }
            }
        }

        Debug.Log($"max_val : {max_val}");

        if (max_val <= 1.0f)
        {
            Debug.Log($"max_val <= 1.0f, non-HDR srgb to lienar, max_val : {max_val}");
            // jave.lin : 将 gamma space 下的 srgb to linear
            for (int faceIDX = 0; faceIDX < CubemapFaceIterateArray.Length; faceIDX++)
            {
                var face = CubemapFaceIterateArray[faceIDX];
                for (int mipmapIDX = 0; mipmapIDX < cubemap.mipmapCount; mipmapIDX++)
                {
                    Color[] colors_mipmap = cubemap.GetPixels(face, mipmapIDX);

                    for (int i = 0; i < colors_mipmap.Length; i++)
                    {
                        colors_mipmap[i] = colors_mipmap[i].linear;
                    }
                    // jave.lin : Unsupported GraphicsFormat(130) for SetPixel operations.
                    cubemap.SetPixels(colors_mipmap, face, mipmapIDX);
                }
            }
        }
        else
        {
            //var assetPath = AssetDatabase.GetAssetPath(cubemap);
            //Debug.LogWarning($"不是HDR贴图不用处理, assetPath : {assetPath}");
            // jave.lin : 计算 next power of two (npot)
            var npot = (float)Mathf.Max(Mathf.NextPowerOfTwo((int)max_val), 1.0f);
            Debug.Log($"max_val > 1.0f, HDR srgb to lienar, max_val : {max_val}, npot : {npot}");

            // jave.lin : 将 gamma space 下的 srgb to linear
            for (int faceIDX = 0; faceIDX < CubemapFaceIterateArray.Length; faceIDX++)
            {
                var face = CubemapFaceIterateArray[faceIDX];
                for (int mipmapIDX = 0; mipmapIDX < cubemap.mipmapCount; mipmapIDX++)
                {
                    Color[] colors_mipmap = cubemap.GetPixels(face, mipmapIDX);

                    for (int i = 0; i < colors_mipmap.Length; i++)
                    {
                        var c = colors_mipmap[i];
                        c = new Color(c.r / npot, c.g / npot, c.b / npot, c.a).linear;
                        c *= new Color(npot, npot, npot, 1.0f);
                        colors_mipmap[i] = c;
                    }
                    // jave.lin : Unsupported GraphicsFormat(130) for SetPixel operations.
                    cubemap.SetPixels(colors_mipmap, face, mipmapIDX);
                }
            }
        }
    }
    private static void OnPostprocessCubemapEXT(string assetPath, Cubemap cubemap)
    {
        Debug.Log($"OnPostprocessCubemapEXT.assetPath:{assetPath}");

        TextureImporter ti = AssetImporter.GetAtPath(assetPath) as TextureImporter;
        // jave.lin : 修改 readable (这一步风险有点大),会导致 主存、显存 都有一份 内存
        if (ti.isReadable == false)
        {
            Debug.Log($"assetPath:{assetPath}, changing readable true");
            ti.isReadable = true;
            ti.SaveAndReimport();
            return;
        }

        GammaSpace_HDR_TexPP_Handler(cubemap);
    }
    private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        foreach (var path in importedAssets)
        {
            if (NeedToRemoveGammaCorrect(path))
            {
                Debug.Log($"OnPostprocessAllAssets.assetPath:{path}");

                //var tex2D = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
                //var tex = AssetDatabase.LoadAssetAtPath<Texture>(path);
                var cubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(path);
                // jave.lin : 下面输出:
                /*
                imported asset path: Assets/Scene/UiEffectScene/ReflectionProbe-0.exr, tex2D : , tex :ReflectionProbe-0 (UnityEngine.Cubemap), cubemap: ReflectionProbe-0 (UnityEngine.Cubemap)
                UnityEngine.Debug:Log(object)
                */
                //Debug.Log($"imported asset path: {path}, tex2D : {tex2D}, tex :{tex}, cubemap: {cubemap}");

                if (cubemap == null) continue;

                OnPostprocessCubemapEXT(path, cubemap);
            }
        }
    }

其实上面的代码判断 是否有分量 > 1.0f 的方式来判断是否 HDR 是不太合理的,因为不同的贴图格式的编码方式不同

有一些编码比如,RGBM,使用 A 通道来保存 255 被缩放的数值,作为: color_normalized * pow(2, A_channel_normalized * 255) 来解码

百度,谷歌上也没有搜索到对应的回答,唯一搜索到类似的:unity报错篇-Unsupported texture format - needs to be ARGB32。。。。

如果 使用了 带压缩格式的,然后再使用 Cubemap.SetPixels 都会报这个错误
在这里插入图片描述

注意压缩后大小非常小,才 288B 字节 (我这个是测试用的纹理)
在这里插入图片描述

然后我们将其格式修改成 未压缩 格式,就没有这个报错了
在这里插入图片描述

但是大小会比原来的大4倍
在这里插入图片描述

本身H5里面的内存就是很珍贵的设备资源,因此这种方式不可取
那么只能牺牲一些性能,在 shader 代码中采样处理了
比如: skybox对cubemap的处理,或是 reflection probe 等 IBL 反射效果 的 颜色的 pow(val, 2.2) 的处理


Project

  • Testing_Recovery_Linear_shading_in_UnityGammaSpace_2020.3.37f1_BRP.rar - 里面带有一些 逆向学习用的资源,不能公开
  • Testing_Recovery_Linear_shading_in_UnityGammaSpace_2020.3.37f1_BRP_V2.rar - 同上

References

  • gamma下还原linear效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/348227.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

接口自动化测试实践

众所周知&#xff0c;接口自动化测试有着如下特点&#xff1a; 低投入&#xff0c;高产出。 比较容易实现自动化。 和UI自动化测试相比更加稳定。 如何做好一个接口自动化测试项目呢&#xff1f; 我认为&#xff0c;一个“好的”自动化测试项目&#xff0c;需要从“时间”…

【算法练习Day51】柱状图中最大的矩形

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 柱状图中最大的矩形思路动态…

HTML+CSS:飞翔按钮

效果演示 实现了一个按钮的动画效果&#xff0c;当鼠标悬停在按钮上时&#xff0c;按钮的背景颜色和图标会发生变化&#xff0c;并且图标会旋转45度并向右移动1.2em&#xff0c;同时按钮中的文字也会向右移动5em。当鼠标点击按钮时&#xff0c;按钮会变小并向下移动0.1em。整个…

软考复习之软件工程篇

软件生命周期 问题定义&#xff1a;要示系统分析员与用户进行交流&#xff0c;弄清”用户需要计算机解决什么问题”然后提出关于“系统目标与范围的说明”&#xff0c;提交用户审查和确认 可行性研究&#xff1a;一方面在于把待开发的系统的目标以明确的语言描述出来&#xf…

LINUX服务之YUM仓库

1. YUM概述 YUM基于RPM包构建的软件更新机制 可以自动解决依赖关系 所有软件包由集中的YUM软件仓库提供 YUM支持软件源 搭建yum支持的的软件源主要有以下三种&#xff1a; 本地yum&#xff1a;file&#xff1a;//… 网络yum&#xff0c;又分为HTTP服务器&#xff1a;http…

Vue3 watch与watchEffect区别

✨ 专栏介绍 在当今Web开发领域中&#xff0c;构建交互性强、可复用且易于维护的用户界面是至关重要的。而Vue.js作为一款现代化且流行的JavaScript框架&#xff0c;正是为了满足这些需求而诞生。它采用了MVVM架构模式&#xff0c;并通过数据驱动和组件化的方式&#xff0c;使…

从全流程的角度来了解python包的使用,也许你会有不一样的认识

在python中&#xff0c;只要我们一谈到包或模块&#xff0c;基本默认说的就是包的导入和使用。也就是说只要我们知道包的名字&#xff0c;导入后知道怎么使用基本就可以了&#xff0c;但本人认为&#xff0c;我们仅仅了解的是包的一部分&#xff0c;若想对包有个整体的认识&…

376. 摆动序列 - 力扣(LeetCode)

题目描述 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为摆动序列。第一个差&#xff08;如果存在的话&#xff09;可能是正数或负数。少于两个元素的序列也是摆动序列。 例如&#xff0c; [1,7,4,9,2,5] 是一个摆动序列&#xff0c;因为差值 (6,…

【机器学习300问】15、什么是逻辑回归模型?

一、逻辑回归模型是为了解决什么问题&#xff1f; 逻辑回归&#xff08;Logistic Regression&#xff09;是一种广义线性回归分析模型&#xff0c;尤其适用于解决二分类问题&#xff08;输出为两个类别&#xff09;。 &#xff08;1&#xff09;二分类举例 邮件过滤&#xff…

详解BLDC和PMSM的特点

文章目录 前言BLDC和PMSM的优点基础架构前言 在电机领域中,有刷电机和无刷电机代表着两种不同的技术路径。有刷电机的绕组通常位于转子,即电机的旋转部分。 而无刷电机则采用一种更为先进的设计,其绕组安置在定子,即电机的静止部分。 这样的设计理念在于将绕组固定在电机的…

深入理解stress/stress-ng

文章目录 一、概述二、安装2.1、源码编译安装2.2、命令行安装2.3、安装确认 三、重要参数详解3.1、查询支持的参数3.2、重要参数说明 四、实例4.1、压测CPU4.2、压测内存4.3、压测IO4.4、压测磁盘及IO4.5、压测磁盘及CPU 团队博客: 汽车电子社区 一、概述 stress是一种工作负载…

【AIGC】Diffusers:AutoPipeline自动化扩散生图管道

前言 &#x1f917; 扩散器能够完成许多不同的任务&#xff0c;并且您通常可以将相同的预训练权重用于多个任务&#xff0c;例如文本到图像、图像到图像和修复。但是&#xff0c;如果您不熟悉库和扩散模型&#xff0c;可能很难知道将哪个管道用于任务。例如&#xff0c;如果您…

新闻界的AI革命:Newspager GPT 全面解析

简介有没有想过一家报社是如何运作的&#xff1f;传统的报社要有策划、采编、编辑、美工、审校等等角色&#xff0c;而现在借助 AI&#xff0c;很多事情可以由 AI 代替了&#xff01;Newspager GPT 就是这样一个由多智能体组成的 AI 系统&#xff0c;你只要输入几个你感兴趣的主…

Javaweb之SpringBootWeb案例之阿里云OSS服务入门的详细解析

2.3.2 入门 阿里云oss 对象存储服务的准备工作我们已经完成了&#xff0c;接下来我们就来完成第二步操作&#xff1a;参照官方所提供的sdk示例来编写入门程序。 首先我们需要来打开阿里云OSS的官方文档&#xff0c;在官方文档中找到 SDK 的示例代码&#xff1a; 参照官方提供…

基于 Gurobi 的纸浆运载船顺序装卸决策建模求解|Gurobi优化应用

Pulp-Carrier-Loading-Optimization-with-Gurobi 基于 Gurobi 的纸浆运载船顺序装卸决策建模求解。中山大学智能工程学院《运筹学》课程期末建模课程设计。优化工具&#xff1a;Python的Gurobi 项目仓库 Github: Pulp-Carrier-Loading-Optimization-with-Gurobi 摘要 本研究…

E4 基于Mysql的游标定义和应用

一、实验目的: 熟练使用MySQL游标的定义和应用。 二、实验要求: 1、基本硬件配置:英特尔Pentium III 以上,大于4G内存&#xff1b; 2、软件要求:Mysql&#xff1b; 3、时间:1小时&#xff1b; 4、撰写实验报告并按时提交。 三、实验内容: 问题1&#xff1a;请写一个存储…

快速打通 Vue 3(五):详解 Vue 中的路由

08. 路由 很激动进入了 Vue 3 的学习&#xff0c;作为一个已经上线了三年多的框架&#xff0c;很多项目都开始使用 Vue 3 来编写了 这一组文章主要聚焦于 Vue 3 的新技术和新特性 如果想要学习基础的 Vue 语法可以看我专栏中的其他博客 Vue&#xff08;一&#xff09;&#xff…

蓝桥杯备战——5.动态数码管扫描

1.分析原理图 经查阅说明书得知数码管为共阳极&#xff0c;共阳端口接到了U8,而段码接到了U7。 如果需要选中U8,我们只需要将P250;P261;P271; 如果需要选中U7,我们只需要将P251;P261;P271; 2.代码示例 void Delay1ms() //12.000MHz {unsigned char data i, j;i 12;j 169;…

贪心算法-01:跳跃游戏

关于贪心算法 贪心算法是动态规划的一个特例&#xff0c;相对于动态规划&#xff0c;使用贪心算法需要满足更多条件&#xff0c;但是效率比动态规划要高。 贪心选择的性质就是&#xff1a;每一步都做出一个局部最优解&#xff0c;最终的结果就是全局最优。不过这是一种特殊性…

uniapp组件库中Collapse 折叠面板 的使用方法

目录 #平台差异说明 #基本使用 #控制面板的初始状态&#xff0c;以及是否可以操作 #自定义样式 #1. 如果修改展开后的内容&#xff1f; #2. 如何自定义标题的样式&#xff1f; #3. 如何修改整个Item的样式&#xff1f; #API #Collapse Props #Collapse Item Props #…