GAMES202 PCSS软阴影算法细节解析

news2025/6/8 13:58:27

在LearnOpenGL框架的基础上实现了一遍GAMES202的PCF+PCSS软阴影,之前学习GAMES202时一些没弄清楚的问题顺便搞清楚了。
注:本文中代码和shader均在笔者自学LearnOpenGL的框架中实现,因此有一些细节可能和GAMES202作业框架不一致,且对202框架中的一些错误进行了修正。

PCSS算法

在这里插入图片描述

  • PCSS的核心就是根据遮挡物(Blocker)的平均深度去估算当前着色点的半影大小。而半影大小就对应着PCF的过滤半径。上图中计算半影大小的公式通过简单的相似三角形即可推导出,很简单,不做过多解释。需要注意的是这儿的深度值dxx,都是和shadow map中记录的深度值是同一个坐标系,正常来说都是Screen Space Z,即NDC [-1,1]的z线性变换到[0,1]。强调这个是因为在Find Blocks时其实使用的是eye space的Z,而202框架中把这两个不同坐标系的Z混用了,其实是错误的。
  • 为了计算遮挡物的平均深度,需要在shadow map上的一个范围内去进行测试和计算平均深度值。而如何确定这个范围是PCSS的另一个核心点。当然也可以使用一个固定范围,而202中使用了相似三角形的方法,如下图:
    在这里插入图片描述
    这儿需要理解一下,Shadow Map是在光源的视锥的Near plane上。上图中没有画出光源的视锥,如果是平行投影,就是这个面光源向下(因为根据这个图的位置关系,可知光源方向是向下的),范围为光源的尺寸;如果是透视投影,则从面光源的中心点出发向下。

计算细节分析

Find Blocker中计算 searchRadius

float searchRadius = LIGHT_SIZE_UV * (zReceiverEyeSpace - NEAR_PLANE) / zReceiverEyeSpace;

这是我修正过的代码,202框架中,使用的zReceiver其实是screen space的(尽管他的注释里面写了z当做是eye space的,但这个z还要和shadow map中的z比较,所以只能是screen space),因此我独立出了一个eye space的z用于计算,eye sapce z的计算可参考代码。
首先,理解这个算法,要看一下LIGHT_SIZE_UVNEAR_PLANE的含义。
在我的代码中进行如下定义:

#define LIGHT_WORLD_SIZE 0.05
#define LIGHT_FRUSTUM_WIDTH 6.0
#define LIGHT_SIZE_UV (LIGHT_WORLD_SIZE / LIGHT_FRUSTUM_WIDTH)
#define NEAR_PLANE 1.0

LIGHT_WORLD_SIZE 是世界坐标系下面光源的大小,如果单位是米,这儿面光源大小为5厘米,显然面光源越大,半影范围越大,阴影越软。(注:这儿只讨论宽度,可认为光源和视口都是方的)
LIGHT_FRUSTUM_WIDTH 是光源视锥的宽度,这个宽度也是在世界空间度量的。因此这两个值相除得到的其实是光源在视锥截面上的比例(平行投影等于是在近裁面上,透视投影也可映射到近裁面,只要使用近裁面的尺寸作为LIGHT_FRUSTUM_WIDTH ),这个比值的范围为[0,1],而Shadow Map同样是可以认为在视锥的近裁面上。因此LIGHT_SIZE_UV可认为是在shadow map uv坐标系下的一个长度。
而NEAR_PLANE 就是近裁面的位置,即光源位置到近裁面的距离。
回到上面searchRadius的计算,我在图上标注几个长度:
在这里插入图片描述

  • 绿色的箭头线长度就是NearPlane的距离
  • 蓝色的箭头线长度就是zReceiverEyeSpace,即eye space下着色点在光源视锥中的深度值。
  • 红色的箭头线的长度就是光源的尺寸(宽度),映射到uv空间就是LIGHT_SIZE_UV
  • 而黄色箭头线的长度就是我们要计算的serachRadius
    那么根据相似三角形,很容易计算得到:
    黄线长/红线长 = (蓝线长-绿线长)/ 蓝线长
    也就是:
float searchRadius = LIGHT_SIZE_UV * (zReceiverEyeSpace - NEAR_PLANE) / zReceiverEyeSpace;

可以看到,这儿必须使用光源空间eye space的zReceiver,否则计算单位不统一,计算结果失去物理意义。
由于这是一个比例关系,因此计算得到的searchRadius 的范围和LIGHT_SIZE_UV 一样是[0,1]。即在shadow map这张贴图上的一个归一化的范围。使用这个值进行采样距离的调节如下:

vec2 sample_coords = uv + poissonDisk[i] * searchRadius;

