四十三、光源VP矩阵的构造与调整 您所在的位置:网站首页 keyshot怎么调光源 四十三、光源VP矩阵的构造与调整

四十三、光源VP矩阵的构造与调整

2023-03-26 06:18| 来源: 网络整理| 查看: 265

出发啦/不要问那路在哪/迎风向前/是唯一的方法 ——《亡命之徒》(纵贯线)

在阴影贴图的构建中,需要把场景中的顶点投影回光源视角,这就需要光源的VP变换。VP即View-Projection,指视图-投影变换。回忆:九、GAMES101_变换。本章介绍Falcor框架如何构造光源的VP矩阵,以及需要如何进行调整。

回到Falcor/CSM,简单起见只考虑最简单的CascadeCount==1的情况,即不作分层。

首先计算distanceRange。传入相机信息和之前渲染好的相机深度信息。这里是用SDSM的办法来计算CSM分区的,为什么要计算这个,在后面再回过来看会比较容易理解。

重头戏是void CSM::partitionCascades(const Camera* pCamera, const float2& distanceRange)。

相机Frustum

首先获取相机Frustum。这个不完全叫做视锥,实际上是一个被平面截断的椎体,锥体和Frustum的关系,就类似于三角形和梯形的关系。

为什么需要相机Frustum?对于点光源来说,可以直接完成视图变换,构造View矩阵,但需要根据场景尺度来决定如何构造Projection矩阵,即光源相机应该“拍摄”多大的范围。Falcor用相机Frustum来粗略估计这一范围;但是用场景尺度有时会更加有效。这一粗略估计也导致后面需要进行Scale和Offset的调整。

总之,怎么获取相机Frustum呢?回忆九、GAMES101_变换,经过相机VP变化后,世界坐标会转换为相机的clipSpace中的坐标,其范围是[-1,1]的标准立方体。在clipSpace中,由于相机位于原点,因此不关心zgetWorldDirection() + lightPos; float3 up(0, 1, 0); if (abs(glm::dot(up, pLight->getWorldDirection())) >= 0.95f) { up = float3(1, 0, 0); } rmcv::mat4 view = rmcv::lookAt(lightPos, lookat, up); float distFromCenter = glm::length(lightPos - center); float nearZ = std::max(0.1f, distFromCenter - radius); float maxZ = std::min(radius * 2, distFromCenter + radius); float angle = pLight->getOpeningAngle() * 2; rmcv::mat4 proj = rmcv::perspective(angle, fboAspectRatio, nearZ, maxZ); shadowVP = proj * view; }

视图变换矩阵可以直接拿现成的光源位置和朝向。

投影矩阵用这个函数构造:inline mat4 perspective(float fovy, float aspect, float zNear, float zFar)

这四个参数分别指定了垂直视场角、相机纵横比、近平面、远平面z坐标。

不过同样地,会随着相机变化,因为会随着视锥变动。

所以falcor里面点光源也得指定一下方向啊()

为什么需要Cascade参数? CascadeOffset和Scale本质是对光源VP矩阵的调整

回忆:四十二、NVIDIA Falcor - RSM实现方案(一)里面遇到的一个问题,为什么之前cascade_count==1的时候,如果不注释掉这个地方会出错?

// if (mCsmData.cascadeCount == 1) // { // mCsmData.cascadeScale[0] = float4(1); // mCsmData.cascadeOffset[0] = float4(0); // mCsmData.cascadeRange[0].x = 0; // mCsmData.cascadeRange[0].y = 1; // return; // }

为什么需要加一个offset?

为什么需要进行放缩?

我们来看看这个问题。

首先如果把这些代码注释掉,shadowMap可视化之后是下面这样:

把计算可见性函数里面shadowPos.xyz = shadowPos.xyz * scale + offset;一行注释掉,可见性是错误的。但如果不注释掉,就正确了。

如果不加这些注释,即认为cascadeScale和cascadeOffset不起作用,shadowMap就是错误的:

可以稍微大胆一点推测,这一块蓝色部分其实是我们的场景。也就是说,场景在阴影贴图里面仅仅占了极小一块,导致阴影贴图的有效分辨率极低。

为什么会这样?再次大胆推测是因为创建光源VP矩阵的时候,正交投影范围太大。

为什么会范围太大?判断正交投影范围是通过相机Frustum来定的;但是相机可见范围显然极宽广,会超出场景尺度甚至一两个数量级。输出发现,本用例的场景中,radius=867.96,但场景尺度也就是10左右。

因此cascadeScale和cascadeOffset在这里起到了调整阴影贴图的作用,也就是把上面阴影贴图中蓝色的那部分有效信息扩展到整个阴影贴图中。

为了验证这一点,把radius硬调整为600试试:

确实是这样子的。

那么Offset的作用也迎刃而解:为什么场景不位于阴影贴图中心,需要加一个偏移量?

这是因为相机Frustum的中心并不一定是场景中心。例如上面,如果再缩小radius,可能就无法把场景包含到阴影贴图里了,会把场景给丢掉,阴影贴图完全拍在背景板上。

既然如此,那么我们完全可以取用场景包围盒,取场景中心;而不必取相机Frustum中心。这个之后如果有必要可以写一下。

所以,上面那个cascade_count==1的情况,进行特判就是错的!(悲)框架开发人员就没有试过这种情况吗!

如何计算Cascade参数?

方便起见,仍然不考虑多层cascade的情况。关键代码如下:

float nextCascadeStart = distanceRange.x; for (uint32_t c = 0; c getProjMatrix(); distanceRange = camProj[2][2] - distanceRange * camProj[3][2]; distanceRange = camProj[2][3] / distanceRange; distanceRange = (distanceRange - pCamera->getNearPlane()) / (pCamera->getFarPlane() - pCamera->getNearPlane()); distanceRange = glm::clamp(distanceRange, float2(0), float2(1)); ... }

上面的pDepthBuffer是相机视角下的深度信息,可以理解为是GBuffer的信息。我们可以认为,拥有深度信息的部分就是场景囊括的部分

实际上,这是VP变换后的z值范围。上面获取投影矩阵,然后把VP变换后的z范围换算到V变换后、P变换前的z范围,这实际上获取了场景占据的z范围相对于相机Frustum深度的比例。以此来对相机Frustum进行调整。

我们回到cascadeFrust的计算:

float3 cascadeFrust[8]; for (uint32_t i = 0; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有