Unity & PS Linear Workflow - Unity 和 PS 的线性工作流实践 - 简单配置示例

本文详细探讨了在Unity中使用线性工作流时遇到的颜色显示问题,以及从Photoshop颜色配置、Unity纹理设置到自定义Shader的各种解决方案。重点比较了不同方案的优缺点,包括颜色空间配置、伽马校正、纹理导入设置和自定义Shader的实现,旨在找到兼顾3D渲染质量和UI美术工作流程的最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


目的

因为 新的 Unity 项目人物走写实PBR风格
所以铁定基于 Linear Workflow 比基于 Gamma Workflow 的渲染效果更好
但是 Linear Workflow 下对 美术工作流不太友好,下面就实验并总结一些方案的优缺点
供大家选取

先看看不同 Color Space 下的 PBR 选择差异有多大

在 Unity Linear Color Space 渲染质量接近 SP (Substance Painter) 等选择 DCC 软件的 PBR 渲染效果

先说明,我 SP 里没有重写 Shader,先不高度还原 Shader 效果,就看看 Linear 和 Gamma 下的差异都非常大

Linear Color Space 下的效果
在这里插入图片描述

Gamma Color Space 下的效果
在这里插入图片描述

可以看到 Gamma 下的头发,和其他非黑色部分的饱和度都不同了
在这里插入图片描述

可以看到,在 Unity Gamma Space 真的差别很大

至于为何有差别,可以参考我之前的一篇:Gamma Correction/Gamma校正/灰度校正/亮度校正(已更正) - 部分 DCC 中的线性工作流配置


问题

在 Linear Workflow 下3D渲染效果是好了好多

但是 UI特效 美术同学生产资源仍然是基于 Gamma Workflow(blog是在:2022/09/02写的,安装好的 Photoshop 默认都是在 Gamma Workflow 的颜色空间配置的,以后的 Photoshop 版本可能会变,等 sRGB 过时了,可能会变)

所以 基于 Gamma Workflow 生产出来的资源,放在 Unity Linear Workflow 下使用,肯定会有显示问题的


解决方案1


PS 颜色空间配置

我让 UI 特学给了一张 gamma 空间下制作的图
在这里插入图片描述

然后我将这张 Gamma 空间的图放到 调整好的颜色设置好之后的 PS 下,效果如下

(没有了 Gamma Correct,即:没有了 pow(inputVal, 2.2),感觉过亮,并且灰蒙蒙的,相当于Non-sRGB 下就是这样的效果)

在这里插入图片描述


Unity纹理设置

我将上面的 gamma.png 图,导出为:linear.png,然后将纹理的 sRGB 的勾去掉(即:sRGB=false)

bg.png 也是 sRGB = false
在这里插入图片描述

然后放两个图叠在一起,显示如下,和 Photoshop 中一模一样了

在这里插入图片描述


GIF效果对比

请添加图片描述


优缺点

  • 有点:效果还原度 100%
  • 缺点:PS色板色相不均衡、灰度丢失,而且如果你用PS的颜色吸管吸取 PS 颜色,你会发现不对,特别是吸取 PS 程序窗口意外的其他程序窗口内容的颜色值

下面细说缺点:

但是这种方式,UI 和 特效美术在使用 PS 生产过程中有些不方便的地方:

  • 如果是项目初期,直接使用这种工作流的方式还好
  • 如果是项目中途,那么需要将以往的所有色相,饱和度,都统统需要修改,这会让美术同学不可接受的
  • 而且,部分灰度,在 PS 的色板中也会丢失进度(即:部分灰度无法选择而使用)

在这里插入图片描述

在这里插入图片描述


解决方案2


PS gamma Correct 保持 2.2 & 灰度混合系数 1.0 勾上

在这里插入图片描述


Unity 然后纹理 sRGB = false 并使用自己的 CustomShader来做 Gamma Correct

在这里插入图片描述


CustomShader - UI/Default_Ext 源码

// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
// jave.lin : 2022/09/03 基于 unity 2020.3.37f1 的 builtshader 的 UI/Default Shader 修改而来

