文章目录
-
- [0. 效果预览](#0. 效果预览)
- [1. 原理简述](#1. 原理简述)
- [2. 功能点](#2. 功能点)
- [3. 完整 Shader(可直接用)](#3. 完整 Shader(可直接用))
- [4. 使用方法](#4. 使用方法)
- [5. 参数说明](#5. 参数说明)
- [6. 变体与扩展](#6. 变体与扩展)
-
- [6.1 双层扫描线(粗线 + 细线)](#6.1 双层扫描线(粗线 + 细线))
- [6.2 顶点抖动(信号干扰感)](#6.2 顶点抖动(信号干扰感))
- [6.3 主贴图叠加](#6.3 主贴图叠加)
- [7. 常见问题](#7. 常见问题)
- [8. 性能建议](#8. 性能建议)
0. 效果预览
全息扫描线是科幻游戏/UI 中的经典视觉效果:半透明的模型表面叠加水平滚动的扫描亮线,配合菲涅尔边缘光,营造出全息投影的未来感。常用于全息通讯、敌人扫描、科幻 UI 面板等场景。

1. 原理简述
全息扫描线的本质:用物体的世界空间 Y 坐标做周期性 sin/frac 运算,生成水平条纹,再让条纹随时间滚动。
关键伪代码:
hlsl
float worldY = positionWS.y;
// 用 frac 生成 0~1 重复的锯齿波,乘以密度控制条纹数量
float scanline = frac(worldY * _LinesDensity + _Time.y * _ScrollSpeed);
// step 把锯齿波二值化:低于阈值的部分变成亮线
scanline = step(scanline, _LinesWidth);
整个效果由三层叠加:
- 半透明底色:全息主色调(青色/蓝色),整体半透明
- 扫描线:世界空间 Y 方向的水平条纹,随时间滚动
- 菲涅尔边缘光:边缘比中心更亮,增强立体感和全息感
2. 功能点
- 世界空间扫描线:基于世界 Y 坐标生成水平条纹,不受模型 UV 影响,多个模型的扫描线能自然对齐
- 滚动动画 :扫描线随
_Time自动向上滚动,速度可控 - 条纹密度与宽度:独立控制扫描线的密度和每条线的粗细
- 菲涅尔边缘光:视角边缘更亮,模拟全息投影的发光边沿
- 半透明渲染 :
Blend SrcAlpha OneMinusSrcAlpha,可叠加在场景中 - 全息主色调:统一的 HDR 颜色控制整体色调
- 闪烁效果:可选的全局闪烁,模拟全息信号不稳定
3. 完整 Shader(可直接用)
hlsl
Shader "Custom/HologramScanline_URP"
{
Properties
{
// 全息主色调(建议 HDR,如青色 (0, 2, 3),配合 Bloom 效果更好)
[HDR] _HoloColor ("Hologram Color", Color) = (0, 1.5, 2, 1)
// 整体透明度(0 = 完全透明,1 = 完全不透明)
_Alpha ("Alpha", Range(0, 1)) = 0.5
// ===== 扫描线参数 =====
// 扫描线密度(值越大条纹越密)
_LinesDensity ("Lines Density", Float) = 60.0
// 扫描线宽度(占一个周期的比例,越小线越细)
_LinesWidth ("Lines Width", Range(0.0, 1.0)) = 0.1
// 扫描线亮度增益
_LinesIntensity ("Lines Intensity", Range(0, 3)) = 1.0
// 扫描线滚动速度(正值向上,负值向下)
_ScrollSpeed ("Scroll Speed", Float) = 1.0
// ===== 菲涅尔参数 =====
// 菲涅尔强度(值越大,边缘光越集中在边沿)
_FresnelPower ("Fresnel Power", Range(0.1, 10)) = 2.0
// 菲涅尔亮度增益
_FresnelIntensity ("Fresnel Intensity", Range(0, 5)) = 1.5
// ===== 闪烁参数 =====
// 闪烁速度(0 = 不闪烁)
_FlickerSpeed ("Flicker Speed", Float) = 3.0
// 闪烁幅度(0~1,0 = 无闪烁,1 = 完全明灭)
_FlickerAmount ("Flicker Amount", Range(0, 1)) = 0.1
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalRenderPipeline"
"Queue" = "Transparent" // 半透明队列
"RenderType" = "Transparent"
}
Pass
{
Name "HologramPass"
Tags { "LightMode" = "UniversalForward" }
Cull Back // 只渲染正面(全息效果一般不需要背面)
ZWrite Off // 半透明物体关深度写入,避免遮挡问题
Blend SrcAlpha OneMinusSrcAlpha // 标准 Alpha 混合
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// GPU Instancing 支持
#pragma multi_compile_instancing
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// =========================================================
// 材质属性(与 Properties 一一对应)
// =========================================================
float4 _HoloColor;
float _Alpha;
float _LinesDensity;
float _LinesWidth;
float _LinesIntensity;
float _ScrollSpeed;
float _FresnelPower;
float _FresnelIntensity;
float _FlickerSpeed;
float _FlickerAmount;
struct Attributes
{
float4 positionOS : POSITION; // 模型空间顶点位置
float3 normalOS : NORMAL; // 模型空间法线(菲涅尔需要)
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionHCS : SV_POSITION; // 裁剪空间位置
float3 positionWS : TEXCOORD0; // 世界空间位置(扫描线用)
float3 normalWS : TEXCOORD1; // 世界空间法线(菲涅尔用)
float3 viewDirWS : TEXCOORD2; // 世界空间视线方向(菲涅尔用)
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
// =========================================================
// 顶点着色器:计算世界空间位置、法线、视线方向
// =========================================================
Varyings vert(Attributes v)
{
Varyings o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
// 变换到各个空间
o.positionHCS = TransformObjectToHClip(v.positionOS.xyz);
o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
o.normalWS = TransformObjectToWorldNormal(v.normalOS);
// 视线方向 = 相机位置 - 顶点世界位置
o.viewDirWS = GetWorldSpaceNormalizeViewDir(o.positionWS);
return o;
}
// =========================================================
// 片元着色器:扫描线 + 菲涅尔 + 闪烁
// =========================================================
half4 frag(Varyings i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
// ---------- 1) 扫描线 ----------
// 用世界 Y 坐标生成条纹,不依赖 UV,多物体能对齐
float scanY = i.positionWS.y * _LinesDensity + _Time.y * _ScrollSpeed;
// frac 取小数部分 → 0~1 锯齿波
float scanline = frac(scanY);
// step 二值化:小于 _LinesWidth 的部分 = 1(亮线),其余 = 0
scanline = step(scanline, _LinesWidth) * _LinesIntensity;
// ---------- 2) 菲涅尔边缘光 ----------
float3 N = normalize(i.normalWS);
float3 V = normalize(i.viewDirWS);
// 经典菲涅尔:1 - NdotV,边缘 NdotV 接近 0,所以 fresnel 接近 1
float NdotV = saturate(dot(N, V));
float fresnel = pow(1.0 - NdotV, _FresnelPower) * _FresnelIntensity;
// ---------- 3) 闪烁 ----------
// sin 产生 -1~1 波动,映射到 1-amount ~ 1 范围
float flicker = 1.0 - _FlickerAmount * 0.5 * (1.0 + sin(_Time.y * _FlickerSpeed));
// ---------- 4) 合成 ----------
// 基础颜色 = 全息色 × (扫描线亮度 + 菲涅尔亮度 + 基础底色)
half3 color = _HoloColor.rgb * (scanline + fresnel + 0.3);
// 闪烁影响整体亮度
color *= flicker;
// 透明度:边缘更亮也更不透明,中心更透
float alpha = _Alpha * (fresnel + scanline * 0.5 + 0.3);
alpha = saturate(alpha) * flicker;
return half4(color, alpha);
}
ENDHLSL
}
}
}
4. 使用方法
- 在 Unity 项目的
Assets/Shaders/下新建文件HologramScanline_URP.shader,粘贴上方完整代码。 - 新建材质(Create → Material),Shader 选择
Custom/HologramScanline_URP。 - 将材质赋给场景中的模型(推荐用人形模型或几何体,效果更明显)。
- 在 Inspector 中调节参数:
Hologram Color:推荐青色系 HDR 值,如(0, 1.5, 2)或(0, 3, 4)Lines Density:60~150 之间尝试,值越大扫描线越密Lines Width:0.05~0.2,控制每条线的粗细Scroll Speed:1~3,控制滚动快慢
- 开启 URP 的 Bloom 后处理效果,全息的 HDR 颜色会产生辉光,效果大幅提升。

用脚本控制全息显隐(如扫描敌人时触发):
csharp
using UnityEngine;
public class HologramToggle : MonoBehaviour
{
[SerializeField] private Material holoMaterial;
[SerializeField] private float fadeDuration = 0.5f;
private float _targetAlpha;
private float _currentAlpha;
// 外部调用:显示全息
public void ShowHologram()
{
_targetAlpha = 0.5f;
}
// 外部调用:隐藏全息
public void HideHologram()
{
_targetAlpha = 0f;
}
void Update()
{
_currentAlpha = Mathf.MoveTowards(_currentAlpha, _targetAlpha,
Time.deltaTime / fadeDuration);
holoMaterial.SetFloat("_Alpha", _currentAlpha);
}
}
5. 参数说明
| 参数 | 类型 | 范围/默认值 | 说明 |
|---|---|---|---|
_HoloColor |
Color [HDR] | (0, 1.5, 2, 1) | 全息主色调,建议 HDR 值配合 Bloom |
_Alpha |
Range(0,1) | 0.5 | 整体透明度 |
_LinesDensity |
Float | 60.0 | 扫描线密度,值越大条纹越密 |
_LinesWidth |
Range(0,1) | 0.1 | 扫描线宽度,占一个周期的比例 |
_LinesIntensity |
Range(0,3) | 1.0 | 扫描线亮度增益 |
_ScrollSpeed |
Float | 1.0 | 滚动速度,正值向上,负值向下 |
_FresnelPower |
Range(0.1,10) | 2.0 | 菲涅尔指数,越大边缘光越窄 |
_FresnelIntensity |
Range(0,5) | 1.5 | 菲涅尔亮度增益 |
_FlickerSpeed |
Float | 3.0 | 闪烁频率,0 = 不闪烁 |
_FlickerAmount |
Range(0,1) | 0.1 | 闪烁幅度,越大明灭越剧烈 |
6. 变体与扩展
6.1 双层扫描线(粗线 + 细线)
真实全息投影通常有多层条纹叠加,加一组低密度粗线增强层次感:
hlsl
// 在 frag 中,原有 scanline 之后追加:
float scanCoarse = frac(i.positionWS.y * _LinesDensity * 0.15 + _Time.y * _ScrollSpeed * 0.5);
scanCoarse = step(scanCoarse, 0.03) * 2.0; // 粗线更亮、更稀疏
scanline += scanCoarse;
6.2 顶点抖动(信号干扰感)
给顶点加一点水平方向的随机偏移,模拟全息信号不稳定:
hlsl
// 在 vert 中,TransformObjectToHClip 之前:
float glitch = step(0.95, frac(sin(v.positionOS.y * 50.0 + _Time.y * 20.0) * 43758.5453));
v.positionOS.x += glitch * 0.02; // 偶尔水平偏移一小段
6.3 主贴图叠加
如果需要全息投影上显示纹理细节(如角色衣服花纹),可以加一张贴图:
hlsl
// Properties 中增加:
_BaseMap ("Base Map", 2D) = "white" {}
// 声明贴图和采样器:
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
float4 _BaseMap_ST;
// Attributes 中增加 UV:
float2 uv : TEXCOORD0;
// Varyings 中增加 UV:
float2 uv : TEXCOORD3;
// vert 中:
o.uv = TRANSFORM_TEX(v.uv, _BaseMap);
// frag 合成时叠加贴图灰度:
float texDetail = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv).r;
color *= (0.5 + texDetail * 0.5); // 让贴图明暗影响全息亮度
7. 常见问题
Q: 扫描线在模型上是斜的或者弯曲的?
A: 扫描线基于世界空间 Y 坐标,所以它始终是水平的。如果看起来不水平,检查模型是否旋转了。如果需要扫描线跟随模型旋转,改用模型空间坐标 v.positionOS.y(在 vert 中传给 Varyings)。
Q: 开了 Bloom 但全息没有辉光效果?
A: 检查两点:① _HoloColor 的 RGB 分量需要大于 1(HDR 值),比如 (0, 3, 4) 而不是 (0, 0.5, 0.8)。② URP Asset 中确认勾选了 HDR,并且 Post-processing 的 Bloom 已添加到 Volume 中。
Q: 模型完全看不见 / 完全透明?
A: 检查 _Alpha 是否太小。另外确认 URP Renderer 的 Transparent 排序没有问题------在 Universal Renderer Data 中确保 Transparent Layer Mask 包含了物体所在 Layer。
Q: 扫描线滚动有卡顿/跳帧感?
A: _ScrollSpeed 过大时,条纹移动过快会产生视觉频闪(类似车轮倒转错觉)。降低速度或增加 _LinesDensity 可以缓解。
Q: 想让全息效果只出现在模型的一部分(比如上半身)?
A: 在 frag 中加一个高度裁剪:clip(i.positionWS.y - _CutoffY);,用 _CutoffY 控制从哪个高度开始显示。也可以用一张 Mask 贴图控制更精细的区域。
8. 性能建议
- 简单数学 :整个效果只用了
frac、step、pow、sin,没有纹理采样(除非加变体),片元开销极低。 - 关闭深度写入 :
ZWrite Off是半透明必须的,但也意味着全息物体不会遮挡后面的物体。如果场景中有大量全息物体互相重叠,注意 Overdraw。 - 远距离简化 :远处全息扫描线太密会变成噪点,可以用脚本根据距离降低
_LinesDensity,或者直接隐藏全息层换成简单的半透明色。 - Bloom 开销 :Bloom 后处理是全息效果的重要组成部分,但它是全屏后处理。如果只有少量全息物体,Bloom 的性价比不高------可以考虑在 Shader 中用
smoothstep模拟柔和发光,省掉 Bloom 的全屏 Pass。 - Instancing :多个全息物体使用同一材质时,GPU Instancing 可以合批渲染。确保材质属性用
MaterialPropertyBlock设置而非直接修改material,否则会打断合批。