URP 渲染实战 / 第 18 章 溶解、全息与护盾 / 案例 18.3
案例 18.3方向溶解与球形溶解进阶
目标:让溶解有方向感——比如角色从头到脚被“传送”走;或者从一个点向外扩散——比如被魔法击中的位置先开始瓦解。
原理:换一种溶解值
clip 和边带的逻辑完全不变,只换溶解值 v 的来源:
- 方向溶解:把对象空间位置投影到一个方向上,h = dot(positionOS, dir),再按物体在这个方向上的范围 [min, max] 归一化到 0~1。投影值小的一端先消失。
- 球形溶解:v = distance(positionWS, center) / R。离球心越近越先消失,看起来像一个看不见的球从 center 处胀开,把物体“吃掉”。
纯几何的溶解值会得到一条笔直的切割线或者一个完美的圆,太“干净”了。所以再用 lerp( 掺一点噪声,切割线就变成了不规则的锯齿边。
Assets/Cookbook/Ch18/DissolveCore.hlsl(节选:dissolveValue)
// 返回 0~1 的“溶解值”:值越小越早消失。进度 p 时,值 < p 的像素被裁掉。 half DissolveValue(float2 uv, float3 positionOS, float3 positionWS) { half noise = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, uv * _NoiseMap_ST.xy + _NoiseMap_ST.zw).r; #if defined(_MODE_DIRECTION) // 方向溶解:沿对象空间某方向的投影,归一化到 0~1,再掺一点噪声让边缘不规则 float h = dot(positionOS, normalize(_DissolveDir.xyz)); half t = saturate((h - _DirRange.x) / (_DirRange.y - _DirRange.x)); return lerp(t, noise, _NoiseStrength); #elif defined(_MODE_SPHERE) // 球形溶解:离球心越近越早消失,像被一个不断变大的球“吞掉” half t = saturate(distance(positionWS, _SphereCenter.xyz) / _SphereCenter.w); return lerp(t, noise, _NoiseStrength); #else return noise; #endif }
// 返回 0~1 的“溶解值”:值越小越早消失。进度 p 时,值 < p 的像素被裁掉。 float DissolveValue(vec2 uv, vec3 positionOS, vec3 positionWS) { float noise = texture(_NoiseMap, uv * _NoiseMap_ST.xy + _NoiseMap_ST.zw).r; #if defined(_MODE_DIRECTION) // 方向溶解:沿对象空间某方向的投影,归一化到 0~1,再掺一点噪声让边缘不规则 float h = dot(positionOS, normalize(_DissolveDir.xyz)); float t = saturate((h - _DirRange.x) / (_DirRange.y - _DirRange.x)); return mix(t, noise, _NoiseStrength); #elif defined(_MODE_SPHERE) // 球形溶解:离球心越近越早消失,像被一个不断变大的球“吞掉” float t = saturate(distance(positionWS, _SphereCenter.xyz) / _SphereCenter.w); return mix(t, noise, _NoiseStrength); #else return noise; #endif }
三种模式用 [KeywordEnum( 生成三个关键字 _,Shader 里用 #pragma shader_ 声明——它们是材质级别的选择,不会在运行时频繁切换,shader_feature 只编译实际用到的变体。注意三个 Pass 都要声明同样的关键字,否则阴影 Pass 会走默认分支,影子的溶解形状就和物体对不上了。
为什么方向溶解用对象空间、球形溶解用世界空间
方向溶解描述的是“物体自身从头到脚”,角色转身、倒地,头脚关系不变,所以用对象空间;球心通常来自游戏逻辑(被击中的点、爆炸中心),它天然是世界坐标,所以用世界空间。如果方向溶解想用世界方向(比如永远从上往下),在 C# 里用 transform. 换算成对象空间再传进来即可。

从游戏逻辑驱动
球形溶解最常见的用法是“击中点开始瓦解”。C# 侧只需要在命中时记录世界坐标,然后用协程推进进度:
// 命中时调用:从 hitPoint 开始,duration 秒内溶解完 IEnumerator DissolveFrom(Renderer r, Vector3 hitPoint, float radius, float duration) { var mat = r.material; // 材质实例,只影响这一个物体 mat.EnableKeyword("_MODE_SPHERE"); mat.SetVector("_SphereCenter", new Vector4(hitPoint.x, hitPoint.y, hitPoint.z, radius)); for (float t = 0; t < duration; t += Time.deltaTime) { mat.SetFloat("_Progress", t / duration); yield return null; } r.gameObject.SetActive(false); }
注意运行时
EnableKeyword 打开一个 shader_feature 关键字,前提是这个变体被打进了包。如果构建时没有任何材质用到 _MODE_SPHERE,变体会被剥离,运行时打开关键字也不会生效。解决办法是让工程里至少有一个材质使用它,或者改用 multi_compile_local(变体数更多),详见第 29 章。