Shader "UI/Default_Ext"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
        // jave.lin : 自己添加一个是否需要使用 gamma correct 的开关
        [Toggle(GAMMA_CORRECT_ON)] _GammaCorrectOn ("GammaCorrectOn", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend One OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile_local _ UNITY_UI_CLIP_RECT
            #pragma multi_compile_local _ UNITY_UI_ALPHACLIP
            // jave.lin : 使用一个全局变体 keyword,便于外部脚本 Shader.EnabledKeyword() 的方式来全局控制
            #pragma multi_compile _ GAMMA_CORRECT_ON

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                float4  mask : TEXCOORD2;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float4 _MainTex_ST;
            float _UIMaskSoftnessX;
            float _UIMaskSoftnessY;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                float4 vPosition = UnityObjectToClipPos(v.vertex);
                OUT.worldPosition = v.vertex;
                OUT.vertex = vPosition;

                float2 pixelSize = vPosition.w;
                pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));

                float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
                float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
                OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
                OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));

                OUT.color = v.color * _Color;
                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = IN.color * (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);

                // jave.lin : 我们自己来添加 gamma correct
                #ifdef GAMMA_CORRECT_ON
                // jave.lin : 试过好几种数值,就这个是最接近的了

                // jave.lin : 写法1
                // // jave.lin : step1 : rgba 整体 pow(val, 2.2) 压暗
                // color.rgba = pow(color.rgba, 2.2);
                // // jave.lin : step2 : 单独对 a 通道 pow(val, 0.5) 曲线提升一些 alpha 值
                // color.a = pow(color.a, 1.0/2.0);

                // jave.lin : 写法2,在写法1基础上简化
                // color.rgb = pow(color.rgb, 2.2);
                // color.a = pow(color.a, 2.2 * 0.5);

                // jave.lin : 写法3,在写法2基础上,优化:充分利用 SIMD 4分量并行指令
                color.rgba = pow(color.rgba, half4(2.2, 2.2, 2.2, 2.2 * 0.5));

                #endif

                #ifdef UNITY_UI_CLIP_RECT
                half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
                color.a *= m.x * m.y;
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif
                
                color.rgb *= color.a;

                return color;
            }
        ENDCG
        }
    }
}


GIF效果对比

除了字体 alphablend 的稍微有些不同(这个到时再做实验,,已经做了式样,往下看)
请添加图片描述


字体的 AlphaBlend 同步

我们还是需要在:颜色设置中,将 用灰度系数混合文本颜色 设置为:1.0,这样 Unity 中的 Shader 不就用对字体的混合做调整了

在这里插入图片描述


修复前

可以看到 不透明度30% 差异有一些,70% 的差异就更大一些
在这里插入图片描述


修复后

修复后,Photoshop 和 Unity 中的效果是一致的
在这里插入图片描述


优缺点

  • 优点:这种方式,UI、特效美术 几乎是不用修改的 工作流的,只要将:alpha blend 1.0 勾上,而且对 PS 颜色吸管的功能可以保持原有功能效果,所以对美术工作流友好最大
  • 缺点:每个 UI 或是 特效的 Shader 都要添加 gamma correct(一个 pow运算),如果 overdraw 很多时会放大这个点的性能消耗,但是对于现代显卡来说,应该可以忽略不计

解决方案3

今天和美术大佬对了一下,他们上个项目使用了另一种方式,如下:

在PS制作过程中,确保下面两个选择开启:编辑器…/颜色设置…/高级控制

在这里插入图片描述
灰度系数 保留 2.2

在这里插入图片描述
注意我们新建文档制作时,确保 勾上 :用灰度系数混合 RGB 颜色的 勾选项

只有在 导出单个图层 的时候,去掉:用灰度系数混合 RGB 颜色的 勾选项

导出完毕后,继续制作 PSD 内容的时候,需要 再次 勾上:用灰度系数混合 RGB 颜色的 勾选项

总结三句话:

  • 平时制作时 确保 勾上 :用灰度系数混合 RGB 颜色的 勾选项
  • 导出单个图层时,去掉:用灰度系数混合 RGB 颜色的 勾选项
  • 导出整体效果图时,勾上:用灰度系数混合 RGB 颜色的 勾选项

“塌陷” 注意要领 - PS 的一些 BUG

另外导出贴图是需要 PS 处理几个点,应该是PS的塌陷的BUG:
(这里的塌陷包含:栅格化,合并图层,转为智能对象,合并可见)


透明 - 填充 不能使用,否则 alpha 值在导出的时候会衰减

图层的填充不能使用 否则会导致 PS 中的透明混合差异和 引擎中得 透明度混合差异很大
在这里插入图片描述


透明 - 使用不透明度

比如,如果要使用 20% 不透明度,我们只要 使用不透明度即可填充不可使用,否则也会导致 塌陷是 alpha值 衰减
在这里插入图片描述在这里插入图片描述


透明 - 单张图层导出是,确保 不透明度、填充度 都是 100%

最给要求美术导出的图层是 不透明度、填充度,都是 100% 的值才可以导出 单个图层,否则会有图层样式丢失,或是 alpha 衰减的问题
在这里插入图片描述


组合并不要使用

组合并也不能使用,同样会导致 alpha 衰减
在这里插入图片描述


Texture Importer sRGB 勾上

在 Texture 的 ImportSettings 中,保留 sRGB (使用硬件 gamma correct)
在这里插入图片描述


效果

发现 alphablend 也时还原度比较高的(如果细看还是会有一丢丢差异的,但是美术说比较方便制作,效果也在可接受范围)
在这里插入图片描述


解决方案4

  • 3D相机渲染使用 Framebuffer 的 color format 为:RGBA_HALF_16
    对应 unity 的 在这里插入图片描述(这个水印让我无语了)
    手动码一下:R16G16B16A16_SFloat

  • 2D UI 相机使用 Framebuffer 的 color format 为:在这里插入图片描述
    手动码一下:R8G8B8A8_sRGB

  • 将3D RT 和 2D RT混合输出到最终缓存区


