【ShaderGraph】Unity の Gradient 节点源码解析

1,050 阅读1分钟

前言

哈哈哈,此 “色色” 非彼 “色色”,而是 “梯度渐变色”。 最近在学习 Unity 的 Shader Graph,一时兴起想研究一下它的 采样梯度渐变Sample Gradient)节点是如何实现的,所以就开始翻它的源代码,顺便总结一下。

正文

  • ShaderGraph 的 Sample Gradient 节点 image.png

调用入口

hlsl:

SurfaceDescription PopulateSurfaceData(SurfaceDescriptionInputs IN)
{
    SurfaceDescription surface = (SurfaceDescription)0;
    /// @note 构造输入
    Gradient _Gradient_6E85A12E_Out_0 = NewGradient(0, 2, 2, float4(0.2403346, 0.4197932, 0.8113208, 0),float4(0.8867924, 0.1372018, 0.3612635, 1),float4(0, 0, 0, 0),float4(0, 0, 0, 0),float4(0, 0, 0, 0),float4(0, 0, 0, 0),float4(0, 0, 0, 0),float4(0, 0, 0, 0), float2(1, 0),float2(1, 1),float2(0, 0),float2(0, 0),float2(0, 0),float2(0, 0),float2(0, 0),float2(0, 0));
    /// @note 保存输出渐变色
    float4 _SampleGradient_B007B233_Out_2;
    Unity_SampleGradient_float(_Gradient_6E85A12E_Out_0, 0, _SampleGradient_B007B233_Out_2); ///< 见下方函数代码
    surface.Out_2 = _SampleGradient_B007B233_Out_2;
    return surface;
}

结合下方梯度结构的定义,可知本例实际只输入了 两个 颜色,即对两个颜色(float4(0.2403346, 0.4197932, 0.8113208, 0), float4(0.8867924, 0.1372018, 0.3612635, 1))进行插值,默认插值类型 type == 0

image.png

具体插值类型对应的 type 值如下

  • Blend(Classic): 0
  • Blend(Perceptual): 2
  • Fixed: 1

输入结构的定义

hlsl:

/// @note 定义渐变(梯度)色的结构体
struct Gradient
{
    int type; ///< 梯度(插值)类型
    int colorsLength; ///< 实际插值的颜色个数
    int alphasLength; ///< 实际插值的透明度个数
    float4 colors[8]; ///< 最多 8 个颜色进行插值(渐变)
    float2 alphas[8]; ///< 最多 8 个透明度进行插值(渐变)
};

/// @note 构造函数
Gradient NewGradient(int type, int colorsLength, int alphasLength,
    float4 colors0, float4 colors1, float4 colors2, float4 colors3, float4 colors4, float4 colors5, float4 colors6, float4 colors7,
    float2 alphas0, float2 alphas1, float2 alphas2, float2 alphas3, float2 alphas4, float2 alphas5, float2 alphas6, float2 alphas7)
{
    Gradient output =
    {
        type, colorsLength, alphasLength,
        {colors0, colors1, colors2, colors3, colors4, colors5, colors6, colors7},
        {alphas0, alphas1, alphas2, alphas3, alphas4, alphas5, alphas6, alphas7}
    };
    return output;
}

注意:透明度是二维的 float2

实际插值渐变色(核心)

hlsl:

void Unity_SampleGradient_float(Gradient Gradient, float Time, out float4 Out)
{
    float3 color = Gradient.colors[0].rgb; ///< 保存第 0 个颜色
    [unroll]
    for (int c = 1; c < 8; c++) ///< 对其余 7 个颜色进行插值
    {
        float colorPos = saturate((Time - Gradient.colors[c-1].w) / (Gradient.colors[c].w - Gradient.colors[c-1].w)) * step(c, Gradient.colorsLength-1);
        color = lerp(color, Gradient.colors[c].rgb, lerp(colorPos, step(0.01, colorPos), Gradient.type)); 
    }
#ifndef UNITY_COLORSPACE_GAMMA
    color = SRGBToLinear(color);
#endif
    ///@note 基本和上方代码一致
    float alpha = Gradient.alphas[0].x; ///< 保存第 0 个透明度,注意是 x 分量
    [unroll]
    for (int a = 1; a < 8; a++) ///< 对其余 7 个透明度进行插值
    {
        float alphaPos = saturate((Time - Gradient.alphas[a-1].y) / (Gradient.alphas[a].y - Gradient.alphas[a-1].y)) * step(a, Gradient.alphasLength-1); ///< 注意是用 y 分量进行计算
        alpha = lerp(alpha, Gradient.alphas[a].x, lerp(alphaPos, step(0.01, alphaPos), Gradient.type)); 
    }
    Out = float4(color, alpha);
}

本例在调用入口传入的两个颜色的 w 分量分别是 01,即本段代码的 Gradient.colors[c-1].w == 0 (下界)和 Gradient.colors[c-1].w == 1 (上界),

float colorPos = saturate((Time - Gradient.colors[c-1].w) / (Gradient.colors[c].w - Gradient.colors[c-1].w)) * step(c, Gradient.colorsLength-1);

那么 colorPos 会根据传入不同的 Time 而进行映射(默认值为 Time == 0)。

step(c, Gradient.colorsLength-1) 这句话考虑到实际颜色个数少于 8 的情况

从以上代码我们可以看到:

由于 本例 Gradient.type == 0,所以 lerp(colorPos, step(0.01, colorPos), Gradient.type) 实际效果等价于直接使用 colorPos

更进一步我们可以得到 color = lerp(color, Gradient.colors[c].rgb, colorPos),即最终结果的颜色是基于 colorPos 进行插值的

最后实际的效果如下所示:

sample_gradient.gif

Gradient 节点

Gradient 节点的定义实际上只是一个结构体,必须要配合 Sample Gradient 节点来使用

Gradient Unity_Gradient_float()
{
    Gradient g;
    g.type = 1;
    g.colorsLength = 4;
    g.alphasLength = 4;
    g.colors[0] = 0.1;
    g.colors[1] = 0.2;
    g.colors[2] = 0.3;
    g.colors[3] = 0.4;
    g.colors[4] = 0;
    g.colors[5] = 0;
    g.colors[6] = 0;
    g.colors[7] = 0;
    g.alphas[0] = 0.1;
    g.alphas[1] = 0.2;
    g.alphas[2] = 0.3;
    g.alphas[3] = 0.4;
    g.alphas[4] = 0;
    g.alphas[5] = 0;
    g.alphas[6] = 0;
    g.alphas[7] = 0;
    return g;
}

Gradient _Gradient = Unity_Gradient_float();

image.png