目的
因为 新的 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,在了解该方案优缺点,来取舍,是否需要使用这个方案文章来源:https://www.toymoban.com/news/detail-404461.html
Project
backup project(备份用)文章来源地址https://www.toymoban.com/news/detail-404461.html
- Testing_UI_Default_CustomShaderGammaCorrect_in_Linear_workflow
- 2D_RT_BLEND_TO_3D_RT_unity_2020.3.37f1_BRP
References
- Gamma Correction/Gamma校正/灰度校正/亮度校正(已更正) - 部分 DCC 中的线性工作流配置
到了这里,关于Unity & PS Linear Workflow - Unity 和 PS 的线性工作流实践 - 简单配置示例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!