Unity URP管线Linear空间下玻璃效果

效果展示

玻璃效果

制作思路

功能概述(视觉组成)

该玻璃效果由 4 个层叠模块构成:

  1. 基础 MatCap 反射层

    • 使用视空间法线与视线计算 MatCap UV
    • 提供玻璃外观的主要反射/明暗结构(不依赖场景光源)
  2. 折射 MatCap 扰动层

    • 不是传统意义的屏幕抓取折射(GrabPass/SceneColor),而是第二张 MatCap
    • 通过"厚度 thickness"生成 UV 偏移,让 RefractMatCap 产生"错位/扭曲"的观感
  3. Fresnel 边缘色

    • 通过 NoV(法线与视线点积)做边缘增强
    • 与折射层做 lerp,决定边缘区域更偏 Fresnel 还是更偏折射扰动
  4. 贴花 Decal

    • 最终叠加层,用 decal 的 alpha 做覆盖混合
    • 可用于污渍、裂纹、标签等局部细节
2. 渲染设置与队列行为
2.1 SubShader Tags
复制代码
"RenderType"="Transparent"
"Queue"="Transparent"
2.2 Pass 状态
复制代码
Blend SrcAlpha OneMinusSrcAlpha
ZWrite On
Cull Back
ZTest LEqual
  • Blend:标准 alpha 混合。
  • ZWrite On :透明物体写深度(不常见但有用途)
    • 优点:玻璃自身排序/遮挡更稳定,减少"穿插错误"
    • 风险:多个透明物体叠加时,后绘制的透明可能被深度挡掉(透明排序问题会更明显)。适合"单层玻璃"或希望玻璃优先占位的场景。
  • Cull Back:背面剔除,避免内表面干扰。
  • ZTest LEqual:常规深度测试。
3. 顶点阶段(Vertex)输出内容

顶点着色器输出:

  • positionCS:裁剪空间坐标(用于屏幕栅格化)
  • positionWS:世界坐标(用于视线向量、计算高度)
  • normalWS:世界法线(用于 Fresnel、转换到视空间算 MatCap)
  • uv:模型 UV(用于 dirty/decal)

实现上依赖 URP 的工具函数:

  • GetVertexPositionInputs
  • GetVertexNormalInputs
4. MatCap 原理与 UV 计算(核心之一)
4.1 为什么 MatCap 适合玻璃"反射外观"

MatCap(Material Capture)是一种"把一个球面在特定光照/环境下拍成贴图"的技术。运行时根据视角和法线查表得到颜色,可快速得到类似反射的明暗变化,成本低且稳定。

它不是真反射:不会反映真实场景,只是"看起来像反射"。

4.2 Shader 中的 MatCap UV 算法

关键代码:

复制代码
float3 Vws = SafeNormalize(GetWorldSpaceViewDir(i.positionWS));

float3 Vvs = normalize(TransformWorldToViewDir(-Vws));      
float3 Nvs = normalize(TransformWorldToViewDir(N));     

float3 c = cross(Vvs, Nvs);
float2 matCapUV = float2(-c.y, c.x) * 0.5 + 0.5;

解释:

  1. 计算世界空间视线 Vws(从像素指向相机)。
  2. 将视线与法线转换到视空间(View Space),因为 MatCap 本质是"以相机为中心"的查表。
  3. cross(Vvs, Nvs) 得到一个与两者垂直的向量,其 x/y 分量用于构造 2D 查表坐标。
  4. 乘 0.5 加 0.5:把 [-1,1] 映射到 [0,1],用于采样贴图。

输出 matcap = SAMPLE_TEXTURE2D(_MatCap, ..., matCapUV) 作为基础反射层。

5. Fresnel(边缘增强)实现
5.1 NoV 的意义
复制代码
float NoV = dot(N, Vws);
  • 当视线接近法线方向(正视表面)时,NoV 趋近 1
  • 当视线与表面接近平行(掠射角/边缘)时,NoV 趋近 0
