- 音视频
- 图形学
- 桌面应用
【免费下载链接】t3
TiXL is an open source software to create realtime motion graphics.
WaveForm 是 TiXL(Lib.image.analyze 库)中用于可视化图像颜色与亮度分布的分析算子,它在一张画面上同时叠加 Waveform(亮度波形)与 Vectorscope(矢量色域示波器)两种专业监视工具,被广泛用于色彩校正、调色检查与实时影像监看。读完本文,你将掌握 WaveForm 的全部输入参数与默认值、它背后的 HLSL 计算着色器实现原理,以及如何把它接入 TiXL 节点图做实时视频分析。
算子定位与适用场景
WaveForm 属于 TiXL 的算子库 Lib.image.analyze,该库汇总了图像分析类算子,其中与 WaveForm 直接相关的同类工具有:
- ImageLevels:同样用于可视化图像亮度分布(直方图式);
- CompareImages:通过可移动分界线对比处理前后的两张图像;
- GetImageBrightness:把二维输入的亮度直接输出为 float 值。
WaveForm 的独特之处在于:它不是把亮度信息导出成数值,而是把亮度波形与矢量色域示波器以叠加层的形式直接渲染到图像上,让你在实时画面中同时观察"亮度分布是否合理"与"色相/饱和度是否偏移"。正如原文档所说,这类叠加层在色彩校正中有极高的实用价值,国内外大量调色教程都依赖这类示波器判断画面问题。
在实际项目中,WaveForm 常与颜色处理算子(如 ColorGrade)配合使用:先用 ColorGrade 调整色彩,再用 WaveForm 监看调整结果是否过曝、欠曝或偏色。在示例工程 HowToUseColors.t3(如何用色的教学示例)中,WaveForm 被直接实例化并开启了矢量示波器(ShowVectorscope: true),说明它在 TiXL 官方示例中就是作为颜色教学与监看工具出现的。
输入参数详解(含源码默认值)
原文档给出了算子的全部输入参数,本节结合 WaveForm.t3 中实际保存的DefaultValue,补充每个参数的默认值与取值范围说明,方便直接照着调参:
| 参数名(相关性 & 类型) | 说明 | 默认值(来自 WaveForm.t3) |
|---|---|---|
| EffectTexture(Texture2D) | 待分析的输入图像(纹理),可直接连接任意图像/视频输出 | 空(需手动连接) |
| Height(Single) | 波形显示区域的高度比例,决定亮度波形在画面中的纵向占位 | 0.5 |
| Opacity(Single) | 波形与矢量示波器叠加层的整体不透明度(0~1),越低越接近"半透明监看" | 0.14 |
| DimBackground(Single) | 背景压暗强度,数值越高,原画面越暗,示波器叠加层越突出 | 0.5 |
| ColorIntensity(Single) | 波形中红/绿/蓝三通道分量的着色强度,值越大颜色越浓 | 0.15 |
| ShowVectorscope(Boolean) | 是否显示矢量示波器(色域圆环)叠加层 | false |
| EnlargeVectorScopeCenter(Single) | 放大矢量示波器中心区域的显示强度,便于观察低饱和度/暗部区域的色相分布 | 0.5 |
| OverrideSize(Int2) | 覆盖内部渲染缓冲的尺寸;默认(-1, 0)表示自动使用输入纹理尺寸 | X: -1, Y: 0 |
| VectorScopePosition(Vector2) | 矢量示波器叠加层在画面中的位置(归一化坐标) | X: 1.17, Y: 0.113 |
对应的 C# 实现位于 WaveForm.cs,每个输入都通过InputSlot<T>声明并带有固定的 Guid,类型与上表一一对应(如Height为InputSlot<float>、ShowVectorscope为InputSlot<bool>、OverrideSize为InputSlot<Int2>、VectorScopePosition为InputSlot<System.Numerics.Vector2>)。从源码可以看出,这些参数会被内部节点图收集后统一写入计算着色器的常量缓冲区(cbufferParamConstants),下一节将详细说明它们如何影响渲染。
输出参数
| 名称 | 类型 |
|---|---|
| ImgOutput | T3.Core.DataTypes.Texture2D |
ImgOutput是在 WaveForm.cs 中声明的Slot<Texture2D>输出,携带叠加层后的最终纹理。从 WaveForm.t3 的连接关系看,ImgOutput直接连接自内部RenderTarget(8e078c2f节点)的输出,即"背景压暗 → 波形 / 示波器叠加 → 输出"的最终合成结果。
内部实现原理:从节点图到 HLSL
WaveForm 不是一个"魔法黑盒",而是一个用 TiXL 节点图组装出来的复合算子。从 WaveForm.t3 的Children列表可以清晰还原它的内部工作流:
亮度波形(Waveform)的生成链路
- 纹理读取与格式准备:
SrvFromTexture2d(026eb5b0)把输入纹理包装成 SRV,SamplerState(042a96e3)使用 Wrap 寻址模式;内部Texture2d(c2a6f2da)声明了一块R16G16B16A16_Float、256×256、绑定ShaderResource | RenderTarget | UnorderedAccess的中间缓冲; - 计算着色器统计:
ComputeShader(b105eba2)加载着色器源Lib:shaders/img/analyze/waveform-cs.hlsl,由ComputeShaderStage(7cb6fbd3)以128×128×1的 Dispatch 规模执行;UavFromTexture2d(5d07eac9)把中间缓冲作为 UAV 传入; - 画线绘制:
ListToBuffer→DrawLines(064843a8等)把统计结果以线条形式绘制出来,线条颜色为白色(W: 0.016)或深色(W: 0.36),形成波形网格。
矢量示波器(Vectorscope)的生成链路
- 色域参考图:
LoadImage(e8208f66)加载Lib:shaders/img/analyze/vectorscope.png(仓库中确实存在该 512×512 参考图,见 Operators/Lib/Assets/shaders/img/analyze); - 色点采样:
ComputeShader(f5a958a0)加载Lib:shaders/img/analyze/sample-vectorscope-points-cs.hlsl,配合RadialPoints(生成圆环点列)与RadialGradient(80e08043,内建 5 档黑白渐变)生成色点分布; - 叠加合成:
Layer2d(c3762894,Scale 0.6)与DrawBillboards(c576188d,Scale 0.88)把示波器圆环与色点合成到画面,Transform(e3d3fcf4)以VectorScopePosition(默认 X: 1.17, Y: 0.113)控制其位置。
所有内部图层最终汇入Layer2d/DrawScreenQuad等合成节点,输出到RenderTarget,再由Camera(8721aec0,AspectRatio 默认 -1)投影后经ImgOutput输出。值得注意的是节点图中还保留了_GridPoints_Old(d9e03f8a)这样的旧版网格生成节点,从命名可以推断波形绘制经历了从"网格点列"到"径向点/画线"的演进。
核心着色器源码逐段剖析
波形统计的核心逻辑集中在 waveform-cs.hlsl,完整源码如下:
cbuffer ParamConstants : register(b0) { float Height; float Opacity; float ClearBackground; float Intensity; } sampler texSampler : register(s0); Texture2D<float4> InputTexture : register(t0); RWTexture2D<float4> WriteOutput : register(u0); groupshared float4 SharedColors[512]; static const float ToRad = 3.141592/180; [numthreads(1,512,1)] void main(uint3 i : SV_DispatchThreadID) { int texWidth; int texHeight; WriteOutput.GetDimensions(texWidth, texHeight); SharedColors[i.y] = i.y > 256 ? float4(0,0,0, ClearBackground) : 0; GroupMemoryBarrierWithGroupSync(); const int steps = 256; if(i.y == 0) { float dashColor = i.x % 8 < 4 ? 1 : 0; SharedColors[511] = float4(dashColor.xxx, 1); SharedColors[256] = float4(dashColor.xxx, 1); [fastopt] for(int rowIndex= 0; rowIndex < 256; rowIndex++) { float2 uv = float2(float2( i.x / (float)texWidth, (float) rowIndex / 256.0 )); float4 col = InputTexture.SampleLevel(texSampler, uv,0); int3 level = 512 - clamp( col.rgb * 256, 0, 511); SharedColors[level.r] += float4(Intensity,0,0,Opacity) * col.a; SharedColors[level.g] += float4(0,Intensity,0,Opacity) * col.a; SharedColors[level.b] += float4(0,0,Intensity,Opacity) * col.a; } } GroupMemoryBarrierWithGroupSync(); WriteOutput[i.xy] = clamp(SharedColors[i.y], 0, 1); }逐段解读其原理:
- 常量缓冲区:
Height、Opacity、ClearBackground、Intensity四个 uniform 与算子的Height、Opacity、DimBackground、ColorIntensity四个输入一一对应,节点图负责把这些参数打包进 cbuffer; - 线程组织:
[numthreads(1,512,1)]表示每个线程组有 512 个线程,对应SharedColors[512]这个共享内存数组——它把亮度级(0~255 共 256 级亮度 bin,另加波形刻度区)映射到共享内存槽位,实现 GPU 内高效直方图累加; - 采样与直方图累加:线程组第一行(
i.y == 0)的线程逐行遍历输入图像(rowIndex0~255,配合i.x / texWidth归一化 UV),对每个像素做col.rgb * 256得到亮度等级,再以512 - clamp(...)反向映射到共享内存下标,把(Intensity, 0, 0, Opacity)、(0, Intensity, 0, Opacity)、(0, 0, Intensity, Opacity)分别累加到 R/G/B 三个亮度槽——这正是波形上红、绿、蓝三条彩色曲线的来源,累加强度受ColorIntensity(即Intensity)控制; - 波形刻度线:
i.x % 8 < 4生成 4:4 交替的虚线样式,写入SharedColors[511](顶部)与SharedColors[256](中位线),形成示波器上的参考刻度; - 同步与输出:两次
GroupMemoryBarrierWithGroupSync()确保共享内存读写同步,最终WriteOutput[i.xy] = clamp(SharedColors[i.y], 0, 1)把直方图写回纹理。
从SharedColors[i.y] = i.y > 256 ? float4(0,0,0, ClearBackground) : 0;可以看出,输出纹理上半部分(256 槽以上)会被ClearBackground(即DimBackground输入)填充为半透明黑色,用于压暗背景、突出波形——这与参数的命名语义完全吻合。
典型使用步骤
在 TiXL 中接入 WaveForm 的完整流程如下:
- 从算子库
Lib.image.analyze拖入WaveForm节点到你的图像处理链末端; - 把任意图像/视频/渲染结果(如
EffectTexture输入)连接到EffectTexture端口; - 按需调整参数:
- 调Height(默认 0.5)控制波形纵向占位;
- 调Opacity(默认 0.14)控制叠加层的半透明程度,监看时建议保持低值以免遮挡画面;
- 调DimBackground(默认 0.5)压暗背景,让示波器更醒目;
- 调ColorIntensity(默认 0.15)控制 RGB 三通道波形的着色浓度;
- 打开ShowVectorscope(默认 false)显示色域圆环,配合EnlargeVectorScopeCenter(默认 0.5)放大中心区域观察暗部色相,用VectorScopePosition(默认 X: 1.17, Y: 0.113)把示波器摆到画面合适位置;
- 将
ImgOutput连接到最终合成或输出节点,即可在实时画面中监看亮度波形与色域分布。
参考示例:HowToUseColors.t3 中同时实例化了两个 WaveForm(一个开启ShowVectorscope: true)与 ImageLevels,展示了在调色教学场景下的典型摆放方式。
实用调参建议
基于源码中的默认值与着色器逻辑,可以给出以下实测导向的建议:
- 监看亮度是否过曝/欠曝:观察波形顶部的白色刻度线(
SharedColors[511])。若波形大量贴顶被削平,说明高光溢出;若整体集中在底部,说明画面偏暗。中位刻度(SharedColors[256])对应 50% 灰,可用于判断中间调位置; - 判断偏色:波形中红、绿、蓝三条曲线重叠度越高,画面越接近中性。某条曲线明显高于其他两条,说明该色通道过量;
ColorIntensity提高后三条曲线的颜色区分会更明显; - 检查饱和度与色相:打开
ShowVectorscope后,参考圆环(来自vectorscope.png与RadialPoints生成的圆环)用于对照色相角度;色点聚集在圆心附近表示低饱和度,向外扩散表示高饱和;中心区域被EnlargeVectorScopeCenter放大后,低饱和度区域的细微色相偏移更容易被观察到; - 实时性能:波形统计使用共享内存直方图,每线程组仅 512 线程即可完成 256 级亮度统计,
OverrideSize默认(-1, 0)自动跟随输入尺寸,通常无需手动指定缓冲大小。
相关资源索引
- 算子文档:WaveForm.md、Lib.image.analyze 目录
- C# 实现:WaveForm.cs
- 节点图定义(含全部默认值与连接关系):WaveForm.t3
- 核心着色器:waveform-cs.hlsl、sample-vectorscope-points-cs.hlsl
- 色域参考图:vectorscope.png
- 使用示例:HowToUseColors.t3
延伸阅读:与 WaveForm 同属图像分析家族、常搭配使用的算子还有 ImageLevels(亮度分布直方图)、CompareImages(前后对比)、GetImageBrightness(亮度数值化输出),以及 ColorGrade(色彩分级)——把它们组合起来,即可构建一套完整的"调色 → 监看 → 校验"工作流。
- 音视频
- 图形学
- 桌面应用
【免费下载链接】t3
TiXL is an open source software to create realtime motion graphics.
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考