【从UnityURP开始探索游戏渲染】专栏-直达

SSAO概述与作用

SSAO(Screen Space Ambient Occlusion)是一种基于屏幕空间的全局环境光遮蔽技术,它通过计算场景中物体间的遮蔽关系来增强场景的深度感和真实感。在Unity URP中,SSAO通过Renderer Feature实现,作为URP渲染管线的扩展模块插入到渲染流程中。

SSAO的主要作用包括:

  • 增强场景深度感知,使物体间的接触区域产生自然阴影
  • 提升场景细节表现,特别是角落和凹陷处的视觉效果
  • 无需额外光照计算即可增强场景的空间感
  • 相比传统AO技术性能开销更低

SSAO发展历史

SSAO技术起源于2007年,由Crytek公司在《孤岛危机》中首次实现并商业化应用。随后该技术经历了多个发展阶段:

  • 早期SSAO‌(2007-2010):基于深度缓冲的简单采样,存在明显的噪点和性能问题
  • HBAO‌(2010-2013):NVIDIA提出的Horizon-Based AO,提高了精度但计算量较大
  • SSDO‌(2013-2015):Screen Space Directional Occlusion,考虑了光线方向
  • 现代SSAO‌(2015至今):结合了降噪技术和自适应采样,如GTAO(Ground Truth AO)

Unity自2018版开始将SSAO集成到URP中,通过Renderer Feature方式提供灵活的配置选项。

SSAO实现原理

SSAO在URP中的实现主要分为以下步骤:

  • 深度/法线信息采集‌:从摄像机深度纹理和法线纹理获取场景几何信息
  • 采样点生成‌:在像素周围半球空间内生成随机采样点
  • 遮蔽计算‌:比较采样点深度与场景深度,计算遮蔽值
  • 模糊处理‌:通过双边滤波消除噪点
  • 合成输出‌:将AO效果与场景颜色混合

SSAO核心原理

  • 环境光遮蔽基础

    AO通过模拟物体表面因几何遮挡导致的环境光衰减,增强场景深度感。其数学本质是法线半球面上可见性函数的积分计算。SSAO在屏幕空间利用深度/法线缓冲近似这一过程,避免传统AO的复杂光线求交。

  • 屏幕空间实现机制

    • 深度重建‌:通过深度缓冲和相机投影矩阵反推像素的世界坐标,公式为:

      float3 clipVec = float3(ndcPos.x, ndcPos.y, 1.0) * _ProjectionParams.z;
      float3 viewVec = mul(unity_CameraInvProjection, clipVec.xyzz).xyz;
    • 法向半球采样‌:在像素法线方向构建半球采样核,对比周围深度值计算遮蔽因子。深度更高的采样点计数越多,遮蔽效果越强。

URP实现流程

  • 关键组件

    • Renderer Feature‌:需创建独立Feature并配置ScriptableRenderPassInput.Normal以获取法线缓冲。
    • Shader计算‌:结合_CameraNormalsTexture和深度图进行世界坐标重建与遮蔽计算。
  • 示例代码
    • SSAORendererFeature.cs

      using UnityEngine;
      using UnityEngine.Rendering;
      using UnityEngine.Rendering.Universal; public class SSAORendererFeature : ScriptableRendererFeature {
      class SSAOPass : ScriptableRenderPass {
      public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) {
      ConfigureInput(ScriptableRenderPassInput.Normal);
      }
      // 实现Execute方法进行SSAO计算
      }
      public override void Create() {
      m_SSAOPass = new SSAOPass();
      m_SSAOPass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
      }
      }
    • SSAO.shader

      Shader "Hidden/SSAO" {
      Properties {
      _Radius ("采样半径", Range(0.1, 5)) = 1
      _Intensity ("强度", Range(0, 10)) = 1
      }
      SubShader {
      Pass {
      // 深度重建与采样核计算代码
      }
      }
      }

参数解析

参数 作用 典型值
_Radius 控制采样范围 0.5-2.0
_Intensity 遮蔽强度 1.0-3.0
_SampleCount 采样点数量 16-32

性能优化建议

  • 降低采样数(如16个)并配合噪声纹理
  • 使用双边滤波消除噪点
  • 仅在高端设备启用(移动端需谨慎)

完整Unity URP实现示例