5.2 smoothstep 控制区间
复制代码
float fres = smoothstep(_Min, _Max, NoV);
  • _Min/_Max 定义 NoV 的映射区间
  • smoothstep 输出 0~1 的平滑曲线
  • 注意:此处 fres 随 NoV 增大而增大,因此:
    • 正视区域 fres 较大
    • 边缘区域 fres 较小

后续用到了 (1.0 - fres),相当于重新得到"边缘更强"的权重(这是这份 shader 的使用方式)。

6. Thickness(厚度)与高度映射(核心之二)
6.1 设计意图

真实玻璃折射强度与厚度相关。该 shader 用一个厚度标量 thickness 来驱动"折射 MatCap 的 UV 偏移",从而模拟厚薄不均导致的折射扰动。

6.2 ThicknessMap 的 UV 构造:用世界高度做纵向采样
复制代码
float3 objOriginWS = TransformObjectToWorld(float3(0,0,0));

float h = ((i.positionWS.y - objOriginWS.y) - _ObjectPivotOffset) / max(1e-5, _ObjectHeight);
float2 thicknessUV = float2(0.5, h);

含义:

  • 以物体原点(pivot)为基准取世界高度差:i.positionWS.y - objOriginWS.y
  • _ObjectPivotOffset:手动修正 pivot 不在底部/中心时的高度零点
  • _ObjectHeight:归一化高度范围,避免不同尺寸物体导致同一贴图不适配
  • thicknessUV.x 固定为 0.5:说明厚度图在 x 方向不变化,相当于只取一条竖向渐变(或把贴图当作 1D 曲线)

因此 ThicknessMap 实际是一个"沿高度变化的厚度曲线"。

7. DirtyMask(脏污)如何影响厚度与折射
复制代码
float2 uvDirty = i.uv * _DirtyMask_ST.xy + _DirtyMask_ST.zw;
float dirtyA = SAMPLE_TEXTURE2D(_DirtyMask, sampler_DirtyMask, uvDirty).a;
  • 取 dirty mask 的 alpha 通道作为脏污强度。

  • 参与 thickness 计算:

    float thickness =
    (1.0 - fres) +
    SAMPLE_TEXTURE2D(_ThicknessMap, sampler_ThicknessMap, thicknessUV).r +
    dirtyA;

分解:

  • (1 - fres):边缘区域更大 → 边缘更"厚/折射更强"(符合玻璃边缘更明显的主观印象)
  • ThicknessMap.r:提供沿高度变化的基础厚度
  • dirtyA:脏污区域增强 thickness → 让脏污处折射更强、更"糊/扭曲"(模拟指纹、雾化、水渍造成的扰动)
8. 折射 MatCap:UV 偏移与混合逻辑(核心之三)
8.1 偏移量与强度
复制代码
float refrOffset = thickness * _RefractIntensity;
float refrLerp = saturate(refrOffset);
  • _RefractIntensity 是全局折射强度
  • refrOffset 同时也是UV 偏移量 ,且 refrLerp 用它的大小做混合权重(越偏移越用折射层)
8.2 折射 MatCap 采样
复制代码
float4 refrMatcap = SAMPLE_TEXTURE2D(_RefractMatCap, sampler_RefractMatCap, matCapUV + refrOffset);

注意:这里 refrOffset 是一个标量,加到 float2 上会同时影响 u、v(等价于 (refrOffset, refrOffset))。这会产生一种"对角线方向统一漂移"的扭曲风格。若你希望更自然的扰动,一般会改成:

  • matCapUV + normalVS.xy * refrOffset
  • 或从 noise/flow map 得到 2D 偏移

但当前实现的优点是:简单、稳定、可控。

8.3 FresnelColor 与折射层的混合
复制代码
float4 fresOrRefr = lerp(_FresnelColor, refrMat, refrLerp);

