[游戏中的图形学实时渲染技术] Part1 实时阴影技术

原理篇:

常见的渲染方程如下:

在不考虑自发光项与考虑阴影对于着色结果的影响之后可以将方程变化为如下形式:

如果射线在到达光源前击中了其他物体时,就认为这条来自光源的光线对着色点没有贡献。

利用上述渲染方程进行正确的着色和阴影计算是非常耗时的因为它需要进行积分,因此在光栅化的实时渲染中需要对该方程进行简化:

(微积分的近似公式)

Image

而通过该方程可以将着色和阴影分开,也就是说,可以在先计算着色,在此基础上再乘上阴影计算的结果,即可近似正确的表示阴影。

基础篇:

1. Shadow Mapping

渲染阴影场景涉及两个主要的绘图步骤。第一个生成阴影贴图本身,第二个将其应用于场景。根据实施(和灯的数量),这可能需要两次或更多次绘图过程(多次绘图指的是场景中存在多个光源)。

shadow mapping 的基础原理:

总结:Shadow Mapping的原理是首先将场景从光源的视角进行深度图的渲染,然后再从观察者的视角进行渲染,并使用深度比较来检测可见性并生成阴影。

Shadow Mapping的基本步骤如下:

  1. 从光源视角渲染深度图:先将相机移动到光源位置,以光源为视点向场景中渲染深度图,即从灯光的角度生成一个视锥体,然后对视锥体内的场景使用透视投影矩阵进行投影。
  2. 生成阴影贴图:将深度信息保存,并将结果存入一个纹理,作为阴影贴图

Image

  1. 从相机视角渲染场景:现将相机移回到视点位置,在进行场景渲染前需要将阴影贴图传递给着色器

Image

  1. 计算阴影:在片元着色器中,使用深度测试函数来检查当前像素是否被光源照射到。首先通过使用当前片元的深度值与阴影贴图中对应位置处的深度值进行比较,如果当前像素被遮挡则该像素被认为是在阴影中,否则它处在光照下。

示例:

下图是第一个渲染 pass 中渲染出的光源

Image

下图示从相机视角,第二个 pass 中渲染场景深度图

Image

由此可以进行下一步深度比较,计算阴影操作。

传统shadow mapping 的问题

1.精度问题

在shadow mapping 由光源点生成的一张深度图中,由于深度图的本质是一张纹理,而纹理是一种离散化的数字信号,从深度图还原源场景深度信息时难免对发生精度不同的问题。

Image

而由于精度原因容易产生自遮挡的问题。

自遮挡问题产生的原因是:

Image

解决方案:

Image

上面左图显示了这种自遮挡的现象大多出现在原本没有阴影的地方。这些地方本该直接被光源照的,但却出现了条纹状的阴影。其原因是光源是倾斜照射的,并且Shadow Map产生的深度图的分辨率有限。

解决方案是容忍一段区间的遮挡物,即将阴影图中的深度值偏移一段。这一段距离就称为bias,如下。

但这种情况的 bias过大变会造成上面右图的人物的模型和阴影没有连上的情况。

此外还有一种解决方案是Games202中讲解的学术界(工业界中不用)中用的一个解决方法:

记录最小深度和次小深度的平均,作为后续的深度进行比较。

Image

工业界不用的原因

  • 要求物体双面渲染(这在游戏中完全不可能),有正面就得有反面,面片也得做成box
  • 开销太大,可能并不值得,虽然O(n),但是GPU里面并行处理下会爆炸(不过实时渲染不注重复杂度只注重速度)

问题: 必须要是两面的

2.走样问题

Image

由于单一的shadowmap会存在精度不足,我们在性能与效果中平衡提出Cascade Shadowmap技术。

Cascade Shadow Mapping是一种用于实时渲染的实时阴影技术。通常在实时渲染中,光源(如太阳)的影响区域比较大,如果只使用单个深度图(Shadow Mapping)来计算阴影,很容易出现阴影质量不佳或者阴影断层的问题。而阴影CSM技术可以将一个视锥体分割成多个级别,每个级别使用不同的投影矩阵和深度贴图来计算阴影,从而提高阴影的质量和稳定性。