以下是完整的SSAO Renderer Feature实现流程:

  • SSAORendererFeature.cs

    using UnityEngine;
    using UnityEngine.Rendering;
    using UnityEngine.Rendering.Universal; public class SSAORendererFeature : ScriptableRendererFeature
    {
    [System.Serializable]
    public class SSAOSettings
    {
    public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    public Material blitMaterial = null;
    public float radius = 0.5f;
    public float intensity = 1.0f;
    public float power = 2.0f;
    public int sampleCount = 16;
    public float bias = 0.025f;
    public float downsampling = 1;
    public bool blur = true;
    public float blurRadius = 1.0f;
    } public SSAOSettings settings = new SSAOSettings();
    private SSAORenderPass ssaoPass; public override void Create()
    {
    ssaoPass = new SSAORenderPass(settings);
    } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
    if (settings.blitMaterial == null)
    {
    Debug.LogWarning("Missing SSAO material");
    return;
    }
    renderer.EnqueuePass(ssaoPass);
    }
    } public class SSAORenderPass : ScriptableRenderPass
    {
    private Material ssaoMaterial;
    private SSAORendererFeature.SSAOSettings settings;
    private RenderTargetIdentifier source;
    private RenderTargetHandle tempTexture;
    private RenderTargetHandle tempTexture2; public SSAORenderPass(SSAORendererFeature.SSAOSettings settings)
    {
    this.settings = settings;
    this.renderPassEvent = settings.renderPassEvent;
    tempTexture.Init("_TempSSAOTexture");
    tempTexture2.Init("_TempSSAOTexture2");
    } public void Setup(RenderTargetIdentifier source)
    {
    this.source = source;
    } public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    {
    if (settings.downsampling > 1)
    {
    cameraTextureDescriptor.width = (int)(cameraTextureDescriptor.width / settings.downsampling);
    cameraTextureDescriptor.height = (int)(cameraTextureDescriptor.height / settings.downsampling);
    }
    cmd.GetTemporaryRT(tempTexture.id, cameraTextureDescriptor, FilterMode.Bilinear);
    cmd.GetTemporaryRT(tempTexture2.id, cameraTextureDescriptor, FilterMode.Bilinear);
    } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
    CommandBuffer cmd = CommandBufferPool.Get("SSAO"); // Set SSAO material properties
    ssaoMaterial = settings.blitMaterial;
    ssaoMaterial.SetFloat("_Radius", settings.radius);
    ssaoMaterial.SetFloat("_Intensity", settings.intensity);
    ssaoMaterial.SetFloat("_Power", settings.power);
    ssaoMaterial.SetInt("_SampleCount", settings.sampleCount);
    ssaoMaterial.SetFloat("_Bias", settings.bias); // First pass - generate AO
    Blit(cmd, source, tempTexture.Identifier(), ssaoMaterial, 0); if (settings.blur)
    {
    // Second pass - horizontal blur
    ssaoMaterial.SetVector("_Direction", new Vector2(settings.blurRadius, 0));
    Blit(cmd, tempTexture.Identifier(), tempTexture2.Identifier(), ssaoMaterial, 1); // Third pass - vertical blur
    ssaoMaterial.SetVector("_Direction", new Vector2(0, settings.blurRadius));
    Blit(cmd, tempTexture2.Identifier(), tempTexture.Identifier(), ssaoMaterial, 1);
    } // Final pass - composite
    Blit(cmd, tempTexture.Identifier(), source, ssaoMaterial, 2); context.ExecuteCommandBuffer(cmd);
    CommandBufferPool.Release(cmd);
    } public override void FrameCleanup(CommandBuffer cmd)
    {
    cmd.ReleaseTemporaryRT(tempTexture.id);
    cmd.ReleaseTemporaryRT(tempTexture2.id);
    }
    }
  • SSAO.shader

    Shader "Hidden/SSAO"
    {
    Properties
    {
    _MainTex ("Texture", 2D) = "white" {}
    } SubShader
    {
    Cull Off ZWrite Off ZTest Always Pass // 0: Generate AO
    {
    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;
    }; v2f vert(appdata v)
    {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    return o;
    } sampler2D _MainTex;
    sampler2D _CameraDepthNormalsTexture;
    float _Radius;
    float _Intensity;
    float _Power;
    int _SampleCount;
    float _Bias; float3 GetPosition(float2 uv)
    {
    float depth;
    float3 normal;
    DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, uv), depth, normal);
    float4 pos = float4(uv * 2 - 1, depth * 2 - 1, 1);
    pos = mul(unity_CameraInvProjection, pos);
    return pos.xyz / pos.w;
    } float3 GetNormal(float2 uv)
    {
    float depth;
    float3 normal;
    DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, uv), depth, normal);
    return normal;
    } float random(float2 uv)
    {
    return frac(sin(dot(uv, float2(12.9898, 78.233))) * 43758.5453);
    } float3 getSampleKernel(int i, float2 uv)
    {
    float r = random(uv * (i+1));
    float theta = random(uv * (i+2)) * 2 * 3.1415926;
    float phi = random(uv * (i+3)) * 3.1415926 * 0.5; float x = r * sin(phi) * cos(theta);
    float y = r * sin(phi) * sin(theta);
    float z = r * cos(phi); return normalize(float3(x, y, z));
    } float frag(v2f i) : SV_Target
    {
    float3 pos = GetPosition(i.uv);
    float3 normal = GetNormal(i.uv); float occlusion = 0.0;
    for(int j = 0; j < _SampleCount; j++)
    {
    float3 sampleKernel = getSampleKernel(j, i.uv);
    sampleKernel = reflect(sampleKernel, normal); float3 samplePos = pos + sampleKernel * _Radius;
    float4 sampleClipPos = mul(unity_CameraProjection, float4(samplePos, 1.0));
    sampleClipPos.xy /= sampleClipPos.w;
    sampleClipPos.xy = sampleClipPos.xy * 0.5 + 0.5; float sampleDepth = GetPosition(sampleClipPos.xy).z;
    float rangeCheck = smoothstep(0.0, 1.0, _Radius / abs(pos.z - sampleDepth));
    occlusion += (sampleDepth >= samplePos.z + _Bias ? 1.0 : 0.0) * rangeCheck;
    } occlusion = 1.0 - (occlusion / _SampleCount);
    return pow(occlusion, _Power) * _Intensity;
    }
    ENDCG
    } Pass // 1: Blur
    {
    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;
    }; v2f vert(appdata v)
    {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    return o;
    } sampler2D _MainTex;
    float4 _MainTex_TexelSize;
    float2 _Direction; float frag(v2f i) : SV_Target
    {
    float2 texelSize = _MainTex_TexelSize.xy;
    float result = 0.0;
    float weightSum = 0.0; for(int x = -2; x <= 2; x++)
    {
    float weight = exp(-(x*x) / (2.0 * 2.0));
    float2 offset = _Direction * x * texelSize;
    result += tex2D(_MainTex, i.uv + offset).r * weight;
    weightSum += weight;
    } return result / weightSum;
    }
    ENDCG
    } Pass // 2: Composite
    {
    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;
    }; v2f vert(appdata v)
    {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    return o;
    } sampler2D _MainTex;
    sampler2D _SSAOTex; float4 frag(v2f i) : SV_Target
    {
    float4 color = tex2D(_MainTex, i.uv);
    float ao = tex2D(_SSAOTex, i.uv).r;
    return color * ao;
    }
    ENDCG
    }
    }
    }