解释:

  • refrOffset 小(折射弱)→ 更接近 _FresnelColor
  • refrOffset 大(折射强)→ 更接近 refrMatcap

这让 FresnelColor 更像是"折射不足时的补色/边缘色",并与厚度共同控制视觉风格。

9. Decal 贴花叠加(最终合成)
复制代码
float4 col = lerp(matcap + fresOrRefr, decal, decal.a);
  • 先把 matcap + fresOrRefr 作为玻璃主体颜色
  • 再用 decal alpha 做覆盖混合
    • decal.a=1:完全显示 decal
    • decal.a=0:完全显示玻璃主体

这适合做:裂纹、LOGO、污渍局部贴纸等。

10. Alpha(透明度)生成逻辑
复制代码
float a = decal.a + saturate(max(matcap.r, thickness));
return half4(col.rgb, a);

含义:

  • max(matcap.r, thickness):取 MatCap 的红通道或厚度的较大值作为"主体不透明度趋势"
  • saturate 限制到 [0,1]
  • 再加上 decal.a:贴花区域额外提高不透明度(例如裂纹、贴纸更实)

这是一个"风格化透明度"方案:透明度不是物理的 Beer-Lambert 吸收,而是让玻璃在高光强、厚度大、或有贴花时更"实"。

11. 参数说明与调参建议
MatCap 相关
  • _MatCap:基础反射外观(决定玻璃主质感)
  • _RefractMatCap:折射扭曲外观(决定"扰动的反射色块"长什么样)
  • _RefractIntensity:折强度(同时影响 UV 偏移与折射混合权重)

建议:

  • _RefractMatCap 可以用更"模糊/散"的 MatCap,配合偏移更像折射糊影。
Fresnel
  • _FresnelColor:折射不足时的边缘色/补色
  • _Min/_Max:控制 NoV 到 fres 的过渡区间
    • _Min 小、_Max 大:过渡更平缓
    • 若想"边缘更明显",通常会让 (1-fres)在更宽区间保持较大(可通过调整 min/max 或改成 1-smoothstep(...)
Thickness/高度
  • _ObjectHeight:高度归一化尺度(非常关键,否则 thicknessUV 的 h 会跑飞)
  • _ObjectPivotOffset:修正 pivot 高度零点
  • _ThicknessMap:沿高度控制厚度变化
  • _DirtyMask:局部增强厚度/折射(用 alpha 通道)
Decal
  • _Decal:贴花内容
  • _Decal_ST:平铺与偏移
12. 已知限制与可扩展方向(工程角度)
限制
  1. 不是真折射:没有采样 SceneColor,因此不会折射真实背景。
  2. UV 偏移是标量统一偏移(u、v 同时加同一个值),扰动方向单一。
  3. ZWrite On 的透明会带来多层透明叠加不正确的风险。
相关推荐
June bug5 小时前
【领域知识】休闲游戏一次发版全流程:Google Play + Apple App Store
unity
星夜泊客8 小时前
C# 基础:为什么类可以在静态方法中创建自己的实例?
开发语言·经验分享·笔记·unity·c#·游戏引擎
dzj20219 小时前
PointerEnter、PointerExit、PointerDown、PointerUp——鼠标点击物体,则开始旋转,鼠标离开或者松开物体,则停止旋转
unity·pointerdown·pointerup
心前阳光10 小时前
Unity 模拟父子关系
android·unity·游戏引擎
在路上看风景13 小时前
26. Mipmap
unity
咸鱼永不翻身15 小时前
Unity视频资源压缩详解
unity·游戏引擎·音视频
在路上看风景15 小时前
4.2 OverDraw
unity
在路上看风景16 小时前
1.10 CDN缓存
unity
ellis19701 天前
Unity插件SafeArea Helper适配异形屏详解
unity
nnsix1 天前
Unity Physics.Raycast的 QueryTriggerInteraction枚举作用
unity·游戏引擎