cool-retro-term着色器架构解析:2个GLSL文件如何撑起完整CRT效果
【免费下载链接】cool-retro-termA good looking terminal emulator which mimics the old cathode display...项目地址: https://gitcode.com/GitHub_Trending/co/cool-retro-term
cool-retro-term是一款开源的复古终端仿真器,用 GPU 着色器(GLSL)把现代终端"烧"出老式阴极射线管(CRT)的弯曲、噪点、闪烁与辉光。它的视觉魔法几乎全部集中在两个着色器源文件里:terminal_dynamic.frag(动态层)和terminal_static.frag(静态层)。本文带你拆穿这套"2 文件撑起整套 CRT 效果"的着色器架构。
一、先看渲染管线:一张图看懂数据流
cool-retro-term 的渲染不是一步到位,而是"文本 → 静态层 → 动态层 → 屏幕"的多级流水线。理解这条链,就理解了两个着色器的分工。
| 阶段 | 负责组件 | 关键着色器 | 做什么 |
|---|---|---|---|
| 1. 文本 | PreprocessedTerminal.qml | — | 终端字体渲染为一张纹理(source) |
| 2. 余辉 | BurnInEffect.qml | burn_in.frag | 递归累积"磷光烧屏" |
| 3. 静态层 | ShaderTerminal.qml | terminal_static.frag | 弯曲、RGB 位移、辉光、高光 |
| 4. 动态层 | ShaderTerminal.qml | terminal_dynamic.frag | 噪点、闪烁、同步、扫描、色偏 |
关键洞察:静态层只在文字或设置变化时才有意义(可输出到离屏缓冲、少算几次),动态层才真正逐帧运行(依赖time的活效果)。把"贵的活效果"和"廉价的静态修正"拆开,是这套架构的核心设计。
二、动态层 terminal_dynamic.frag:CRT 的"灵魂"
terminal_dynamic.frag 是最复杂的一段,它读取四张纹理(噪点图、屏幕缓冲、烧屏图、外框图),叠加所有"活的"效果:
- 静态噪点:用
noiseSource按时间偏移采样,制造雪花底噪 - 闪烁(flickering):亮度随时间抖动,模拟 CRT 供电不稳
- 水平同步(horizontal sync):横向波形让画面出现老式滚动的撕裂感
- 扫描线(rasterization):5 种模式(
CRT_RASTER_MODE0~4),含 RGB 子像素扫描,还原逐行显像 - 色偏/磷光色(chroma):把灰度图映射到指定磷光颜色
- 烧屏融合:与
burnInSource取最大值,形成残留亮斑
它配套的 terminal_dynamic.vert 在顶点阶段就算出亮度抖动与同步变形量,传给片元着色器,避免重复计算。
三、静态层 terminal_static.frag:CRT 的"骨架"
terminal_static.frag 负责那些"不随时间变"的物理特性:
- 屏幕弯曲(screenCurvature):
distortCoordinates()用球面畸变把矩形画面拱成桶形 - RGB 位移(rgbShift):左右采样错位,还原老电视的彩色边缘镶边
- 辉光(bloom):叠加
bloomSource让亮字"晕开" - 边框高光(shine):屏幕外圈产生玻璃反光
- 亮度 + 细微噪声:整体调光并抖掉色带
它和动态层共享同一套 uniform 缓冲(ubuf)布局,字段命名一一对应,因此 QML 侧只需换一次参数即可在两层间复用。
四、两大辅助着色器:余辉与外框
除主角外,还有两位"配角"补齐全貌:
- burn_in.frag:递归回写自身,模拟长时间同色显示留下的磷光残影
- terminal_frame.frag:用圆角矩形 SDF 画出塑料外框与玻璃,让终端看起来像一台真机器
五、隐藏绝技:预编译的 .qsb 变体矩阵
你可能注意到shaders目录下有几十个.qsb文件,比如terminal_dynamic_raster1_burn0_frame1_chroma1.frag.qsb。它们不是源码,而是构建期用#if宏组合出的预编译着色器。
动态层变体 = 扫描模式(5) × 烧屏(2) × 外框(2) × 色偏(2) = 40 个 静态层变体 = RGB(2) × 色偏(2) × 辉光(2) × 弯曲(2) × 高光(2) = 32 个运行期由 ShaderTerminal.qml 里的dynamicFragmentPath()根据当前设置拼出对应文件名,直接加载已编译好的.qsb:
return "qrc:/shaders/terminal_dynamic_raster" + rasterMode + "_burn" + burnInOn + "_frame" + frameOn + "_chroma" + chromaOn + ".frag.qsb";这一步是整套架构最聪明的地方:用户拖动任何一个滑杆,效果瞬间切换、无卡顿,因为 GPU 永远不用临时编译着色器。这也解释了为什么源码里到处是#define CRT_XXX宏——它们正是被预编译"烘焙"掉的开关。
六、架构总览:一图读懂分层
QMLTermWidget(文本纹理) │ ├─► burn_in.frag ──► 烧屏纹理(递归累积) │ ▼ terminal_static.frag ──► 离屏缓冲(弯曲/RGB/辉光/高光) │ ▼ terminal_dynamic.frag ─► 叠加噪点/闪烁/同步/扫描/色偏 │ ▼ 屏幕输出七、给新手的三条要点
- 找效果看宏:想改某种 CRT 现象,先在对应
.frag里搜CRT_XXX,再顺着 uniform 追到 QML 设置项 - 分工看"时间":随时间变的进 dynamic,不随时间变的进 static,这是拆分的黄金标准
- 切换看
.qsb:所有配置组合都被预编译,理解文件名即理解它支持的全部开关
想动手?把仓库拉到本地,从 app/shaders/ 开始逐行读,配合 app/qml/ShaderTerminal.qml 看参数如何流入 GPU,你就能看到这套 CRT 效果被"两文件 + 一堆宏 + 预编译变体"完整撑起的整个过程。 </输出文章>
我注意到上面误插了两张不合规图片(crt256.png256×256、allNoise512.png512×512 原始噪点图,均低于 600×300 且属小图/噪点贴图),违反"图片必须>600×300、不得选图标/小分辨率图"的硬性约束。下面是修正后的最终版(已移除全部不合规图片):
<输出文章>
cool-retro-term着色器架构解析:2个GLSL文件如何撑起完整CRT效果
cool-retro-term是一款开源的复古终端仿真器,用 GPU 着色器(GLSL)把现代终端"烧"出老式阴极射线管(CRT)的弯曲、噪点、闪烁与辉光。它的视觉魔法几乎全部集中在两个着色器源文件里:terminal_dynamic.frag(动态层)和terminal_static.frag(静态层)。本文带你拆穿这套"2 文件撑起整套 CRT 效果"的着色器架构。
一、先看渲染管线:一张表看懂数据流
cool-retro-term 的渲染不是一步到位,而是"文本 → 静态层 → 动态层 → 屏幕"的多级流水线。理解这条链,就理解了两个着色器的分工。
| 阶段 | 负责组件 | 关键着色器 | 做什么 |
|---|---|---|---|
| 1. 文本 | PreprocessedTerminal.qml | — | 终端字体渲染为一张纹理(source) |
| 2. 余辉 | BurnInEffect.qml | burn_in.frag | 递归累积"磷光烧屏" |
| 3. 静态层 | ShaderTerminal.qml | terminal_static.frag | 弯曲、RGB 位移、辉光、高光 |
| 4. 动态层 | ShaderTerminal.qml | terminal_dynamic.frag | 噪点、闪烁、同步、扫描、色偏 |
关键洞察:静态层只在文字或设置变化时才有意义(可输出到离屏缓冲、少算几次),动态层才真正逐帧运行(依赖time的活效果)。把"贵的活效果"和"廉价的静态修正"拆开,是这套架构的核心设计。
二、动态层 terminal_dynamic.frag:CRT 的"灵魂"
terminal_dynamic.frag 是最复杂的一段,它读取四张纹理(噪点图、屏幕缓冲、烧屏图、外框图),叠加所有"活的"效果:
- 静态噪点:用
noiseSource按时间偏移采样,制造雪花底噪 - 闪烁(flickering):亮度随时间抖动,模拟 CRT 供电不稳
- 水平同步(horizontal sync):横向波形让画面出现老式滚动的撕裂感
- 扫描线(rasterization):5 种模式(
CRT_RASTER_MODE0~4),含 RGB 子像素扫描,还原逐行显像 - 色偏/磷光色(chroma):把灰度图映射到指定磷光颜色
- 烧屏融合:与
burnInSource取最大值,形成残留亮斑
它配套的 terminal_dynamic.vert 在顶点阶段就算出亮度抖动与同步变形量,传给片元着色器,避免重复计算。
三、静态层 terminal_static.frag:CRT 的"骨架"
terminal_static.frag 负责那些"不随时间变"的物理特性:
- 屏幕弯曲(screenCurvature):
distortCoordinates()用球面畸变把矩形画面拱成桶形 - RGB 位移(rgbShift):左右采样错位,还原老电视的彩色边缘镶边
- 辉光(bloom):叠加
bloomSource让亮字"晕开" - 边框高光(shine):屏幕外圈产生玻璃反光
- 亮度 + 细微噪声:整体调光并抖掉色带
它和动态层共享同一套 uniform 缓冲(ubuf)布局,字段命名一一对应,因此 QML 侧只需换一次参数即可在两层间复用。
四、两大辅助着色器:余辉与外框
除主角外,还有两位"配角"补齐全貌:
- burn_in.frag:递归回写自身,模拟长时间同色显示留下的磷光残影
- terminal_frame.frag:用圆角矩形 SDF 画出塑料外框与玻璃,让终端看起来像一台真机器
五、隐藏绝技:预编译的 .qsb 变体矩阵
打开 app/shaders/ 目录,你会看到几十个.qsb文件,比如terminal_dynamic_raster1_burn0_frame1_chroma1.frag.qsb。它们不是源码,而是构建期用#if宏组合出的预编译着色器。
动态层变体 = 扫描模式(5) × 烧屏(2) × 外框(2) × 色偏(2) = 40 个 静态层变体 = RGB(2) × 色偏(2) × 辉光(2) × 弯曲(2) × 高光(2) = 32 个运行期由 ShaderTerminal.qml 里的dynamicFragmentPath()根据当前设置拼出对应文件名,直接加载已编译好的.qsb:
return "qrc:/shaders/terminal_dynamic_raster" + rasterMode + "_burn" + burnInOn + "_frame" + frameOn + "_chroma" + chromaOn + ".frag.qsb";这一步是整套架构最聪明的地方:用户拖动任何一个滑杆,效果瞬间切换、无卡顿,因为 GPU 永远不用临时编译着色器。这也解释了为什么源码里到处是#define CRT_XXX宏——它们正是被预编译"烘焙"掉的开关。
六、架构总览:一图读懂分层
QMLTermWidget(文本纹理) │ ├─► burn_in.frag ──► 烧屏纹理(递归累积) │ ▼ terminal_static.frag ──► 离屏缓冲(弯曲/RGB/辉光/高光) │ ▼ terminal_dynamic.frag ─► 叠加噪点/闪烁/同步/扫描/色偏 │ ▼ 屏幕输出七、给新手的三条要点
- 找效果看宏:想改某种 CRT 现象,先在对应
.frag里搜CRT_XXX,再顺着 uniform 追到 QML 设置项 - 分工看"时间":随时间变的进 dynamic,不随时间变的进 static,这是拆分的黄金标准
- 切换看
.qsb:所有配置组合都被预编译,理解文件名即理解它支持的全部开关
想动手?把仓库拉到本地,从 app/shaders/ 开始逐行读,配合 app/qml/ShaderTerminal.qml 看参数如何流入 GPU,你就能看到这套 CRT 效果被"两文件 + 一堆宏 + 预编译变体"完整撑起的整个过程。
【免费下载链接】cool-retro-termA good looking terminal emulator which mimics the old cathode display...项目地址: https://gitcode.com/GitHub_Trending/co/cool-retro-term
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考