URP广告牌Shader实现与性能优化指南

发布时间:2026/8/3 10:59:27
URP广告牌Shader实现与性能优化指南 1. 广告牌Shader的核心概念与应用场景广告牌Billboard技术是3D图形学中的经典渲染技巧它能让平面物体始终面向摄像机在游戏开发中常用于实现植被、粒子特效、远距离物体等场景。URPUniversal Render Pipeline作为Unity新一代轻量级渲染管线其Shader编写方式与内置管线有显著差异。我在多个手游项目中实测发现广告牌技术能节省30%-50%的植被渲染性能。比如一片森林场景用3D模型渲染1000棵树需要绘制约200万个三角面而使用广告牌技术只需2000个面就能达到相似的视觉效果。这种优化对移动端性能提升尤为明显。广告牌主要分为两种类型球形广告牌Spherical Billboard完全跟随摄像机旋转保持物体在所有轴向上都正对镜头常用于粒子特效、全向发光的物体柱形广告牌Cylindrical Billboard仅绕Y轴旋转保持物体在水平方向上始终面向摄像机适合树木、路灯等需要保持垂直状态的物体关键提示在URP中实现广告牌效果时必须使用URP Shader模板传统内置管线的Shader语法会导致编译错误。这是新手最容易踩的坑。2. URP环境下的Shader创建基础2.1 新建URP Unlit Shader在Unity 2021 LTS及以上版本中创建URP Shader的正确步骤项目窗口右键 → Create → Shader → Universal Render Pipeline → Unlit Shader重命名为UnlitBillboardShader双击打开Shader文件会看到如下基础结构Shader Universal Render Pipeline/Unlit { Properties { _BaseMap(Base Map, 2D) white {} _BaseColor(Base Color, Color) (1,1,1,1) } SubShader { Tags { RenderTypeOpaque RenderPipelineUniversalPipeline } // ... 后续Pass定义 } }2.2 关键属性添加我们需要扩展Properties块添加广告牌专用参数Properties { [MainTexture] _BaseMap(Albedo, 2D) white {} [MainColor] _BaseColor(Color, Color) (1,1,1,1) _Cutoff(Alpha Cutoff, Range(0.0, 1.0)) 0.5 [Enum(Spherical,0,Cylindrical,1)] _BillboardType(Billboard Type, Float) 0 _VerticalLock(Vertical Lock, Range(0,1)) 1 }参数说明_BillboardType通过Enum定义广告牌类型切换_VerticalLock柱形广告牌的垂直锁定强度0完全自由1完全锁定_Cutoff用于透明贴图的alpha测试阈值3. 广告牌核心算法实现3.1 顶点着色器改造广告牌的核心逻辑在顶点着色器中实现。我们需要修改URP Unlit Shader的vert函数Varyings vert(Attributes input) { Varyings output; // 获取摄像机位置世界空间 float3 cameraPos _WorldSpaceCameraPos; // 计算广告牌中心点世界坐标 float3 center mul(UNITY_MATRIX_M, float4(0, 0, 0, 1)).xyz; // 计算面向摄像机的方向 float3 viewDir normalize(cameraPos - center); // 球形广告牌处理 if (_BillboardType 0) { // 构建全新的坐标系 float3 up float3(0, 1, 0); float3 right normalize(cross(up, viewDir)); up normalize(cross(viewDir, right)); // 应用顶点偏移 input.positionOS.xy * float2( length(mul(UNITY_MATRIX_M, float4(1,0,0,0))), length(mul(UNITY_MATRIX_M, float4(0,1,0,0))) ); float3 localPos input.positionOS.xyz; float3 worldPos center right * localPos.x up * localPos.y; output.positionCS mul(UNITY_MATRIX_VP, float4(worldPos, 1.0)); } // 柱形广告牌处理 else { // 保持Y轴不变仅旋转XZ平面 viewDir.y 0; viewDir normalize(viewDir); float3 up float3(0, 1, 0); float3 right cross(up, viewDir); // 混合原始Y轴和广告牌Y轴 up lerp(normalize(cross(viewDir, right)), up, _VerticalLock); // 应用顶点偏移保持原始Y轴缩放 float3 scale float3( length(mul(UNITY_MATRIX_M, float4(1,0,0,0))), length(mul(UNITY_MATRIX_M, float4(0,1,0,0))), length(mul(UNITY_MATRIX_M, float4(0,0,1,0))) ); float3 localPos input.positionOS.xyz * scale; float3 worldPos center right * localPos.x up * localPos.y; output.positionCS mul(UNITY_MATRIX_VP, float4(worldPos, 1.0)); } // 传递其他变量 output.uv TRANSFORM_TEX(input.uv, _BaseMap); output.color input.color; return output; }3.2 参数优化技巧在实际项目中我发现几个关键优化点距离衰减远距离物体不需要完全精确的广告牌计算可以通过距离渐变降低计算精度LOD控制为广告牌Shader添加多级LOD距离超过阈值时简化为普通Unlit Shader批次合并相同材质的广告牌物体应尽量使用相同缩放值便于动态合批优化后的距离处理代码片段// 在顶点着色器开头添加 float distance length(_WorldSpaceCameraPos - mul(UNITY_MATRIX_M, float4(0,0,0,1)).xyz); float distanceFactor saturate(distance / _FadeDistance); // 在广告牌计算中应用 if (_BillboardType 0) { // 球形广告牌 viewDir lerp(viewDir, float3(0,0,1), distanceFactor * 0.3); }4. 材质与渲染配置4.1 材质参数设置创建材质后需要合理配置以下参数Rendering Mode根据需求选择Opaque、Cutout或TransparentCulling广告牌通常设置为Off双面渲染Depth Write透明材质建议关闭Alpha Clipping使用Cutout模式时启用实测发现在移动设备上使用Cutout模式比Transparent模式性能高20%左右但边缘会有锯齿。可以通过在片元着色器中添加抗锯齿处理来改善half4 frag(Varyings input) : SV_Target { half4 color SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; // 抗锯齿alpha裁剪 #ifdef _ALPHATEST_ON float alpha color.a; float fade fwidth(alpha) * 0.5; clip(alpha - _Cutoff fade); clip(_Cutoff - alpha fade); #endif return color; }4.2 渲染队列优化广告牌物体通常应设置适当的渲染队列不透明物体Geometry队列2000透明物体Transparent队列3000特殊效果可设为Geometry100等中间值在Shader的SubShader块中添加标签Tags { RenderTypeOpaque RenderPipelineUniversalPipeline QueueGeometry100 }5. 性能分析与实战技巧5.1 性能对比数据在Redmi Note 10 Pro骁龙732G上的测试结果渲染方式三角形数量帧率(FPS)内存占用(MB)3D模型200,00024185广告牌2,00058152广告牌(带LOD)1,500-2,000621505.2 常见问题解决方案问题1广告牌在VR中显示异常解决方案在VR中需要分别处理左右眼的视图矩阵#if defined(USING_STEREO_MATRICES) // VR设备特殊处理 cameraPos unity_StereoWorldSpaceCameraPos[unity_StereoEyeIndex]; #endif问题2阴影投射不正确解决方案添加专门的ShadowCaster Pass使用原始顶点位置Pass { Name ShadowCaster Tags { LightModeShadowCaster } ZWrite On ZTest LEqual Cull Off HLSLPROGRAM #pragma vertex ShadowPassVertex #pragma fragment ShadowPassFragment // 使用标准阴影投射顶点着色器 #include Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl ENDHLSL }问题3与地形接触部分穿帮解决方案在片元着色器中添加底部渐变透明// 在frag函数中添加 float groundFactor saturate(input.worldPos.y - _GroundHeight); color.a * groundFactor;6. 进阶应用风力动画与季节变化6.1 添加风力效果通过Shader实现植被的随风摆动// 在顶点着色器中添加 float windStrength _WindStrength; float2 windDir normalize(_WindDirection.xy); float windWave sin(_Time.y * _WindFrequency input.positionOS.x * _WindTurbulence); float2 windOffset windDir * windWave * windStrength * input.uv.y; input.positionOS.xz float3(windOffset, 0);需要添加的Properties_WindStrength(Wind Strength, Range(0,1)) 0.2 _WindFrequency(Wind Frequency, Float) 2 _WindTurbulence(Wind Turbulence, Float) 0.5 _WindDirection(Wind Direction, Vector) (1,0,0,0)6.2 季节变化控制通过材质参数实现春夏秋冬的植被颜色变化// Properties中添加 _Season(Season, Range(0,3)) 0 // 0-春,1-夏,2-秋,3-冬 _SpringColor(Spring Color, Color) (0.8,1,0.6,1) _SummerColor(Summer Color, Color) (0.2,0.8,0.3,1) _AutumnColor(Autumn Color, Color) (1,0.6,0.2,1) _WinterColor(Winter Color, Color) (0.7,0.7,0.8,1) // 片元着色器中修改 half4 seasonColors[4] {_SpringColor, _SummerColor, _AutumnColor, _WinterColor}; half4 seasonColor seasonColors[floor(_Season)]; color.rgb * seasonColor.rgb;7. 项目实战大规模植被系统在开放世界游戏中应用广告牌技术的完整方案分层渲染近处0-20米使用3D模型中距离20-100米高精度广告牌远处100米简化广告牌GPU Instancing支持 在Shader中添加GPU Instancing支持#pragma multi_compile_instancing #pragma instancing_options procedural:setup // 添加实例化数据支持 UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _InstanceColor) UNITY_INSTANCING_BUFFER_END(Props)动态加载系统 配合Compute Shader实现植被的动态加载与卸载// C#脚本示例 public class VegetationSystem : MonoBehaviour { ComputeBuffer positionBuffer; void Start() { positionBuffer new ComputeBuffer(MAX_INSTANCES, sizeof(float) * 3); Shader.SetGlobalBuffer(_VegetationPositions, positionBuffer); } void Update() { // 更新可见实例位置数据 UpdateVisibleInstances(); } }对应的Shader修改StructuredBufferfloat3 _VegetationPositions; Varyings vert(uint instanceID : SV_InstanceID) { float3 worldPos _VegetationPositions[instanceID]; // 后续广告牌计算... }在移动端项目中这套方案可以实现数万棵植被的流畅渲染帧率稳定在60FPS以上。关键是要平衡好LOD过渡距离和广告牌精度避免明显的视觉跳变。