阴影CSM的基本原理如下:

将摄像机的视锥体划分为多个子视锥体:将摄像机的视锥体沿着近裁剪面到远裁剪面进行划分,将每个子视锥体映射到一个具有固定大小的2D矩形区域内。

对于每个子视锥体,渲染深度贴图:对于每个子视锥体,使用投射矩阵将场景渲染到一个深度贴图中,并保存深度值信息。

计算阴影:对于每个像素,使用与Shadow Mapping类似的算法来比较深度贴图中对应点的深度值和当前像素与光源的距离,来判断该像素是否被阴影所遮盖。

合并结果:将所有子视锥体的阴影贴图进行合并,并将阴影应用于场景中的对象。

中等篇

软阴影的形成

Image

umbra

本影指的是阴影最里面的区域,它看起来是最暗的。换句话说,本影代表光完全被阻挡体阻挡的阴影部分。

Penumbra

半影是一个 物体阴影的一部分,其中只有一部分光束被遮挡体阻挡。半影区就是我们在图形学中的软阴影部分

antumbra:

是指遮挡物体似乎完全位于光源的中心区域内。 (这在咱们的文章中不做讲解)

理想中的点光源会造成硬阴影,但是现实中的光源由于本身存在体积,会形成拥有半影区的软阴影。

硬阴影与软阴影之间的关系不是简单地将阴影的边缘模糊化处理,而是由于现实生活中的光源都存在一定的面积和体积,而这种面积和体积会产生软阴影。

更多自然世界中的阴影的知识可以从下面链接中了解。

什么是影子:13 个有趣的事实 -​zh-cn.lambdageeks.com/how-is-shadow-formed/#um​编辑

PCF的原理

Image

如图,上图为硬阴影,下图为软阴影。

PCF相对于传统的shadow mapping 做了那些改变:

之前的ShadowMapping过程中,假设现在得到了一张阴影图,接下来需要对一个着色点的深度 �(�) 和阴影图的采样结果 �(�) 作比较,得到一个二元的结果即为在阴影中为1,不在阴影中为0。正因为这种二元性,才产生了硬阴影的没有过渡(或者说走样现象)。

PCF的做法是得到阴影图后,如下图所示,选择一个以该着色点映射在阴影图中的位置为中心的 n x n 的filtering(核),用这个着色点的深度值 �(�) 分别和filtering中每个深度值的采样结果 �(��) 进行比较,最后得到一个 n x n 的二元比较结果,再对这个 n x n 的二元的比较结果进行filter(平均),最终得到这个着色点的阴影可见性的值,而这个结果不再是非0即1的值,而是一个在(0,1)之间的浮点数。

总结:

PCF技术通过对阴影贴图进行采样的方式,对像素周围的多个采样点进行插值和混合,从而更加准确地确定每个像素点的阴影强度。具体而言,PCF方法将每个像素的阴影采样点划分为一个网格区域,并在每个区域内进行多个采样。通过对这些采样点的深度值进行比较和插值,可以得到一个更加平滑和准确的阴影效果。

这项技术的重点在如你去如何理解Filtering(本质上是平均)的意义。在这个过程中,Filtering的尺寸决定了阴影的软硬程度。Filtering的尺寸越大,得到的阴影越软,尺寸越小,得到的阴影越硬,如下图可见。

Image

经过PCF之后就可以得到一个相对真实的阴影结果。基本上不会存在错误!

Image

PCF的缺点与改进措施概述

在实时的渲染中PCF的致命缺点是慢,原本的在shadow map贴图中的一次查找变成了 7乘7 甚至是 9乘9 的像素。

所以,如果想在实时渲染中使用这种方式的软阴影技术需要对渲染过程进行优化:

在抗锯齿的是时候的第一个算法一定是SSAA,但是这样抗锯齿处理算法需要4倍的显存...,但是MSAA其举出思想依旧是一个像素点采样多次,但从原来的全部整个屏幕全部的像素,变成了屏幕画面的边缘部分。

