【URP】Unity[RendererFeatures]屏幕空间环境光遮蔽SSAO
【从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和深度图进行世界坐标重建与遮蔽计算。
- Renderer Feature:需创建独立Feature并配置
- 示例代码
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的更多相关文章
- DirectX11 With Windows SDK--32 SSAO(屏幕空间环境光遮蔽)
前言 由于性能的限制,实时光照模型往往会忽略间接光因素(即场景中其他物体所反弹的光线).但在现实生活中,大部分光照其实是间接光.在第7章里面的光照方程里面引入了环境光项: \[C_a = \mathb ...
- Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION)
原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION) 学习目标 ...
- 基于屏幕空间的实时全局光照(Real-time Global Illumination Based On Screen Space)
目录 Reflective Shadow Maps(RSM) RSM 的重要性采样 RSM 的应用与缺陷 Screen Space Ambient Occulsion(SSAO) SSAO Blur ...
- [帖子收集]环境光遮蔽(Ambient Occlusion)
环境光遮蔽,效果示例图 图片左边是一条龙的简单模型,呈现在一个均匀照明的环境中.尽管模型中有一些明暗不同的区域,但大部分光照都是均匀的.虽然模型有着相当复杂的几何形状,但看上去比较光滑平坦,没有明显的 ...
- 在Unity中实现屏幕空间阴影(2)
参考文章: https://www.imgtec.com/blog/implementing-fast-ray-traced-soft-shadows-in-a-game-engine/ 完成的工程: ...
- 在Unity中实现屏幕空间阴影(1)
接着上篇文章,我们实现了SSR效果. 其中的在屏幕空间进行光线追踪的方法是通用的.借此我们再实现一种屏幕空间的效果,即屏幕空间阴影. 文中的图片来自Catlike coding http://catl ...
- Cesium源码剖析---Ambient Occlusion(环境光遮蔽)
Ambient Occlusion简称AO,中文没有太确定的叫法,一般译作环境光遮蔽.百度百科上对AO的解释是这样的:AO是来描绘物体和物体相交或靠近的时候遮挡周围漫反射光线的效果,可以解决或改善漏光 ...
- Unity3d 屏幕空间人体皮肤知觉渲染&次表面散射Screen-Space Perceptual Rendering & Subsurface Scattering of Human Skin
之前的人皮渲染相关 前篇1:unity3d Human skin real time rendering 真实模拟人皮实时渲染 前篇2:unity3d Human skin real time ren ...
- Unity3D 屏幕空间雪场景Shader渲染
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...
- Unity ugui屏幕适配与世界坐标到ugui屏幕坐标的转换
我们知道,如今的移动端设备分辨率五花八门,而开发过程中往往只取一种分辨率作为设计参考,例如采用1920*1080分辨率作为参考分辨率. 选定了一种参考分辨率后,美术设计人员就会固定以这样的分辨率来设计 ...
随机推荐
- LiveQing视频云平台部署实践
LiveQing云平台 LiveQing云平台是一套由LiveQing.LiveGBS或LiveNVR构成的完整云平台架构,支持分布式.跨平台.多点部署,流媒体服务器支持负载均衡,按需直播,非常适用于 ...
- LiveNVR传统安防摄像机互联网直播-二次开发相关的API接口
LiveNVR安防流媒体服务,支持RTSP稳定拉流接入,支持Onvif协议接入,支持RTMP/HLS/HTTP-FLV分发,将传统安防监控设备互联化,无插件直播等. LiveNVR相关二次开发-API ...
- Windows集成笔设备
Windows集成笔设备一般是指屏(数字化仪/笔数字化转换器)和笔(笔传感器)构成的整体.屏.笔.主机之间的连接包含了屏与笔的连接.屏与主机的连接.笔与主机的连接,集成笔的主要功能由屏与笔的连接和屏与 ...
- AI Compass前沿速览:CodeBuddy Code、即梦4.0、MiniCPM 4.1 、Hunyuan2.1、Qwen3-ASR、SpikingBrain脑脉冲大模型
AI Compass前沿速览:CodeBuddy Code.即梦4.0.MiniCPM 4.1 .Hunyuan2.1.Qwen3-ASR.SpikingBrain脑脉冲大模型 AI-Compass ...
- PHP serialize 序列化完全指南
PHP serialize 序列化完全指南 介绍 如果你和我一样,第一次在 PHP 中看到序列化字符串时会觉得很困惑.我当时在做一个 Laravel 项目,想搞清楚将任务推送到队列时到底发生了什么.我 ...
- 源码调试-带你了解下车牌识别的深度学习模型-LPRNet
视频演示 源码调试-带你了解下车牌识别的深度学习模型-LPRNet 大家好,这里是Coding茶水间.本期我们来调试运行一个经典的深度学习网络--LPRNet,它专门用于车牌识别任务. LPRNe ...
- 《深入Linux内核架构》学习笔记 -- 持续更新中
第1章 简介与概述 1.1 内核的任务 (1) 内核代替应用程序与硬件联系 (2) 内核作为资源管理器,分配CPU时间.内存.磁盘空间.网络连接资源 (3) 应用程序可以通过系统调用向内核发送请求 1 ...
- 从 “盲调” 到 “精准优化”:SQL Server 表统计信息实战指南
本文核心要旨在于:SQL Server 表统计信息作为元数据对象,宛如数据分布的 "指南针",精准存储着数据分布信息,为查询优化器提供关键依据,助力其生成高效的查询执行计划.在维护 ...
- 使用Ollama 0.12.2本地部署大模型,友好界面对话,开启飞行模式数据完全存在本地
之前写过一篇Ollama的介绍C#使用OllamaSharp调用Llama 3.Phi 3等大语言模型.那个时候Ollama还是没有界面对话的.需要再命令行下载需要的大模型,对话输出的内容也是在命令行 ...
- 视频图像传输卡设计原理图:220-基于Kintex-7 XC7K160T 的CameraLink转四路光纤数据转发卡(Full Camera Link图像转万兆以太网适配器 )
基于Kintex-7 XC7K160T 的CameraLink转四路光纤数据转发卡(Full Camera Link图像转万兆以太网适配器 ) 一.板卡概述 该板卡是一款CameraLink(Full ...