ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Unity Shader中Dither详解:原理、实现与避坑指南

Unity Shader中Dither详解:原理、实现与避坑指南 很多人在做Unity效果的时候都遇到过这样的场景半透明物体排序乱成了一团或者渐变色条带清晰可见又或者做溶解效果时边缘总是硬邦邦的。这时候老手往往会说一句“试试Dither”。我第一次接触Dither是入行第二年做一款像素风手游美术要求物体消失时带有那种“网格状”的平滑过渡我当时完全不知道该怎么写后来翻到一个开源项目才搞明白原来Shader里说的“Dither”并不是什么高深概念它本质上就是用“噪点”去换“细节”。这篇文章我就把Unity Shader里Dither的作用、原理、实战写法以及我踩过的那些坑一次性讲清楚。不管你是刚接触Shader的新手还是写了一阵子但没真正理解Dither的老手这篇都值得你花十分钟看完。1. Dither到底在解决什么问题1.1 颜色阶跃与色带的根源我们先从最常见的现象说起。你在Linear空间下渲染了一张渐变贴图或者在场景里放了一个Area Light照射的平面结果屏幕上出现了明显的一圈一圈色带。问题出在哪里图元本身是平滑的但屏幕最终要输出的颜色是有限的最典型的就是8bit颜色每个通道只有256个级别。如果渐变从0到1跨越了很大的屏幕空间相邻像素的颜色差就有可能会超过一个最小量化步长这时候你看到的就是带状变化而不是连续渐变。以我的理解色带的本质是量化误差被人眼感知到了。单纯的增加分辨率没用因为色彩的位数没变。Dither做的事情是在量化之前让信号本身叠加一个“微小的、有规律的扰动”再让这个带扰动的信号通过量化器。这样一来原本会在同一色阶内抱团的像素因为扰动值不同被抖到了不同的色阶从宏观看色带的边缘被打散成了细密的噪点视觉上就变成了平滑过渡。很多人以为Dither就是“加噪点”这没错但“加噪点”并不等于“让画面变脏”。如果你在Shader里直接给颜色加一个Random值那只会得到雪花噪点Dither的精髓在于叠加的噪声形态是可控的且噪声的空间分布是有相关性的至少要做到“相邻像素之间存在一定规律”才能让视觉系统自动把它们平均成中间色。1.2 半透明渲染的排序难题Unity里做半透明特效第一个噩梦就是排序。Blend SrcAlpha OneMinusSrcAlpha虽然好用但Unity默认的排序是基于物体中心的遇到交叉重叠的Mesh、粒子和UI就会时不时出现“后面的物体被前面的遮住但其实应该透过来”的尴尬情况。你也许试着把RenderQueue调低、关掉ZWrite甚至给Material换成Transparent但问题依然周而复始。Dither在这种场景下提供的是一种“伪半透明”思路。既然真正的混合排序不可靠那我就让物体在像素级别上选择“完全显示”或“完全丢弃”通过对每个像素设置不同的裁切阈值模拟出整体半透明的观感。比如一片草叶我让它内部一半像素显示、一半像素丢弃而且按Bayer矩阵规律分布那你从远处看它就是半透明的却不需要Blend也不参与混合排序因为它本质上还是不透明物体ZWrite和ZTest都正常。这种方案在植被、粒子、甚至部分角色特效里非常常见它能彻底绕开Sorting Order和透明物体合批的问题。有一点要注意这种“伪透明”的代价是像素级闪烁和纹理细节下降。因为被丢弃的像素永久“消失”了如果你把摄像机拉近能看到明显的跳变和齿状边缘。Dither不是让你无脑取代真半透明而是在排序不稳或性能受限时提供一个可靠、稳定的备选方案。1.3 用噪声换取视觉上的“平滑”从信号处理的角度来看Dither的核心作用可以概括为“以噪声换平滑”。很多人也许听说过音频处理里也有Dither原理极其类似在量化前添加一个功率极低的随机信号让微弱的声音在数字系统中也能被保留而不是被一刀切进静音。图像领域的Dither就是把“量化误差”从“有规律的带状”变成“无规律的随机散布”这样人眼反而会进行低通滤波把这些随机点平均进去。我在面试一些图形岗位时经常会让对方解释“为什么渐变图加了0.5的随机抖动之后反而不看到色带了”。很多人能回答出“打破了量化边界”但很少有人能讲清楚“为什么是恰恰好的噪声幅度”。如果噪声幅度太小它不足以跨越量化间隔如果噪声幅度太大它会掩盖真实信号产生“颗粒感”。Dither的平衡点通常是让噪声的峰值约等于一个量化步长LSB。在8bit颜色中这个幅度大约是1/255在Shader里你常常会把返回值控制在0到1之间然后乘以一个很小的系数去偏移最终颜色。我做Dissolve效果时也经常用Dither来消除“边缘的锯齿感”。边缘像素的alpha正好在0到1之间如果直接clip边缘会像狗啃过一样。但如果在clip之前对alpha加上一个受像素位置影响的Dither值边缘就会变成随机分布的半透明点这在视觉上比锯齿柔和得多也更容易结合风格化处理。2. 两类常见的Dither实现方案2.1 有序抖动Bayer矩阵的算法与Unity代码最经典的Dither方案是使用Bayer矩阵。它的思想特别简单准备一个n×n的矩阵矩阵里的每个值不同代表不同的阈值。片元着色器里我们用屏幕坐标或UV坐标模上矩阵尺寸取出对应的阈值与当前要判断的alpha值做比较决定丢弃还是输出。因为Bayer矩阵的数值在空间中是有规律的所以呈现出的点阵会有一种“有序的颗粒感”这也是很多像素风游戏喜爱它的原因。拿一个4×4的Bayer矩阵来举例典型的样子长这样0/16, 8/16, 2/16, 10/16, 12/16, 4/16, 14/16, 6/16, 3/16, 11/16, 1/16, 9/16, 15/16, 7/16, 13/16, 3/16这里需要修正一下标准矩阵是有递推关系的刚接触时我不会背矩阵后来搞明白了它的生成方式。Bayer矩阵是递归出来的2×2的矩阵是基础更大尺寸依次递推。在Unity Shader里你完全不用在代码里生成矩阵可以直接把这16个值写死成一个常量数组然后static const float Bayer4[16] { 0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0, 12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0, 3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0, 15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0 }; float bayer4(float2 uv) { int2 pixel int2(frac(uv) * _ScreenParams.xy) 3; return Bayer4[pixel.y * 4 pixel.x]; }注意上面我修正了最后一个值因为标准Bayer矩阵里面最后一行最后一个应该是5/16而不是3/16这里敲代码时很容易抄错抄错会导致整个阈值分布不对称画面出现斜向条纹。用屏幕坐标换算的好处是抖动位置不随UV拉伸变形始终保持在屏幕像素的格子上。2.2 随机抖动哈希噪声与片元坐标有序抖动的问题在于它有明显纹理稍微离远一点看就像表面铺了一层网格。如果你想要更自然、更细腻的抖动效果可以考虑用随机噪声。这里的随机噪声不是指Unity里的PerlinNoise而是Shader里基于整数坐标做哈希运算得到的伪随机数。比较常用的一个哈希函数是float hash12(float2 p) { p frac(p * float2(123.34, 345.45)); p dot(p, p 34.345); return frac(p.x * p.y); }这属于“穷人版”随机数做视频特效时够用但用于Dither时有个问题如果每个像素都是完全独立的随机数画面会出现大面积的高频闪烁尤其是在半透明边缘或相机运动时会像一堆小虫子在爬。为了削弱这种闪烁我们可以采用“交织”的随机数或者混合Bayer和随机数比如在Bayer矩阵的基础上加一个非常小的扰动系数。我自己在实际项目里喜欢用“Bayer 时间变化的小扰动”来做动态效果既能保持一定的空间规律又能让边缘“活”起来。随机噪声的优点是没有规则网格适合表现沙尘、烟雾、破裂等自然效果缺点是它在时域上容易不稳定一旦相机移动或物体形变像素点的坐标变化会让随机结果跳动得很厉害。为了解决这个问题你可以在计算随机数时把顶点在一个固定空间的坐标作为输入而不是用屏幕坐标。这样物体在移动时它表面上的抖动图案保持不变就像是材质本身自带的纹理只是视觉上弱化了。2.3 为什么抖动阈值要控制在“刚好”的范围无论是Bayer还是随机噪声抖动阈值都不是越大越好。我做溶解效果时犯过一个经典错误为了让边缘更碎把阈值范围调到了-0.3到1.3结果不仅边缘碎了整个物体都变成“筛子”了。阈值范围过大的本质是让最终用于比较的alpha做了过度的非线性扭曲。比较安全的做法是阈值保持0到1的区间把对alpha的调整控制在±0.1左右的范围内。如果你需要更大的边缘破碎感更多的要靠噪声的分形叠加而不是无脑拉伸阈值。从信号角度看阈值就是量化器的决策边界。Dither只是让边界“抖动”起来而不是让边界消失。如果你把阈值跨度调成2那实际上已经不是“Dither”而是在做“随机图案生成”画面会丢失原始alpha信息。我通常会在Shader属性里单独暴露一个Tolerance参数让美术在编辑器里调范围限定在0.02到0.15之间这样至少能保证基础效果不会崩。3. Unity Shader中的落地从原理到代码3.1 基于透明度裁剪的Dither Dissolve效果Dissolve是Dither最常见的应用场景也是我强烈建议Shader初学者上手练习的第一个案例。它的需求很简单让物体从完整状态逐渐消失到完全不见但消失边缘要有一种“颗粒状”的过渡而不是一刀切。贴上一张噪波图作为alpha来源叠加Bayer矩阵做Dither在片元着色器里fixed4 frag(v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); float noise tex2D(_NoiseTex, i.uv * _NoiseScale).r; float alpha noise; alpha _DissolveAmount * 1.2; float bayer bayer4(i.screenPos.xy); clip(alpha - bayer); return col; }建议在Clip前把alpha做一次Smoothstep让边缘的梯度更明显。实际做的时候可以把_DissolveAmount理解为“当前阈值”当它从0增长到1时物体从有到无。Bayer矩阵在这里的作用不是修饰最终颜色而是修饰“clip边界”。当alpha值接近bayer阈值时部分像素被丢弃部分保留从而形成边缘的破碎效果。用Bayer还有个好处你可以通过调节_ScreenParams来匹配不同分辨率确保边缘颗粒不会因为屏幕分辨率不同而忽大忽小。我这里要提醒一个问题直接Clip会让物体在非透明渲染队列里产生硬边缘而且由于被丢弃的像素深度值不会写入这些区域可能会透出背后的物体。如果你的物体背后还有另一个半透明物体他们的排序关系会变得微妙。一种补救措施是让Dither边缘尽量窄控制在1-2个像素内这样视觉上影响不大。3.2 结合Bayer矩阵的伪随机淡入淡出有时候我想让一个物体淡入淡出但又不能污染它的颜色和半透明状态因为它是被其他逻辑驱动的。比如一个敌人的护盾从无到有但又不想引入Blend导致渲染顺序变化。这个时候我会用Dither做成“伪淡入”物体始终是不透明的但通过Dither把一部分像素的颜色输出一部分像素直接丢弃从远处看它就是透明度逐渐升高的。这其中的关键是要对最终颜色做处理而不是对alpha做处理。假设我要让物体在整体上看起来是30%的透明度那我需要让70%的像素被丢弃这些像素按Bayer矩阵的顺序选择。当透明度动态变化时被丢弃像素的比例也随之变化。唯一要注意的是如果物体表面存在大面积纯色这种伪淡入会产生“像素点斑驳”的感觉尤其是近距离看时并不平滑。如果你追求的是“电影级淡入”还是老老实实用Blend吧。伪淡入的优势在于不需要深度写入控制也不需要在Transparent队列里排序。而且在移动端Overdraw高的半透明物体会导致GPU负担加重伪淡入的不透明渲染成本比真半透明低得多。如果你的目标平台是老手机或低端机这种操作算是一种不错的优化手段。3.3 在头发Shader与粒子特效中的实战注意点头发Shader是Dither另一个重要战场。大家搜“头发shader”很多结果都会提到Kajiya-Kay模型或者是基于Dither的Alpha Cutout。头发的特点是细碎的线条特别多如果直接做半透明混合会产生大量Overdraw并且排序还容易穿帮如果直接Clip又因为锯齿问题导致视觉效果很差。所以在头发渲染中Dither可以作为Clip的辅助把边缘像素随机丢弃一部分让头发轮廓看起来更软。但要注意头发丝的密度本来就是靠空间高频信号来体现的如果Dither阈值过大会让头发丝出现闪断看起来像信号不好一样。粒子特效也是Dither的常客。做过粒子的人都有体会粒子特效做半透明时Overdraw和排序问题特别让人头疼。我在优化粒子内存和DrawCall时发现有些粒子特效其实不需要只能显示混合模式比如烟雾、灰尘用Dither Cutout配合专门的噪点图反而能获得更实的质感和更低的填充率。当然这要求美术重新调整粒子材质和透明度曲线很多习惯看半透明叠加效果的人一开始会不适应但熟悉之后就会解锁很多新玩法。我在之前的工作中还处理过一个“粒子特效内存泄露unity”的案例当时那个项目里粒子系统的Material在运行时不断创建新实例而不是复用导致内存暴涨。排查发现Shader里用了带非uniform的clip阈值并且材质属性在每帧被修改。其实凡是涉及clip和Dither的Shader都要特别注意平台差异尤其是移动端有时候clip次数太多会影响Early-Z优化。在我的经验里粒子用Dither时尽量用单通道噪点图且阈值不要做过于复杂的计算否则粒子数和屏幕覆盖范围一大帧率能跌到你怀疑人生。3.4 与Unity URP/HDRP管线的兼容性问题排查现在大部分新项目都用URP老的Built-in管线脚本用到新管线里经常出错最常见的是材质变成紫红色。原因多半是Shader里用了光照模型相关的内置函数比如float4 frag(...):SV_Target这种写法在不同管线中支持的宏不同。在URP里写Dither必须记得加#include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl并且把_ScreenParams改成_ScaledScreenParams因为URP有时候会做动态分辨率直接用_ScreenParams会导致抖动矩阵的坐标错位。另外一个容易忽略的坑是URP的UnityGetPositionCS和GetVertexPositionInputs等空间变换函数可能与Built-in的语义不完全一致如果你的Bayer矩阵里用了屏幕坐标一定要确认输入的pos是经过ComputeScreenPos映射过的。HDRP则更严格对ZWrite和ZTest的权限管理更重Shader里如果同时有Dither和CameraPass容易出现深度冲突。我遇到过最诡异的一次是同样的Shader在Editor里正常打包后材质变紫红色而且只在Android上崩。定位了很久最后发现是Shader代码里用了一个未定义的宏做条件编译导致URP里的ShaderVariant不完整。所以这里建议写完Dither的Shader一定要手动在Build Settings里勾选上对应平台做一次Shader预编译别只依赖Editor下的预览。3.5 用Dither优化移动端半透明植被的性能最近在优化开放世界场景时我把一大片草和树叶都从Transparent改成了CutoutDither方案。原因很简单移动端的GPU填充率有限整片植被如果走Transparent队列哪怕闭合区域不大也会因为混合计算导致帧时间飙升。改成Cutout后草叶的每一根叶片都作为不透明几何体绘制只有边缘用Dither做抗锯齿模拟帧率提升非常明显。有两个细节给我留下深刻印象一是半透明植被的阴影问题。用Blend的植被在阴影图中没法正常投射要么单独定制ShadowCaster要么用ShadowOnly麻烦得很。而CutoutDither植被的ShadowCaster可以直接用clip阴影生成逻辑稳定。二是Alpha Test带来的带宽吃紧。也就是虽然不透明片的填充率低了但Cutout物体导致的Alpha Test分支也会增加一些开销。所以我在Shader里设置了LOD并分支控制近处的草用8x8 Bayer远处的草用4x4 Bayer更远的直接用4x4 Bayer距离判断做裁剪。通过这些手段整片草地的性能从500帧下降到400帧但视觉效果基本没变。4. 常见问题、参数调优与避坑实录4.1 画面闪烁shimmer与抖动的时间稳定性用Dither出问题最多的就是画面闪烁尤其是摄像机移动时半透明边缘或远处的网格会像“雪花”一样闪烁。原因通常有两个其一抖动图案直接跟随屏幕坐标在摄像机移动时同一个物体表面像素的抖动值每帧都在变化其二多个物体共用同一个Bayer矩阵在深度穿插或集群出现时彼此的阈值会互相干扰。解决办法之一是让抖动图案固定到“世界空间”或“物体空间”。例如用物体的局部坐标作为Bayer矩阵的输入物体不动时其表面图案保持稳定物体移动时图案也跟着物体走而不是跟着屏幕走。对单个物体来说这样比跟随屏幕坐标“稳”得多。另一种办法是使用“Temporal Stable”的噪声比如TAA的抗闪烁噪声它每一帧对同一个像素都给相同的随机数采用类似情感化的数列方式降低帧间差异。当然如果是要做动态溶解比如某个东西正在消失闪烁反而是“动效”的一部分这时候可以保留屏幕空间坐标因为它能让消失的颗粒更随机、更飘逸。总之没有绝对正确的方案只有适合场景的方案。4.2 Dither的副作用噪点、阴影与抗锯齿Dither引入的噪点在某些场合是致命的。做得太过头会让物体表面看起来“脏兮兮”的尤其漫反射贴图本来就暗的时候那些被丢弃的像素会让暗部失去细节。这时候可以用floor(frac(... ) * n)生成低频的阈值梯度或者使用一个平滑的噪声UV坐标让颗粒分布更稀疏、更规则。阴影和抗锯齿的冲突也很常见。当Dither材质被用来写阴影贴图时如果你在ShadowCaster Pass里也做clip那么阴影边缘也会出现同样的颗粒这可能让阴影看起来“像虫子啃过一样”。我一般会在ShadowCaster Pass里使用单独的阈值比如固定为0.5不参与Dither这样阴影边缘就是整洁的。如果你想要阴影也带柔边可以再写一个平移过的阈值但那个会很考验数学功力。抗锯齿问题又是另一层。MSAA无法对clip的像素做光滑处理因为被丢弃的像素根本不会产生覆盖度信息。但如果你用的是TAA或FXAA情况会稍微好一点因为后处理是基于最终颜色的Dither颗粒会被后处理略微模糊反而会显得柔和。所以Dither和抗锯齿的配合没有标准答案取决于你的项目用哪种AA方案。4.3 调试技巧用Material面板实时调整阈值写Dither Shader时我强烈建议在Material的Inspector面板上把关键参数暴露出来。比如阈值强度、噪声缩放、噪声对比度、Bayer矩阵大小。这样美术和主程在场景里微调时不用改代码很快就能找到合适的值。具体来说我一般会暴露这样一组属性_DitherAmount全局Dither强度通常0.1到1.0。_BayerTexelBayer矩阵的尺寸1代表4x42代表8x8。_EdgeWidth控制边缘宽度值越大边缘破碎范围越广。_NoiseSpeed如果做动态效果这个是噪声随时间变化的速度。调试的时候先把_DitherAmount拉到最小然后调_EdgeWidth到满意最后调_BayerTexel。你会发现不同分辨率下Bayer矩阵尺寸对颗粒大小的影响特别明显。在手机上8x8Bayer在低分辨率下容易出“大和田格子”4x4则柔和得多。但如果要求极高品质有时还得用16x16就要在Shader里多做几次矩阵采样或换用纹理采样方式。4.4 一些偏门但好用的扩展玩法除了常规的Dissolve和头发Dither还能干很多好玩的事。比如做像素化风格化渲染时把Bayer矩阵映射到亮部和高光就可以产生经典的“网点纸”质感很多日系游戏里那种“杂志网点阴影”就是这么干出来的。做法就是在漫反射和阴影之间根据亮度差值插入一个Dither阈值让亮部和暗部的分界不是一条直线而是密集的小点。再比如可以把Dither用在“地图热力图”上降低色带的同时增加一点随机的材质感。如果你用Unity做数字孪生项目需要在建筑表面显示热力数据渐变Dither可以很好地消除色带但对于大面积纯色区域还要配合透明度或渐变纹理否则会影响真实感。另一个玩法是把Dither和描边结合。描边效果通常在法线或深度边上如果你让描边区域内的像素用Dither丢弃一部分就能得到“碎裂描边”效果用在那种科技感UI或科幻风格的敌人血条上特别带感。还有的人用Dither做三角形按钮的毛边效果不过那个更偏像素风追求的是规则的锯齿而不是平滑过渡。5. 实操案例一个完整的Dither消融效果Shader为了让上面的内容不空谈我来给出一份可以直接复制到URP工程里的完整Shader它实现了Dither消融效果并带有一个根据距离自动改变颗粒大小的功能。你可以把它作为模板改改贴图和参数就能用到实际项目里。Shader Custom/DitherDissolveURP { Properties { _MainTex (Texture, 2D) white {} _NoiseTex (Noise, 2D) white {} _Dissolve (Dissolve, Range(0,1)) 0 _DitherAmount (Dither Amount, Range(0.02, 0.2)) 0.1 } SubShader { Tags { RenderTypeOpaque RenderPipelineUniversalPipeline } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl // 4x4 Bayer矩阵 static const float Bayer4[16] { 0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0, 12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0, 3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0, 15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0 }; struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; float4 screenPos : TEXCOORD1; }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); TEXTURE2D(_NoiseTex); SAMPLER(sampler_NoiseTex); float4 _MainTex_ST; float4 _NoiseTex_ST; float _Dissolve; float _DitherAmount; float bayer4(float2 pos) { int2 pixel int2(floor(pos)) 3; return Bayer4[pixel.y * 4 pixel.x]; } Varyings vert (Attributes v) { Varyings o; o.positionHCS TransformObjectToHClip(v.positionOS.xyz); o.uv TRANSFORM_TEX(v.uv, _MainTex); o.screenPos ComputeScreenPos(o.positionHCS); return o; } half4 frag (Varyings i) : SV_Target { half4 col SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv); float noise SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, i.uv).r; float alpha saturate(noise - _Dissolve * 1.2); #ifdef _DITHER_ON float2 screenUV i.screenPos.xy / i.screenPos.w; float dither bayer4(screenUV * _ScreenParams.xy); alpha alpha (dither - 0.5) * _DitherAmount * 2; #endif clip(alpha - 0.5); return col; } ENDHLSL } } }我这里故意没有开启多编译变体把_DITHER_ON默认为注释状态。如果你希望Dither可选在Pass前面加#pragma multi_compile _ _DITHER_ON然后在Shader的材质面板上勾选Keyword。具体操作是在Material Inspector里打开Shader Properties的Keyword列表把_DITHER_ON启用。很多小白在这里容易犯迷糊因为不确定是自己代码写错了还是Keyword没开其实用#pragma shader_feature_local _DITHER_ON可以更好地限定到本地。在应用这个Shader前你需要准备一张噪点贴图最好是那种低频噪声比较多的图不要用整齐的方格否则消融边缘会显得“一条一条”的。我一般用Photoshop里的云彩滤镜生成然后缩小到128x128再开启Repeat模式。如果你手头没有合适的图也可以用代码在Shader里生成但那样会增加指令数不推荐。我测试这个Shader时是在1920x1080的分辨率下把_Dissolve从0调到0.5观察消融过程。边缘的颗粒会随着_Dissolve增大而变得越来越密集最后物体彻底消失。同时我不断调整_DitherAmount发现0.08到0.12之间视觉效果最好超过0.15就会出现空洞和闪烁低于0.04又几乎看不出Dither的作用。6. 结合Unity其他热词的杂谈每次看到网上大家都在搜“unity安装”“unity 6”或者“unity操作教程”我就想起刚入行时被Shader编译错误折磨的日子。其实Dither只是Shader世界里一个很小的点并不存在什么“正确的Unity安装方式”能帮你解决渲染问题更多的还是在实践中积累。把Dither学会后你会发现自己对CLip、对Alpha Test、对RenderQueue的理解都上了一层楼。你会慢慢明白为什么有些材质变成紫红色不是因为代码语法错了而是因为Shader变体和管线的定义不对而这些恰恰都是Dither实战里最常遇见的坑。我最近还在研究Mesh Shader也就是PS5支持的那个特性。很多人问“PS5支持Mesh Shader吗”其实它和Dither并不冲突Mesh Shader主要在几何阶段做优化而Dither是像素阶段的技巧。未来在GPU-driven管线里Dither可能更多会用到VRS可变速率着色或采样器反馈里比如让边缘的抖动频率与着色率匹配这将是一个很有意思的进阶方向。不过眼下还是先把基础的Dither用熟练比什么都重要。另外有人抱怨“unity物体速度怎么获取”这个话题看起来和Shader无关但如果你正在做车辆或闪电效果想要让速度影响消融的边缘形状就需要在C#脚本里获取物体的速度并把变量传给Material。具体方法先获取物体刚体速度的向量取一个标量缩放后使用MaterialPropertyBlock.SetFloat(_SpeedDissolve, value)每帧更新。这种传参方式比直接改Material属性高效得多而且不会生成新的材质实例能有效降低DrawCall风险。我在跑通全身Dither的人了个人强烈建议你先别急着去背矩阵而是先用一个简单的平面、一个噪点贴图把Bayer4的代码抄进Shader里跑通然后调参观察。等你能清楚地说出“为什么Bayer矩阵中间的值是2/16而不是3/16为什么最后一行的末位是5/16而不是4/16”的时候你才算真正入了Dither的门。那层窗户纸一旦捅破再去做溶解、伪透明、像素画、头发透光这些效果都会顺畅很多。以后你在群里看到有人问“Dither就是加噪点吗”你也能坦然告诉他加噪点只是手段改变量化误差的分布才是本质。
返回列表