PCF的优化也是同理-如下图,软阴影与硬阴影的之间的变化随着阴影的投射物(笔杆)与阴影的接受物(书本)的距离有关

Image

(由此我们就可以引出PCSS,即在阴影生成过程中 blocker distance <-> Filter size呈现一定的关系)

PCSS的原理

PCSS 是非常经典的一个制作软阴影的算法

PCSS的核心原理是根据光源大小和着色点与遮挡物的距离自适应调节PCF的滤波核大小。阴影边缘的滤波核大小由半影距离决定,如下图所示。 通过相似三角形原理可知, 半影距离由光源的尺寸 、光源与遮挡物的距离 、以及着色平面与遮挡物的距离决定。

下图用来详细说明对应的数学公式来源,通过相似可以得到 ��������� 而我们通过 ��������� 的大小就可以对应出filtering 的大小。 ��������� 越大 filtering 就越大。

Image

PCSS首先假定光源是一个区域光,传统的点光源,聚光灯和平行光,都对应着某种模拟但从广义上来讲他们依旧是属于面光源。

����������=(��������−��������)∗����ℎ�/��������

那么根据上面的操作我们可以总结PCSS生成软阴影的步骤:

● 步骤1:Blocker Search 某个着色点连向光源,找到shadow map上该像素周围一块区域的纹素所记录的深度值,把区域所有texel都找一遍,判断是不是遮挡物,如果是遮挡物,则累加,最后除以遮挡物的个数,以这个平均值作为遮挡物的深度即上面的 �������� 。

● 步骤2:Penumbra estimation 用$d_{Blocker}$计算得到 ���������� ,从而计算得到filtering尺寸

● 步骤3:PCF 利用filtering尺寸,进行可见性值的计算。

注意在可见性计算部分可以,最简单的可以从周围的取若干像素信息然后平均混合;也可以根据一定比例插值;比如按照泊松分布来进行采样。

PCSS算法的优化

在PCSS算法中步骤1与步骤3比较慢。

step1 寻找 blocker的效率低下 step3 在 filtering 进行采样依旧是很耗费性能。

优化方法1:稀疏采样 缺点:由于稀疏草药会出现噪点,需要在最后在图像空间上做一次滤波。稀疏采样计时滤波之后会产生抖动。

优化方法2:对于PCSS计算的过程中做一系列的近似操作,这就引出了VSSM (Variance Soft Shadow Mapping)

VSSM的原理

VSSM的核心思想是快速寻找 blocker and filtering ,他的快速寻找是通过一系类近似操作降低时间复杂度。

可见性值的优化处理: 在刚刚步骤3的核心是找到着色点的可见性值,而可见性值是通过,p的深度值 �(�) 在阴影图中的filtring �� 中排第几。换句话说,需要得到 �(�) 占 �� 的百分比。

近似处1

我们将深度值,近似的设想为正态分布,正态分布图像由期望和方差决定(也就是说我们只要知道Filtering中深度值的期望与方差就可以大致知道深度分布的一个情况)。

剩下的全部问题在于期望(均值)和方差的获取。

  • 期望通过 SAT和mipmap。
  • 方差,数学公式计算 �(�)=�(�2)−�2(�) :

一块区域的平均值(方差)的计算:

使用MipMap

优点:

  • 快速、近似、正方形

缺点:

  • 插值结果只是近似。当查询区域在某层上不太对齐像素格的时候,需要双线性插值。
  • 当查询的范围不为2的n次方时,还要再进行一次层间插值,即三线性插值
  • 如果查询区域是长方形区域查询,还得加入各向异性过滤

使用SAT (类似算法中的前缀和) Summed Area Table可以用来高效地计算图像上任意矩形区域内所有像素值的和,而不需要遍历该区域内的每个像素。这对于一些需要频繁计算图像区域总和的算法非常有用,如图像滤波、特征检测等。