因此这儿不需要知道shadow map的尺寸,以及计算texel的尺寸了,因为直接计算的radius是一个归一化的范围,或者说缩放值。
另外上面几个常量值,除光源尺寸外,都要和代码中设置的光源空间的相关参数匹配,这儿偷懒都直接写了,可以看一下c++代码中如何设置的,可以看到是匹配的:

	glm::mat4 lightProjection, lightView;
	glm::mat4 lightSpaceMatrix;
	float near_plane = 1.0f, far_plane = 7.5f, view_half_size = 3.0f;
	lightProjection = glm::ortho(-view_half_size, view_half_size, -view_half_size, view_half_size, near_plane, far_plane);
	lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0));
	lightSpaceMatrix = lightProjection * lightView;

PCF filterSize的计算

这个filterSize就根据图1的公式即可计算,注意要使用screen sapace下的zReceiver和avgBlockerDepth:

	// STEP 2: penumbra size
    float penumberaRatiio = (zReceiver - avgBlockerDepth) / avgBlockerDepth;
    float filterSize = penumberaRatiio * LIGHT_SIZE_UV * NEAR_PLANE / zReceiverEyeSpace; 

但是202框架代码和讲义不一样的是,对算出来的半影尺寸,又使用 NEAR_PLANE / zReceiverEyeSpace 这个系数进行了缩放。同样这儿要使用eye space的着色点在光源空间的深度,否则坐标系不一致失去物理意义。由于着色点的eye space深度肯定是大于等于Near Plane的,因此这个比值是小于1的,乘上这个比值会让filterSize变小,减少软的程度。这么做也是有意义的,因为物体离近裁面越近,半影越大,反之半影越小。

关于PCF_Filter

这里的PCF Filter计算,使用了泊松分布的圆盘进行随机抽取采样点,这样避免使用固定位置的采样点造成规则的条带。当然由于引入了随机数,会有噪声,需要降噪。202框架中,对于采样点使用了两次,第二次是在斜对角的方向(-yx),这相当于双倍采样点数量,但是由于减少了一些随机性会在噪声方面表现好些(我猜的)。而业界中,我发现Unity等引擎并不使用随机采样,而是使用固定的过滤,可能是为了避免噪音。另外Unity中会使用基于硬件的2x2采样&比较,这样一次采样相当于4个textel。且Unity的软阴影并不是PCSS,就是PCF,即只是边缘软化的阴影,没有PCSS那种越远越软的感觉。

截图

PCSS

在这里插入图片描述

PCSS (去掉NEAR_PLANE / zReceiverEyeSpace)

效果更软些
在这里插入图片描述

PCF

16次采样
在这里插入图片描述

边缘很柔和,效果还可以,和PCSS比没有越远越软。如果PCSS能解决好噪音问题,显然更真实。

Shadow Map

在这里插入图片描述
由于我手动大概调整了一下光源的view space,让shadow map的利用率还比较高,因此1024的shadow map效果还可以。
Shadow Map技术的另一个核心点在于如何提高shadow map的利用率,比如计算合适的矩阵,CSM等。

后续

后续会继续在这个框架上研究Shadow Map技术,包含:

  • 优化光源投影矩阵,提高shadow map利用率
  • CSM
  • 实现Unity的Filter和使用硬件的2x2采样&比较

源码

ShadowMappingApp

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/334451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【前端CSS面试题】2023前端最新版css模块,高频15问

🥳博 主:初映CY的前说(前端领域) 🌞个人信条:想要变成得到,中间还有做到! 🤘本文核心:博主收集的CSS面试题 目录 一、CSS必备面试题 1.CSS3新特性 2.CSS实现元素两个盒子垂…

开发技术-Java switch case 的简单用法

文章目录1. integral-selector2. case3. break4. default5. 总结最近开发写 switch 发现有的技术点还没有掌握,在此做个记录。ON JAVA 中文版中,关于 switch 的描述为: switch 有时也被划归为一种选择语句。根据整数表达式的值,s…

Vue路由 —— vue-router

在上一篇内容讲到关于单页面组件的内容,同时也附上补充讲了关于单页面(SPA)和多页面(MPA)之间的优缺点,在本篇目当中就要来讲这个路由(vue-router),通过路由来实现页面的…

LCR测试仪测量电子元件的4种方法

当今电子元件的设计追求高性能, 而同时又致力于减少尺寸、 功耗和成本。 有效而准确的元件性能描述、设计、评估和制造过程中的测试,对于元件用户和生产厂家是至关重要的。电感、电容、电阻是电子线路中使用广泛的电子器件,在进行电子设计的基…

【图像处理OpenCV(C++版)】——4.5 全局直方图均衡化

前言: 😊😊😊欢迎来到本博客😊😊😊 🌟🌟🌟 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义,适用于平时学习、工作快…

Vue中路由缓存及activated与deactivated的详解

