资讯动态

Unity透明纹理实战:从Shader到视频抠像的完整方案

发布时间:2026/10/20 3:21:44 来源:尧图企业网站定制
1. 项目概述做Unity开发这么多年透明纹理这块说难不难说简单也真容易翻车。标题里这句“靠谱可用”四个字看着朴素其实背后藏着不少坑——网上搜到的方案要么是七八年前的旧代码要么只讲了图片不讲视频要么在Built-in管线里好好的切到URP就彻底失效。这篇文章把我实战验证过的做法完整写出来包含图像和视频两条路径Shader代码可以直接抄走参数含义也一并说清楚。这个需求的常见场景一个是展厅里的全息投影屏一个是虚拟演播室的背景大屏还有游戏里的粒子特效、广告牌、UI特效复用。本质就一句话让Unity 3D对象表面的纹理呈现透明效果而不是把贴图里的黑色或白色区域硬生生显示出来。如果要给视频实现透明还得额外处理视频播放器的透明通道问题。适合谁看刚接触Shader的新手能在这里搞懂渲染队列、混合模式这些基础概念有经验的开发者可以直接跳到视频透明部分的方案选型看我对VideoPlayer和RenderTexture几种实现方式的对比。从原理到踩坑一次讲透。2. 为什么透明纹理在Unity里不能直接用2.1 默认材质的限制很多初学者会直接把一张带透明区域的PNG拖到Standard材质上然后发现完全不透明。原因很简单Unity的Standard Shader默认走的是不透明渲染路径它把纹理的RGB颜色读出来Alpha通道直接丢弃了。你需要明确告诉渲染管线“这个物体要参与透明混合计算”而不是指望引擎自动识别纹理里的透明像素。在做实验的时候你可以在材质球上把Surface Options里的Surface Type从Opaque改成Transparent这样Standard材质也能表现出简单透明效果。但Standard材质的问题是性能开销偏大内部包含大量PBR光照计算对透明渲染来说很多计算是白做的。而且如果多个透明物体互相重叠Standard材质的深度写入处理相当粗糙经常出现排序错乱、相互遮挡穿帮的画面。对于视频透明这种需要抠像或Alpha通道连续变化的场景手写一个专门用于透明混合的Unlit Shader才是更值得走的路。这也是本文所有方案的基础。2.2 渲染队列与ZWrite的底层逻辑透明渲染在Unity里要理解两个核心概念渲染队列Render Queue和深度写入ZWrite。渲染队列决定一个物体在渲染管线中被提交的顺序。数值越小越先被渲染。Unity预设了Background1000、Geometry2000、AlphaTest2450、Transparent3000、Overlay4000这几档。固体物体一般在Geometry玻璃、粒子等透明物体放在Transparent。把透明物体放在后面渲染的理由很简单普通不透明物体需要先画进深度缓冲后面再画透明物体时深度测试能正确遮挡住不该看到的部分。如果反过来先画的透明物体会被后画的不透明物体直接覆盖掉效果完全乱套。ZWrite指是否在渲染时向深度缓冲写入数据。这里有一个看起来矛盾但实际很合理的经验透明物体通常要关闭ZWrite。因为透明混合的意义是让后面的物体颜色透过前面的物体如果开启ZWrite前面透明物体会把自己的深度写进缓冲挡在它后面的物体深度测试不过就不会被渲染透明的意义就没了。但关掉ZWrite会带来排序问题。渲染管线是按物体中心到相机距离排序的如果两个透明物体相交或缠绕排序容易出错出现“后面物体被前面物体错误遮挡”的异常。这个问题在粒子系统尤其常见。解决办法是尽量控制透明物体的重叠比如广告牌之间保持间距或者用一些技巧调整渲染顺序而不是指望Shader一劳永逸。还是用一个生活类比来解释吧。就像透过毛玻璃看后面的人毛玻璃自己不把深度信息刻在墙上后面的人才能被看见。但两块毛玻璃前后叠放你先看哪块、后看哪块就有讲究了。2.3 混合模式Blend Mode到底在混合什么混合模式是透明效果的关键公式。Shader里常见的写法是Blend SrcAlpha OneMinusSrcAlpha这行代码要翻译成数学语言意思是片元颜色乘以自身Alpha作为源颜色目标缓冲颜色乘以1减Alpha作为目标颜色两者相加得到最终的屏幕颜色。当Alpha等于1时完全显示当前像素不透明当Alpha等于0时完全显示背景颜色当前像素不可见Alpha为0.5时则各自贡献一半。最终颜色 源颜色 × Alpha 目标颜色 × (1 - Alpha)这就是标准透明度混合公式。理解了它你就不会把“透明”理解为“看不见”。还有两种额外情况要区分。Alpha Blend是标准的半透明混合适合玻璃、烟雾、全息投影。Alpha Test则是直接裁剪像素Alpha小于某个阈值通常是0.5直接丢大于阈值就保留完全不透明。边缘非常硬没有过渡。这个适合树叶、铁丝网、头发这些需要完全不透明的“镂空”贴图但锯齿明显需要配合抗锯齿措施。2.4 内置管线、URP、HDRP的差异很多从老教程里抄的Shader代码在URP项目里会出现紫粉色错误因为内置管线的Shader结构变了。实际上URP不仅改变了Shader的组织形式还改变了光照模型Tags里需要额外声明渲染管线的标识。具体差异点在于内置管线的Shader用Pass块直接写结构相对自由。URP里要用HLSLPROGRAM块配合UniversalRenderPipeline的引用库。URP的Shader Graph为美术同学提供了更友好的操作界面连节点就行不需要碰代码。HDRP更复杂透明、贴花、体积光都是独立框架不适合新手。我一般建议如果只是做透明纹理展示而且项目是URP优先用Shader Graph的Unlit Master节点能省掉大量调试时间。但如果你需要完全掌控代码或者项目用的是内置管线下面这套手写Shader的方案更通用。3. 图像纹理透明实操一张图走通全流程3.1 贴图导入设置的正确姿势我踩过的第一个坑是贴图带Alpha但导入时被Unity强制压缩成不透明格式。导入任何带透明通道的图片都要在Inspector面板检查以下设置Alpha Is Transparency必须勾选。这个选项告诉Unity这张纹理的Alpha通道代表透明信息而不是透明度贴图或金属度贴图。如果图片是线性工作流的注意sRGB选项。一般彩色贴图保持sRGBColor即可法线贴图、数据贴图要设为None。压缩格式建议用ASTC或ETC2对透明通道支持比较完整。旧式RGBA16压缩格式在移动端表现不佳。一个容易忽略的细节Alpha通道的位深。如果你从Photoshop导出的是8位通道而项目用了HDR渲染或某些特殊后处理带渐变的Alpha边缘会出现色阶断层产生丑陋的锯齿状半透明边缘。这种场景建议导出16位PNG或TGA。3.2 最简Transparent Shader的完整实现下面这个Shader是我在多个项目里实际用过的基础版本核心代码不到三十行但完成了渲染队列、深度写入、混合模式三个关键配置Shader Custom/SimpleTransparentImage { Properties { _MainTex (Texture, 2D) white {} _AlphaScale (Alpha Scale, Range(0,1)) 1.0 } SubShader { Tags { QueueTransparent RenderTypeTransparent } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float _AlphaScale; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); col.a * _AlphaScale; return col; } ENDCG } } }几个细节值得说。_AlphaScale参数用来统一调整整张贴图的透明度比如UI交互时做一个淡入淡出效果不需要改贴图资源调材质参数就行。Cull Off表示双面渲染有些广告牌位移后法线方向反转正面背对相机时不会被剔除。做透明效果时通常建议双面渲染因为单面剔除很容易出现背面视角下物体消失的意外。实际使用中把材质球的Shader切换为这个自定义Shader再把带Alpha的贴图拖到_MainTex槽位透明的部分就直接生效。3.3 让透明边缘更平滑的抗锯齿技巧很多人在这一步遇到问题PNG中边缘的半透明渐变区域显示正常但边缘一圈出现黑色或白色描边。原因通常是纹理压缩格式对Alpha通道做了有损处理或者图片本身带了白色背景没有完全抠干净。除掉重新处理图片之外Shader层面还有一道防线就是在采样之前对透明区域做模糊处理。但最实用的还是用Gamma校正来处理采样值的Gamma空间到线性空间的转换问题。边缘变黑最常见的原因就是贴图在sRGB空间被读取而混合计算在线性空间执行导致Alpha和RGB不匹配。解决方案col.rgb / max(col.a, 0.001);这叫预乘Alpha修复适合处理颜色边缘发暗的情况。使用时要考虑贴图到底是直通Alpha还是预乘Alpha两者不能混用。普通的PNG是直通Alpha贴图但某些渲染软件导出的是预乘格式这时候就用上面的修复方式。另一个常见需求是边缘羽化也就是让硬度不同的边缘有柔和的过渡。用主纹理的Alpha作为输入再用Smoothstep算法做一次边缘过渡float alpha tex2D(_MainTex, i.uv).a; alpha smoothstep(_EdgeThreshold - _EdgeSoftness, _EdgeThreshold _EdgeSoftness, alpha);这个技巧适合制作公告牌、贴花、破坏效果。不过要留意smoothstep在URP的节点里可用但手写Shader要确保版本兼容。3.4 透明物体的深度测试陷阱透明物体默认ZWrite Off但深度测试ZTest是可选的通常是ZTest LEqual保持默认即可。如果要实现某些特殊效果比如透视墙后面显示轮廓或强制显示被遮挡的透明物体可以设置为ZTest Always但绝大多数业务场景不需要改这个。真正麻烦的是自遮挡问题。比如一个透明圆环从侧面观察时正面部分和背面部分重叠因为关闭了ZWrite前面像素的深度值不会挡住后面像素前后部分依然同时参与混合就会出现重复叠加的“发白”效果。解决方案有两个用ZWrite On配合QueueAlphaTest模拟Alpha Test但这样会失去半透明混合效果。用两个Pass先写深度再混合也就是经典的“深度预写透明”技巧适合表现单面物体的正确遮挡。其实大部分图像透明场景不需要这么复杂。多叠加快照时确实会有问题比如粒子系统的火焰叠加但这是引擎级限制而不是Shader能完全解决的。可以用减少重叠、调整相机远近裁剪等方式改善。4. 视频透明纹理的实现方案4.1 视频带不带Alpha通道决定了走哪条路视频透明和图像透明最大的区别在于视频源的编码方式。常见的H.264、H.265编码格式在常规情况下只存储YUV色彩数据不支持Alpha通道。因此一个视频文件即使看起来有透明需求它在内部也不会保存透明度信息。行业内做视频透明通常只有两条路使用支持Alpha通道的编码格式比如Apple ProRes 4444、VP9的alpha模式、某些WebM格式。使用绿幕/蓝幕视频在运行时通过Shader实时抠像把绿色或蓝色背景移除。第一种效果好但兼容性差第二种兼容性好但对拍摄要求高抠像边缘处理不好容易发绿或发毛。Unity的VideoPlayer在Android和iOS上格式支持不尽相同有的系统根本不认ProRes这就更让方案选型变得重要。我这里推荐的稳妥路线是这样的如果是宣传片、产品展示能拿到带Alpha的mov素材优先走VP8/VP9的alpha方案或是ProRes配合特定系统的解码。如果视频素材是绿幕拍摄走实时抠像路线在Shader里把绿色区域的像素Alpha设为0这个方案兼容性最广。如果只是需要视频整体淡入淡出不需要抠像直接把视频当普通纹理渲染用_AlphaScale参数统一调节即可。4.2 VideoPlayer与RenderTexture的基础流程让VideoPlayer播放视频并传给材质基础流程分三步场景中创建VideoPlayer组件指定VideoClip或URL。创建RenderTexture将VideoPlayer的targetTexture指向这个RenderTexture。材质的_MainTex属性设为这个RenderTextureShader逻辑与图像透明完全一致。关键代码using UnityEngine; using UnityEngine.Video; public class VideoTextureAssigner : MonoBehaviour { public VideoPlayer videoPlayer; public RenderTexture renderTexture; public Material targetMaterial; void Start() { if (videoPlayer ! null) { videoPlayer.targetTexture renderTexture; videoPlayer.Prepare(); videoPlayer.prepareCompleted OnPrepared; } } void OnPrepared(VideoPlayer vp) { targetMaterial.mainTexture renderTexture; vp.Play(); } }注意一定要等Prepare完成后再给材质赋纹理。否则材质会拿到空RenderTexture显示纯白或黑屏。另外RenderTexture的分辨率应尽量与视频原始分辨率匹配不匹配时会做缩放造成模糊。4.3 绿幕视频的实时抠像Shader绿幕视频的Shader抠像核心是色度键控。思路是检测像素颜色与背景色的色差接近背景色的像素Alpha归零否则保留完整前景色。一个简易实现Shader Custom/ChromaKeyUnlit { Properties { _MainTex (Texture, 2D) white {} _KeyColor (Key Color, Color) (0, 1, 0, 1) _Sensitivity (Sensitivity, Range(0,1)) 0.2 _Smoothness (Smoothness, Range(0,1)) 0.1 } SubShader { Tags { QueueTransparent RenderTypeTransparent } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _KeyColor; float _Sensitivity; float _Smoothness; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); float dist distance(col.rgb, _KeyColor.rgb); float alpha 1.0 - smoothstep(_Sensitivity, _Sensitivity _Smoothness, dist); col.a * alpha; return col; } ENDCG } } }实拍时绿幕的光照不均匀会导致部分区域抠不干净。因此在实际生产环境中这个Shader还要增加边缘保留、去溢色Spill Suppression等额外功能。去溢色的常用思路是让绿色通道值向红色和蓝色通道收敛减小前景物体边缘的绿光污染// 去溢色简化实现 float spill max(0, col.g - max(col.r, col.b)); col.g - spill * _SpillStrength;如果抠像对象是人物头发丝边缘尤其容易抠出绿色毛边。配合后处理或二次抠图硬件够的话还可以用键控专用算法。但要注意如果项目里跑的是Mobile平台或低端设备这些额外的计算和纹理采样会造成性能压力。4.4 VideoPlayer播放透明视频的兼容性经验关于VP9或带Alpha WebM的播放Unity在不同平台支持情况差异很大。Windows平台可以用VideoPlayer直接播放VP9带alpha的WebM但Android上表现不稳某些渠道包甚至直接无法解码。要保证全平台可用我的经验是准备两套素材高清平台用带Alpha的WebM。低端或兼容性差的平台用绿幕素材加Shade运行实时抠像。如果你需要视频压缩到小体积又想保留透明效果可以从视频编码层面降低帧率或分辨率透明视频体积通常是不透明视频的两到三倍。别把4K透明视频直接丢到移动端卡成幻灯片是必然的。关于RenderTexture设成透明格式其实Unity的RenderTexture默认使用ARGB32支持透明。但需要手动确认创建时指定RenderTextureFormat.ARGB32并设置depthBuffer为0或16。如果用了DefaultFormat在某些平台可能被解析成不透明格式哪怕视频有Alpha最终也看不到透明效果。5. Shader Graph方式实现透明纹理URP专用5.1 Unlit Master节点的配置要点如果你的项目管线是URP用Shader Graph来做透明纹理其实比手写Shader更省心。创建一个Unlit类型的Shader Graph然后在Master节点上把Surface设置为TransparentBlend Mode选Alpha即完成了最核心的两步。Graph里只需要加一个Sample Texture 2D节点把纹理的RGBA输出连接到Master节点的Base Color和Alpha上。注意如果只是连Base Color透明通道不会自动生效必须单独连Alpha。这是美术同学最容易漏掉的一步。还要在Graph Inspector的Graph Settings里把Render Face设置为Both确保双面渲染。如果透明物体要正确的深度接收可以在Master节点里的Depth Write设置做调整但一般不用动。5.2 视频纹理接入Shader Graph的步骤视频接入Shader Graph的逻辑依然不变VideoPlayer输出RenderTexture把RenderTexture指定给Shader Graph的纹理属性。细节上要注意Shader Graph里要勾选纹理属性的Alpha Is Transparency标签大部分透明视频接入失败都是因为这里没有设置。如果纹理用线性空间采样可能出现颜色比预期暗或亮的情况记得在纹理导入设置里确认sRGB选项和项目色彩空间匹配。5.3 从内置管线迁移到URP的注意事项迁移是很多开发者头疼的问题。内置管线的标准Shader无法直接在URP中使用Shader Graph节点图相对独立迁移相对简单但手写的CGPROGRAM代码几乎全部要重写为HLSL。如果你的项目里有几十个手写的透明Shader迁移到URP就非常痛苦。我建议在迁移时先评估一下项目里的大部分效果用URP提供的Lit/Unlit再加上Shader Graph能否覆盖能覆盖的尽量用Shader Graph重做不能覆盖的再单独重写HLSL。毕竟HLSL的Shader代码量不大但需要调试的问题却很多。6. 透明纹理的性能与排序优化6.1 控制Overdraw的实用技巧透明渲染的最大性能杀手是Overdraw即同一像素被多次写入。GPU每做一次混合都要读写帧缓冲像素被重叠越多次开销越大。全屏的粒子特效或大量透明物体叠加移动端很容易发热掉帧。实操中我常用这样的办法用Additive混合模式替代Alpha Blend适合火焰、光晕、闪电等亮色特效开销更低。尽量缩小透明物体的投影面积粒子数量控制得精细一点。尽量少用全屏透明覆盖物尤其是后处理特效叠加透明材质这种组合非常费GPU。注意分辨率帧缓冲读写量与分辨率成正比在低端机上做分辨率缩放能解决很大问题。6.2 透明物体渲染顺序的正确管理渲染顺序的坑集中在多个透明物体交叉时。Unity的默认排序方式是按物体包围盒中心到相机距离来排序的开发者可以通过MaterialPropertyBlock设置renderQueue动态调整。比如一个半透明窗户后面还有一个半透明玻璃瓶窗户的渲染顺序应该在瓶子后面但在特殊视角下引擎可能排反。这种时候手动在代码里根据距离调整材质队列值或者把透明物体拆分成两个独立的渲染批次都能解决。经验法则是对于不重叠的透明物体不用操心排序。对于有交互的透明物体遵循大的先画、小的后画的原则。同一材质合批可以提高性能但不同的材质实例会破坏合批别随意给透明材质不同实例尤其是URP合批机制会更严格。6.3 移动端透明贴图的格式与压缩建议图片纹理透明在移动端不仅是一个效果问题还关系到内存占比。推荐格式iOSASTC 4x4或6x6。AndroidETC2。老设备不支持ETC2时要准备RGBA16的降级方案但内存占用会大幅提升。视频纹理本身不占用贴图内存因为视频解码后到RenderTexture是YUV转换后的RGB数据。RenderTexture分辨率越高占用的GPU内存越高。720P在移动端比较稳妥1080P在主流手机上没问题但4K的RenderTexture尽量不要在移动端使用内存带宽撑不住。7. 常见问题排查速查表与避坑技巧7.1 故障现象与排查步骤这里有张我在工作群里经常分享的速查表基本涵盖了透明纹理最常见的故障故障现象可能原因解决方案图片完全不透明Shader的Surface Type不是Transparent检查材质球的渲染模式或自定义Shader的Blend设置物体显示为全黑渲染队列设置错误透明物体在不透明物体之前渲染且未采样纹理确认QueueTransparent检查纹理槽位是否为空边缘有黑/白边贴图Alpha通道被压缩或者色彩空间不一致勾选Alpha Is Transparency转预乘Alpha或开启预乘修复多个透明物体排序错乱ZWrite Off后深度缓冲不生效调整渲染队列或拆分重叠物体或用深度预写Pass视频播放黑屏VideoPlayer未Prepare完成就赋值纹理监听prepareCompleted回调后再给材质赋纹理视频显示不透明RenderTexture格式不支持Alpha创建ARGB32格式的RenderTexture绿幕视频抠不干净色键容差太小或光照不均调大_Sensitivity与_Smoothness增加去溢色URP下材质变紫内置管线Shader代码不能在URP使用改用Shader Graph或HLSL重写URP兼容Shader7.2 排查过程中的几个实用工具Shader开发调试时可以利用Frame Debugger逐Pass查看绘制顺序在Window菜单下找到Frame Debugger。它能显示每个物体的渲染队列、Shader属性、深度状态很多透明排序问题在这里一眼就能看出来。采集Profiler里的SetPass Calls如果发现透明材质的Pass数量异常多大概率是合批失败或Shader分支太多要考虑简化Shader。另外建议多用Debug.Log输出屏幕坐标下的渲染信息或临时在Shader里用#if UNITY_EDITOR调试代码输出特定像素值快速定位是采样问题还是混合问题能节省不少时间。7.3 关于透明视频“版权与素材压制”的补充透明视频素材相对小众网上很多来源五花八门。自己拍绿幕素材时要特别注意光源不要用手机直出视频压缩过度会导致绿幕噪点巨大抠像时会惨不忍睹。如果是甲方给的素材一定要确认编码格式至少提前半天做兼容性测试不要等到上线当天才发现播放不了。8. 最后的经验总结坦白说透明纹理这块的技术难度并不算高真正的门槛在于对渲染管线和底层原理的理解。我自己在好几个项目里反复折腾最后的体会是能用Shader Graph就用Shader Graph尤其是URP项目省下的调试时间可以干很多事。手写Shader时先把渲染队列、ZWrite、Blend这三板斧搞懂其他都是细枝末节。视频透明里最不靠谱的就是跨平台编码兼容性方案设计时就把多个平台的回退路径想好比等到测试阶段再救火强太多。这篇文章里的代码我都在实际项目里跑过你拿到项目里稍微调一下参数基本能直接落地。如果遇到内置管线和URP迁移的问题记住一点Shader语法差异只是表面底层的渲染状态配置是一样的真正要对齐的是混合、深度、队列而不是代码长什么样。最后再分享一个实用小技巧给透明物体做点击交互时很多人忽略射线检测同样会被透明部分阻断。如果希望点击穿透透明区域要额外处理Physics.Raycast的碰撞体或者通过Shader的Alpha值在代码里做二次判断。这种细节问题往往比渲染效果本身更影响体验。遇到问题欢迎留言交流我尽量回复。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