具体来说,Summed Area Table是通过对原始图像进行一次积分得到的。对于给定的像素坐标(x, y),Summed Area Table中该位置的值表示了原始图像中从(0, 0)到(x, y)的矩形区域内所有像素值的累积和。换句话说,Summed Area Table中的每个元素表示了其左上角矩形区域内所有像素值的累积和。

特点:

  • 百分百准确范围查询结果,但是计算花销较大

得到 E(X) 和 D(X) 之后我们就有了正态分布图像,接下来就需要计算可见性。

可见性值的计算

我们需要通过正态分布,确认百分比数值(可见性值)。

在概率论中,PDF(概率密度函数)为连续型随机变量的概率密度函数,CDF(累积分布函数)为概率密度函数的积分。

也就是说,对于一个值x,只需要求出CDF(x),就可以得到百分之多少的值是小于x的,即1 - 可见性的值。

Image

CDF一般比较难计算,VSSSM又找到一个不等式对它进行近似,即切比雪夫不等式:

�(�>�)≤σ2/(σ2+(�−μ)2)

​ 所以有:

���=1−�(�>�)≈1−�2�2+(�−�)2

同时切比雪夫不等式有一个苛刻的假设:t必须在均值的右边。

(当笔者做到这里的时候有一个疑问:把阴影区间的分布的CDF(x)为什么不将正态分布图像转变为标准正态分布后查表得出呢QAQ)

VSSM加速PCF步骤的总结:

  • 生成shadow map的同时,生成一张存放深度的平方的平方深度图(Square depth map)。两个通道分别存放即可,不需要额外一张texture,
  • 求深度图上某区域的均值,MipMap或者SAT,O(1)
  • 求平方深度图上某区域的均值,依旧MipMap或者SAT,O(1)
  • 知道均值,根据公式得到方差

根据切比雪夫不等式直接求出该点可见性Visibility

VSSM 的问题

VSSM 做了很多的假设,所以会存在种种的问题。

比如漏光(Light leaking)

当物体的深度不是呈现正态分布的时候会出现的问题。这是一个很明显的错误,(为了解决该问题可以使用Moment shadow mapping)

Image

虽然VSSM很快,但是准确度有些堪忧。

高级篇

距离场软阴影

距离场,它反映了任意一个点到某个物体的最小距离。将它可视化后如下:

Image

优点:

  • 快速(查询快)
  • 质量高

缺点:

  • 需要预处理(慢)
  • 需要大量存储空间

ray-SDF intersection

在ray marching的过程中,是解求光线打到物体上的点。

SDF(p) 是点 p 到达最近表面的距离,在ray marching过程很重要的概念是步长,每一次射线前进的距离就是步长也就是SDF(p)。正因如此,就可以认为SDF(p)为点p的“安全距离":从点p出发,按任意方向走SDF(p)距离,都不会碰到物体。 有了“安全距离”的概念后,选定一个起点 �1 后和方向后,按方向走 ���(�1) 距离到 �2 ,再按方向走 ���(�2) 距离,递归直到打到物体。(我们认为当直线与最近平面的距离小于某一个值的时候就是大中了物体)

Image

Distance Field Soft Shadow

SDF还可以应用在软阴影中。在软阴影中,我们用SDF来近似得到一个着色点被遮挡物遮挡的程度。 我们引入"安全角度“的概念:将光源抽象成面光源,从着色点 P沿某方向 ���� 看向光源,将光线从 ���� 朝物体方向旋转最大角度 � ,光线不会打到物体,则 � 为”安全角度“。

那么 � 的计算如下:

对于光线上任意一点Q,它的”安全距离“为SDF(Q),则”安全角度“为光线方向和着色点到圆切线的角度。 于是,安全角度的大小可以反映阴影的软硬程度(Visibility)。安全角度越小,遮挡越多,越趋近于硬阴影。安全角度越大,遮挡越少,越趋近于软阴影。

Image

如上图所示,安全角度的求解方法

问题又变成了如何求 � ?一种思路是求反三角函数,

�=���������(�)|�→−�→|

