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 的透明会带来多层透明叠加不正确的风险。
相关推荐
极客柒7 小时前
Unity 大地图高性能砍树顶点动画Shader
unity·游戏引擎
avi911110 小时前
UnityProfiler游戏优化-举一个简单的Editor调试
游戏·unity·游戏引擎·aigc·vibe coding·editor扩展
学嵌入式的小杨同学11 小时前
C 语言实战:动态规划求解最长公共子串(连续),附完整实现与优化
数据结构·c++·算法·unity·游戏引擎·代理模式
学嵌入式的小杨同学13 小时前
顺序表(SqList)完整解析与实现(数据结构专栏版)
c++·算法·unity·游戏引擎·代理模式
程序猿多布14 小时前
HybridCLR热更打包后AOT泛型函数实例化缺失处理
unity·hybridclr·aot generic
平行云15 小时前
实时云渲染支持数字孪生智能工厂:迈向“零原型”制造
人工智能·unity·ue5·云计算·webrtc·制造·实时云渲染
dzj202115 小时前
Unity中使用LLMUnity遇到的问题(一)
unity·llm·llmunity
Howrun77715 小时前
虚幻引擎 C++ 制作“射击FPS游戏“
游戏·游戏引擎·虚幻
DowneyJoy16 小时前
【Unity通用工具类】列表扩展方法ListExtensions
unity·c#·交互