Step1

3D 内容渲染到 R16G16B16A16_SFloat
在这里插入图片描述在这里插入图片描述


Step2

2D 内容渲染到 R8G8B8A8_sRGB
在这里插入图片描述在这里插入图片描述


Step3

3D RT 混合 2D RT
在这里插入图片描述

// jave.lin : 将3D层的 线性 RT 和 2D 伽马 RT 混合处理

using UnityEngine;

public class Blend3DAnd2DRT : MonoBehaviour
{
    public RenderTexture _3dTexture_Linear;
    public RenderTexture _2DTexture_Gamma;
    public RenderTexture _OutputTexture;
    public Material _blendMat;
    public Material _clearMat;

    public bool _create_SRGB_RT_via_Script;
    public Camera _2d_cam;

    private void Update()
    {
        //Graphics.Blit(_2DTexture_Gamma, _clearMat);
        _blendMat.SetTexture("_3dTexture_Linear", _3dTexture_Linear);
        _blendMat.SetTexture("_2DTexture_Gamma", _2DTexture_Gamma);
        Graphics.Blit(null, _OutputTexture, _blendMat);
        if (_create_SRGB_RT_via_Script)
        {
            if (_2DTexture_Gamma == null)
            {
                //_2DTexture_Gamma = new RenderTexture(Screen.width, Screen.height, 24, UnityEngine.Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat, 0);
                // jave.lin : 这里通过代码的方式来创建 SRGB,因为再 2020.2.5f1 版本不能再编辑器中对 RT 的 color format 使用 SRGB
                // 我自己亲测:在 2020.3.37f1 是可以的,但是由于我们项目使用的是 2020.2.5f1,所以这里使用 代码的方式来创建 SRGB RT 给 2D UI RT 使用
                _2DTexture_Gamma = new RenderTexture(Screen.width, Screen.height, 0, UnityEngine.Experimental.Rendering.GraphicsFormat.R8G8B8A8_SRGB, 0);
            }
            if (_2d_cam != null)
            {
                _2d_cam.targetTexture = _2DTexture_Gamma;
            }
        }
    }

    private void OnPreRender()
    {
        // jave.lin : 3D RT 混合 2D RT 处理

        // jave.lin : 调试用:清理 2D texture
        //Graphics.Blit(null, _2DTexture_Gamma, _clearMat);

        _blendMat.SetTexture("_3dTexture_Linear", _3dTexture_Linear);
        _blendMat.SetTexture("_2DTexture_Gamma", _2DTexture_Gamma);
        Graphics.Blit(null, _OutputTexture, _blendMat);
    }
}

// jave.lin : 混合 linear RT 和 gamma RT 的颜色

Shader "Test/BlendLinearAndGammaRT"
{
    Properties
    {
        _3dTexture_Linear ("_3dTexture_Linear", 2D) = "white" {}
        _2DTexture_Gamma ("_2DTexture_Gamma", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            sampler2D _3dTexture_Linear;
            sampler2D _2DTexture_Gamma;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 _3dCol = tex2D(_3dTexture_Linear, i.uv);
                fixed4 _2dCol = tex2D(_2DTexture_Gamma, i.uv);
                fixed4 finalCol;
                // jave.lin : 这里在 SRGB 的颜色混合到 SFloat 中
                // 前者 pow(colVal, 2.2) 硬件处理了,后者 pow(colVal, 1.0) 可以理解为伍 pow的 gamma 校正
                // 所以混合前,应该是需要 pow(colVal, 1.0 / 2.2) 的反 gamma 校正的
                // 具体效果,根据你们自己的项目来定夺

                 同步颜色空间来混合
                //finalCol.rgb = lerp(_3dCol.rgb, pow(_2dCol.rgb, 1.0 / 2.2), _2dCol.a);

                // 不同步颜色空间来混合
                finalCol.rgb = lerp(_3dCol.rgb, _2dCol.rgb, _2dCol.a);

                finalCol.a = 1;
                return finalCol;
            }
            ENDCG
        }
    }
}


优缺点

  • 优点:3D, 2D 的 渲染效果完全独立,2D 还是可以按照以前的制作工作流
  • 缺点:多了4~5此 blit 性能有所下降,并且,如果想要将 3D 内容直接用在 2D 相机中渲染的话,那么 3D 的内容渲染效果将会是异常的

总结

显而易见


想在 linear workflow 中,既要 有更好的 PBR 3D 光照效果,又要 有兼容就版本的 gamma workflow 的 UI 和 特效效果,那么最好使用:方案2,的方式,前提是:那么一丢丢的性能损耗是可接受的


如果 你们项目美术可以接受 方案1 的灰度,和色相范围丢失 的情况下,可以选用 方案1,shader 也不用处理 gamma correct


美术说比较方便制作,效果也在可接受范围,那就直接 方案3 (目前我们的项目使用的是这种工作流)


方案4,在了解该方案优缺点,来取舍,是否需要使用这个方案


Project

backup project(备份用)


References

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值