用简单的乘法运算去近似该公式

�=min�⋅���(�)|�→−�→|,1.0 。 这个近似式的k项,它决定阴影的软硬程度。当k小时, ���(�)|�→−�→| 需要更大的值(更少区间)才能>=1,最终映射到1。当k大时, ���(�)|�→−�→| 只需要较小的值(更多区间)就能>=1,最终映射到1。也就是说,k越大反映阴影更硬。

Image

参考:

  • Real-Time Rendering 4th Edition-2018-英文版
  • http://www.ownself.org/blog/2010/percentage-closer-filtering.html
  • games202
  • https://www.yuque.com/gaoshanliushui-mbfny/sst4c5/sl2q1b#49a7623c

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用MybatisPlus时出现的java.lang.NullPointerException异常~

错误描述如下所示&#xff1a; 错误原因&#xff1a;Junit的导包错误 单元测试的包有如下所示两个 我们应该根据springboot的版本进行选择&#xff0c; 在Spring Boot 2.2.X以后使用import org.junit.jupiter.api.Test Junit5 在Spring Boot 2.2.x之前使用import org.junit.T…

基于SSM的校园二手物品交易平台设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

高防IP是什么?有什么优势?

一.高防IP的概念 高防IP是指高防机房所提供的IP段&#xff0c;一种付费增值服务&#xff0c;主要是针对网络中的DDoS攻击进行保护。用户可以通过配置高防IP&#xff0c;把域名解析到高防IP上&#xff0c;引流攻击流量&#xff0c;确保源站的稳定可靠。 二.高防IP的原理 高防I…

k8s 裸金属集群部署metalLB软负载均衡 —— 筑梦之路

metalLB 官方网站 Repo&#xff1a;https://github.com/metallb/metallb 官网&#xff1a;https://metallb.universe.tf/installation metalLB解决什么问题&#xff1f; MetalLB 是一个用于裸机 Kubernetes 集群的负载均衡器实现&#xff0c;使用标准路由协议。 k8s 并没有为裸…

CSS特效006:绘制不断跳动的心形

css实战中&#xff0c;怎么绘制不断跳动的心形呢&#xff1f; 绘图的时候主要用到了transform: rotate(-45deg); transform-origin: 0 100%; transform: rotate(45deg); transform-origin: 100% 100%; 动画使用keyframes 时间上为infinite。 效果图 源代码 /* * Author: 大剑…

两数之和问题

题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意顺…

[头歌]第1关:动态学生信息管理

题目&#xff1a; C 面向对象 _ STL 的应用 (educoder.net) 考点&#xff1a; 1.自定义排序 bool cmp 2.如何使用find和erase来找到学生类里面的指定姓名的人并将其从动态数组中删除。 3.find要找的是学生类里面的成员变量而非单纯的直接找值&#xff0c;应如何实现 &…

dcat admin 各种问题