SSAO参数详解与使用指南

参数含义与调整建议

  • Radius 半径

    • 含义:控制采样点的搜索半径
    • 范围:0.1-2.0
    • 用例:小半径适合细节丰富的场景,大半径适合开阔场景
  • Intensity 强度
    • 含义:控制AO效果的强度
    • 范围:0.5-4.0
    • 用例:值越大,遮蔽效果越明显
  • Power 幂次
    • 含义:控制AO效果的对比度
    • 范围:1.0-4.0
    • 用例:值越大,暗部越暗,亮部越亮
  • Sample Count 采样数
    • 含义:每个像素的采样点数
    • 范围:8-32
    • 用例:值越高效果越平滑但性能消耗越大
  • Bias 偏移
    • 含义:防止自遮蔽的偏移量
    • 范围:0.01-0.1
    • 用例:值过小会产生噪点,值过

【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,)

【URP】Unity[RendererFeatures]屏幕空间环境光遮蔽SSAO的更多相关文章

  1. DirectX11 With Windows SDK--32 SSAO(屏幕空间环境光遮蔽)

    前言 由于性能的限制,实时光照模型往往会忽略间接光因素(即场景中其他物体所反弹的光线).但在现实生活中,大部分光照其实是间接光.在第7章里面的光照方程里面引入了环境光项: \[C_a = \mathb ...

  2. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION) 学习目标 ...

  3. 基于屏幕空间的实时全局光照(Real-time Global Illumination Based On Screen Space)

    目录 Reflective Shadow Maps(RSM) RSM 的重要性采样 RSM 的应用与缺陷 Screen Space Ambient Occulsion(SSAO) SSAO Blur ...

  4. [帖子收集]环境光遮蔽(Ambient Occlusion)

    环境光遮蔽,效果示例图 图片左边是一条龙的简单模型,呈现在一个均匀照明的环境中.尽管模型中有一些明暗不同的区域,但大部分光照都是均匀的.虽然模型有着相当复杂的几何形状,但看上去比较光滑平坦,没有明显的 ...

  5. 在Unity中实现屏幕空间阴影(2)

    参考文章: https://www.imgtec.com/blog/implementing-fast-ray-traced-soft-shadows-in-a-game-engine/ 完成的工程: ...

  6. 在Unity中实现屏幕空间阴影(1)

    接着上篇文章,我们实现了SSR效果. 其中的在屏幕空间进行光线追踪的方法是通用的.借此我们再实现一种屏幕空间的效果,即屏幕空间阴影. 文中的图片来自Catlike coding http://catl ...

  7. Cesium源码剖析---Ambient Occlusion(环境光遮蔽)

    Ambient Occlusion简称AO,中文没有太确定的叫法,一般译作环境光遮蔽.百度百科上对AO的解释是这样的:AO是来描绘物体和物体相交或靠近的时候遮挡周围漫反射光线的效果,可以解决或改善漏光 ...

  8. Unity3d 屏幕空间人体皮肤知觉渲染&次表面散射Screen-Space Perceptual Rendering & Subsurface Scattering of Human Skin

    之前的人皮渲染相关 前篇1:unity3d Human skin real time rendering 真实模拟人皮实时渲染 前篇2:unity3d Human skin real time ren ...

  9. Unity3D 屏幕空间雪场景Shader渲染

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  10. Unity ugui屏幕适配与世界坐标到ugui屏幕坐标的转换

    我们知道,如今的移动端设备分辨率五花八门,而开发过程中往往只取一种分辨率作为设计参考,例如采用1920*1080分辨率作为参考分辨率. 选定了一种参考分辨率后,美术设计人员就会固定以这样的分辨率来设计 ...