目录前言一,路由缓存1.1 引子1.2 路由缓存的方法1.2.1 keep-alive1.2.2 keep-alive标签中的include属性1.2.3 include中多组件的配置二,activated与deactivated2.1 引子2.2 介绍activated与deactivated2.3 解决需求三,整体代码总结前言 在Vu…

【C++】C++11语法 ~ lambda 表达式

🌈欢迎来到C专栏~~ lambda 表达式 (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort目前状态:大三非科班啃C中🌍博客主页:张小姐的猫~江湖背景快上车🚘,握好方向盘跟我有一起打天下嘞!送给自己的一句鸡…

WPF常用UI库和图标库(MahApps、HandyControl、LiveCharts)

WPF常用UI库和图表库(MahApps、HandyControl、LiveCharts) WPF有很多开源免费的UI库,本文主要介绍常见的MahApps、HandyControl两个UI库;在开发过程中经常会涉及到图表的开发,本文主要介绍LiveCharts开源图表库。 UI…

Dell Precision T7910 工作站做RAID

1:开机根据提示按Ctrl-C 2:进入下面界面直接按回车。Adapter是LSISAS3008IR的卡。 3:回车来到下面的界面,我们选择RAID Propertie回车。 4:回车来到选择RAID级别的界面。根据自己的硬盘数量和需求进行选择。 5&#xf…

云原生丨Prometheus+Grafana监控 OpenGauss 数据库

文章目录前言一、Prometheus的介绍及安装1、Prometheus 介绍2、Prometheus 安装二、Grafana的介绍及安装1.Grafana 介绍2、Grafana 安装三、安装探针1、安装Node Exporter探针2.安装opengauss_exporter探针四、 访问Prometheus与Grafana1、 访问Prometheus2、 访问 Grafana五、…

React 组件性能优化

React 组件性能优化1. 组件卸载前进行清理操作2. PureComponent3. shouldComponentUpdate4. React.memo5. 使用组件懒加载6. 使用 Fragment 避免额外标记7. 不要使用内联函数定义8. 在构造函数中进行函数this绑定9. 类组件中的箭头函数10. 避免使用内联样式属性11. 优化条件渲染…

记录复现一下第一次awd

前言 之前没打过awd,这次学长组织了一场awd娱乐赛,两个web一个pwn,还有一个黑盒,只会web,第一次啥也不会瞎打,被打烂了,不会写脚本,手交flag的感觉真“不错”,感觉awd还…

NetIQ 高级认证框架

NetIQ 高级认证框架 NetIQ Advanced Authentication 提供无密码身份验证并提升安全访问,以满足这个可扩展的基于标准的身份验证框架的合规要求。 优点 1、灵活性不仅仅在于方法。平台和应用程序支持至关重要。将安全范围扩展到您的所有系统。 2、通过一套适合…

大数据技术之Hadoop(生产调优手册)

第1章 HDFS—核心参数 1.1 NameNode内存生产配置 1)NameNode内存计算 每个文件块大概占用150byte,一台服务器128G内存为例,能存储多少文件块呢? 128 * 1024 * 1024 * 1024 / 150Byte ≈ 9.1亿 G MB KB Byte 2)Hadoop…

百度笔记聚合怎么写

百度笔记聚合怎么写,#百度笔记聚合,#百度笔记优化,#百度笔记排名 小红书笔记收录大揭秘什么是笔记被收录? 你将你的笔记的标题复制,去搜索框搜索,如果能搜索到你的笔记出来,那就是被收录了。什…

C语言学习笔记-文件读写

C 文件读写 什么是文件? 文件是以计算机硬盘为载体存储在计算机上的信息集合。是数据源的一种,最主要的作用是保存数据。在程序设计中,我们可将文件分为两大类:程序文件和数据文件 (1)程序文件 包括源程…

浅谈前端安全和浏览器安全策略

前端安全 XSS(跨站脚本攻击) Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Co…

208:vue+openlayers 监听瓦片地图加载情况,200、403及其他状态码的处理示例

第208个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayer中加载XYZ地图,在load瓦片时,通过XMLHttpRequest进行捕捉,监听瓦片地址的loadend事件,针对不同的状态码,给出不同的处理结果。具体的方法参看源代码。 直接复制下面的 vue+openlayers源代码,操作2分钟即…

Conda安装到虚拟环境中的包在pycharm中不显示--pip下载的包都到了base环境中-Ubuntu20.04

问题 今天刚装了一个Ubuntu20.04系统,安装完Anaconda,虚拟环境的包也都下载好了,结果在pycharm中配置完解释器后,只有几个基础的包,切换到base环境后发现,这些包都被下载到了base环境中。 在网上查了各种…

《小猫猫大课堂》三轮5——动态内存管理(通讯录动态内存化)

宝子,你不点个赞吗?不评个论吗?不收个藏吗? 最后的最后,关注我,关注我,关注我,你会看到更多有趣的博客哦!!! 喵喵喵,你对我真的很重…