样式问题 如何根据条件给表格数据栏添加背景色 use Illuminate\Support\Collection;protected function grid(){return Grid::make(new BookArticle(), function (Grid $grid) {... 其他代码// Collection的完整路径&#xff1a;Illuminate\Support\Collection;$grid->row…

Python 使用tkinter复刻Windows记事本UI和菜单功能(二)

上一篇&#xff1a;Python tkinter实现复刻Windows记事本UI和菜单的文本编辑器&#xff08;一&#xff09;-CSDN博客 下一篇&#xff1a;敬请耐心等待&#xff0c;如发现BUG以及建议&#xff0c;请在评论区发表&#xff0c;谢谢&#xff01; 相对上一篇文章&#xff0c;本篇文…

数字化仪的超声波应用

超声波是频率大于人类听觉范围上限的声学声压&#xff08;声学&#xff09;波。超声波设备的工作频率为 20 kHz 至几千 MHz。表 1 总结了一些更常见的超声波应用的特征。每个应用中使用的频率范围都反映了实际情况下的平衡。提高工作频率可以通过提高分辨率来检测较小的伪影&am…

JAVA集合学习

一、结构 List和Set继承了Collection接口&#xff0c;Collection继承了Iterable Object类是所有类的根类&#xff0c;包括集合类&#xff0c;集合类中的元素通常是对象&#xff0c;继承了Object类中的一些基本方法&#xff0c;例如toString()、equals()、hashCode()。 Collect…

时间序列预测实战(九)PyTorch实现LSTM-ARIMA融合移动平均进行长期预测

一、本文介绍 本文带来的是利用传统时间序列预测模型ARIMA(注意&#xff1a;ARIMA模型不属于机器学习)和利用PyTorch实现深度学习模型LSTM进行融合进行预测&#xff0c;主要思想是->先利用ARIMA先和移动平均结合处理数据的线性部分&#xff08;例如趋势和季节性&#xff09…

【mysql】将逗号分割的字段内容转换为多行并group by

先说需求&#xff1a; 公司想让我通过mysql导出一个报表&#xff0c;内容为公司每个人参加会议的次数&#xff0c;现在有一个会议表fusion_meeting&#xff0c;正常的逻辑是通过人员直接group by就可以得出结果&#xff0c;但是我们的参会人是通过逗号分割这种方式存在一个字段…

【MySQL基本功系列】第二篇 InnoDB事务提交过程深度解析

通过上一篇博文&#xff0c;我们简要了解了MySQL的运行逻辑&#xff0c;从用户请求到最终将数据写入磁盘的整个过程。 当数据写入磁盘时&#xff0c;存储引擎扮演着关键的角色&#xff0c;它负责实际的数据存储和检索。 在MySQL中&#xff0c;有多个存储引擎可供选择&#xf…

vColorPicker——基于 Vue 的颜色选择器插件

文章目录 前言样例特点 一、使用步骤&#xff1f;1. 安装2.引入3.在项目中使用 vcolorpicker 二、选项三、事件 前言 vColorPicker——官网 vColorPicker——GitHub 样例 vColorPicker是基于 Vue 的一款颜色选择器插件&#xff0c;仿照Angular的color-picker插件制作 特点 …

【GIT】git分支命令,使用分支场景介绍git标签介绍,git标签命令,git标签使用的场景git查看提交历史

目录 一&#xff0c;git分支命令&#xff0c;使用分支场景介绍 二&#xff0c;git标签介绍&#xff0c;git标签命令&#xff0c;git标签使用的场景 三&#xff0c;git查看提交历史 前言&#xff1a; 今天我们来聊聊关于Git 分支管理。几乎每一种版本控制系统都以某种形式支持…

第24章_mysql性能分析工具的使用

文章目录 1. 数据库服务器的优化步骤2.查看系统性能参数3. 统计SQL的查询成本&#xff1a;last_query_cost4. 定位执行慢的 SQL&#xff1a;慢查询日志4.1 开启慢查询日志参数4.2 查看慢查询数目4.3 测试慢sql语句&#xff0c;查看慢日志4.4 系统变量 log_output&#xff0c; l…

【蓝桥杯 第十三届省赛Java B组】真题训练(A - F)

目录 A、星期计算 - BigInteger B、山 - 暴力判断 字符串 C、字符统计 - 简单哈希 D、最少刷题数 - 排序 思维 二分 分情况讨论 &#xff08;1&#xff09;&#xff08;错误&#xff09;自写哈希表 &#xff08;2&#xff09;正解 E、求阶乘 - 数学思维 二分 F、…

SAP实现文本框多行输入(类cl_gui_textedit)

参考文章&#xff1a;https://blog.csdn.net/SAPmatinal/article/details/130882962 先看效果&#xff0c;在输入框先来一段《赤壁赋》 然后点击 ‘保存输出’按钮&#xff0c;就能把输入内容从表里读取并输出来 源代码&#xff1a; *&-------------------------------…

多个div横向排列的几种方法

以下面这组 div 为例&#xff0c;group的高度由内容撑开 <div id"group"><div id"div1">div1</div><div id"div2">div2</div><div id"div3">div3</div> </div>显示结果如下为上下排…