随机推荐

  1. LiveGBS GB28181视频平台实现监听摄像头报警并关联报警图片和录像功能的配置方法

    @ 目录 1.报警信息 1.1.报警查询 1.2.配置开启报警订阅 1.2.1.国标设备编辑 1.2.2.配置平台订阅下级设备的报警 1.2.3.选择开启报警订阅 1.3.配置摄像头报警 1.3.1. ...

  2. MySQL 主备同步技术演化

    主库出问题了,从库怎么办? 备库:同步主库的binlog,当主库出问题时,备库切换为主库.一般不提供读服务. 从库:同步主库的binlog,只对外提供读服务. 一主多从主备切换 方法 基于位点的主备切 ...

  3. iOS视图切割圆角

    转载请注明出处!!! iOS切圆角的方式有三种  1. 通过设置layer的属性 最简单的一种,但是很影响性能,一般在正常的开发中使用很少. self.button.layer.cornerRadiu ...

  4. Gaia2 与 ARE:赋能社区的智能体评测

    在理想情况下,AI 智能体应当是可靠的助手.当接收到任务时,它们能够轻松处理指令中的歧义,构建逐步执行的计划,正确识别所需资源,按计划执行而不被干扰,并在突发事件中灵活适应,同时保持准确性,避免幻觉. ...

  5. PCIe 载板设计资料原理图:382-基于FMC+的XCVU3P高性能 PCIe 载板

    基于FMC+的XCVU3P高性能 PCIe 载板 一.板卡概述 板卡主控芯片采用Xilinx UltraScale+16 nm VU3P芯片(XCVU3P-2FFVC1517I).板载 2 组 64b ...

  6. 【光照】UnityURP[天空盒]原理与[动态天空盒]实现

    [从UnityURP开始探索游戏渲染]专栏-直达 技术原理与核心机制 ‌立方体贴图映射‌:天空盒本质是包裹场景的立方体纹理映射,通过六个面的HDR图像(前.后.左.右.上.下)构成全景环境.URP渲染 ...

  7. docker compose 安装mysql后group by使用不了,提示'this is incompatible with sql_mode=only_full_group_by'

    本人出现问题的mysql版本是8.x,但是所有出现这种问题的mysql一下的解决方法是通用的 问题出现的原因: 默认情况下,MySQL镜像中的sql_mode含有only_full_group_by, ...

  8. 如何通过TR技术评审管控IPD开发风险?

    作为产品开发全流程的技术风险管控实践,TR(Technical Review,技术评审)自然是IPD流程中不可或缺的一环. 接下来我们需要明确:TR评审是什么? 一般TR评审是团队对技术方案.设计输出 ...

  9. 权威调研榜单:商场新风系统厂家TOP3榜单好评深度解析

    在商业空间空气品质要求日益提升的背景下,商场新风系统作为保障室内空气环境的核心设备,其技术性能和可靠性备受关注.据暖通行业统计数据显示,2024年中国商用新风系统市场规模预计达到186亿元,年均增长率 ...

  10. 软件神器 --- 安卓文件管理和逆向双修之王 mt管理器

    # MT Manager 克隆 - 开发计划 本文件概述了开发一个与 MT Manager 功能相似的应用程序的开发计划. ## 1. 功能分析 **A. 基本文件管理*** **双窗格视图**:并排 ...