这几天花了点时间优化了一下阴影。

原本的阴影实现有一些比较严重的问题:

第一个是我使用了透视投影,再加上角度问题,导致阴影看起来存在近大远小的变形,不符合太阳光产生的阴影;

第二个是我的阴影贴图是在世界空间的固定位置生成的,且阴影贴图的大小是有限的,导致整个画面中,只有落在阴影贴图中的非常小的一部分物体能够产生阴影;如果扩大阴影的视锥体范围,则可以容纳更多的物体,但这会导致深度精度降低,阴影质量大幅下降。

为了避免阴影只能在某个区域产生,首先需要做的改动是在相机前生成阴影区域(fit to view)。比较理想的位置是阴影贴图的区域恰好覆盖整个视锥体,但是这个实际上不太现实,因为为了显式更多的物体,视锥体的远裁剪面一般都会非常大。因此我们优先选择比较靠近相机位置的地方作为灯光空间的原点。

我尝试着做了第一版(只有一个靠近相机的阴影贴图)阴影优化,此时确实解决了只在世界空间某个位置能生成阴影的问题,但是仍然存在一个弊端,只要相机稍微拉远一点,阴影就消失了。

为了保证阴影能覆盖到更多区域,需要考虑使用层级阴影。通俗而言,就是在视锥体不同深度的位置分别生成阴影贴图,从而覆盖更多区域。此时,随着镜头推动,我们切换使用不同的阴影贴图,阴影精度也在发生改变。镜头比较远的时候,阴影将消失不显示,也比较符合lod的概念。

随着镜头越来越远,阴影精度降低,直到消失

计算灯光空间变化矩阵、灯光视锥体

我们把视锥体(上图为截面)分为多个区域,其中圆点为相机位置,射线为太阳光方向。图中我们为三个区域分别生成shadow map。

对于太阳光而言,我们使用正交投影来生成阴影。那么灯光视锥体的形状将为一个立方体,一个比较好的选取方法就是在灯光空间取区域的AABB包围盒。比如,对于橙色区域而言,我们选取的包围盒为:

为了能够在灯光空间求得视锥体的AABB包围盒,首先我们需要求出视锥体区域的八个顶点的世界坐标:

计算的一个比较简单的方法是,在相机空间中,利用相似三角形先求得每个顶点的坐标,然后将它们转换到世界空间。如下代码中,将视锥体分为了四个区域,共五个截面,分别计算每个截面的顶点坐标(此处zPos就是相机空间下的多个z值,注意在OpenGL坐标系里,相机前物体的z坐标都是负数):

void Camera::UpdateVertex()
{float fov = RenderCommon::Inst()->GetFov();float aspect = RenderCommon::Inst()->GetAspect();QMatrix4x4 inverted_viewMatirx = viewMatrix.inverted();// leftTop, rightTop, leftBottom, rightBottomfloat tan_fov = tan(fov/2);vector<float> zPos{0, 15, 30, 100, 200};for(size_t i = 0;i < zPos.size(); i++){float z = zPos[i];float y = z * tan_fov;float x = y * aspect;z = -z;QVector4D tmp_leftTopPos = QVector4D(-x, y, z, 1);QVector4D tmp_rightTopPos = QVector4D(x, y, z, 1);QVector4D tmp_leftBottomPos = QVector4D(-x, -y, z, 1);QVector4D tmp_rightBottomPos = QVector4D(x, -y, z, 1);tmp_leftTopPos = inverted_viewMatirx * tmp_leftTopPos;tmp_rightTopPos = inverted_viewMatirx * tmp_rightTopPos;tmp_leftBottomPos = inverted_viewMatirx * tmp_leftBottomPos;tmp_rightBottomPos = inverted_viewMatirx * tmp_rightBottomPos;viewPos[i][0] = QVector3D(tmp_leftTopPos.x(),tmp_leftTopPos.y(),tmp_leftTopPos.z()) / tmp_leftTopPos.w();viewPos[i][1] = QVector3D(tmp_rightTopPos.x(),tmp_rightTopPos.y(),tmp_rightTopPos.z()) / tmp_rightTopPos.w();viewPos[i][2] = QVector3D(tmp_leftBottomPos.x(),tmp_leftBottomPos.y(),tmp_leftBottomPos.z()) / tmp_leftBottomPos.w();viewPos[i][3] = QVector3D(tmp_rightBottomPos.x(),tmp_rightBottomPos.y(),tmp_rightBottomPos.z()) / tmp_rightBottomPos.w();}
}

然后将其转换到灯光空间中。我们先构造一个转换矩阵。其中向量N代表的就是太阳光的方向。为了构造一个右手坐标系的转换矩阵,我们需要把N取反,也就是说,实际上以下代码构造的太阳光方向为(0, -1, -1)

void RenderCommon::UpdateLightSpace()
{QVector3D upDir(0, 1, 0);QVector3D N = QVector3D(0, 1, 1);QVector3D U = QVector3D::crossProduct(upDir, N);QVector3D V = QVector3D::crossProduct(N, U);N.normalize();U.normalize();V.normalize();lightSpace.setRow(0, {U.x(), U.y(), U.z(), 0}); // xlightSpace.setRow(1, {V.x(), V.y(), V.z(), 0}); // ylightSpace.setRow(2, {N.x(), N.y(), N.z(), 0}); // zlightSpace.setRow(3, {0, 0, 0, 1});
}

接下来,对于每个区域,我们需要构造两个矩阵。一个是灯光空间变换矩阵,另一个是灯光的视锥体。对于前者而言,它和刚才求得的lightSpace比较类型,但lightSpace不需要考虑位移变换,所以选取了原点即可。而灯光空间变换矩阵则需要我们确定灯光的位置,并将其作为原点。对于后者而言,如前面所提到的,我们需要使用正交投影。

我们首先将视锥体的几个顶点转换到灯光空间,然后直接计算Xmin, Xmax, Ymin, Ymax, Zmin, Zmax,从而得到包围盒的八个顶点的位置。此时,我们将灯光的位置选定在这个包围盒((Xmin + Xmax) / 2, (Ymin + Ymax) / 2, Xmax)的位置。注意这个坐标是灯光空间下的,因此我们还需要将其转回到世界空间,此时我们就求出了灯光的坐标。

同时,我们也得到了视锥体的长宽分别为Xmax - Xmin, Ymax - Ymin。就这样构造完成了我们所需要的几个矩阵。

void RenderCommon::UpdateLightMatrix()
{QVector3D upDir(0, 1, 0);QVector3D N = QVector3D(0, 1, 1);QVector3D U = QVector3D::crossProduct(upDir, N);QVector3D V = QVector3D::crossProduct(N, U);N.normalize();U.normalize();V.normalize();QMatrix4x4 lightSpace_inverted = lightSpace.inverted();for(size_t i = 0;i < Shadow_Layer; i++){QVector3D frontLeftTop = Camera::Inst()->GetViewPos(i, 0);QVector3D frontRightTop = Camera::Inst()->GetViewPos(i, 1);QVector3D frontLeftBottom = Camera::Inst()->GetViewPos(i, 2);QVector3D frontRightBottom = Camera::Inst()->GetViewPos(i, 3);QVector3D backLeftTop = Camera::Inst()->GetViewPos(i + 1, 0);QVector3D backRightTop = Camera::Inst()->GetViewPos(i + 1, 1);QVector3D backLeftBottom = Camera::Inst()->GetViewPos(i + 1, 2);QVector3D backRightBottom = Camera::Inst()->GetViewPos(i + 1, 3);QVector4D frontLeftTop1 = lightSpace * QVector4D(frontLeftTop, 1);QVector4D frontRightTop1 = lightSpace * QVector4D(frontRightTop, 1);QVector4D frontLeftBottom1 = lightSpace * QVector4D(frontLeftBottom, 1);QVector4D frontRightBottom1 = lightSpace * QVector4D(frontRightBottom, 1);QVector4D backLeftTop1 = lightSpace * QVector4D(backLeftTop, 1);QVector4D backRightTop1 = lightSpace * QVector4D(backRightTop, 1);QVector4D backLeftBottom1 = lightSpace * QVector4D(backLeftBottom, 1);QVector4D backRightBottom1 = lightSpace * QVector4D(backRightBottom, 1);frontLeftTop = QVector3D(frontLeftTop1.x(),frontLeftTop1.y(),frontLeftTop1.z()) / frontLeftTop1.w();frontRightTop = QVector3D(frontRightTop1.x(),frontRightTop1.y(),frontRightTop1.z()) / frontRightTop1.w();frontLeftBottom = QVector3D(frontLeftBottom1.x(),frontLeftBottom1.y(),frontLeftBottom1.z()) / frontLeftBottom1.w();frontRightBottom = QVector3D(frontRightBottom1.x(),frontRightBottom1.y(),frontRightBottom1.z()) / frontRightBottom1.w();backLeftTop = QVector3D(backLeftTop1.x(),backLeftTop1.y(),backLeftTop1.z()) / backLeftTop1.w();backRightTop = QVector3D(backRightTop1.x(),backRightTop1.y(),backRightTop1.z()) / backRightTop1.w();backLeftBottom = QVector3D(backLeftBottom1.x(),backLeftBottom1.y(),backLeftBottom1.z()) / backLeftBottom1.w();backRightBottom = QVector3D(backRightBottom1.x(),backRightBottom1.y(),backRightBottom1.z()) / backRightBottom1.w();float x1 = min({frontLeftTop.x(),frontRightTop.x(), frontLeftBottom.x(), frontRightBottom.x(),backLeftTop.x(), backRightTop.x(),  backLeftBottom.x(),  backRightBottom.x() });float x2 = max({frontLeftTop.x(),frontRightTop.x(), frontLeftBottom.x(), frontRightBottom.x(),backLeftTop.x(), backRightTop.x(),  backLeftBottom.x(),  backRightBottom.x() });float y1 = min({frontLeftTop.y(),frontRightTop.y(), frontLeftBottom.y(), frontRightBottom.y(),backLeftTop.y(), backRightTop.y(),  backLeftBottom.y(),  backRightBottom.y() });float y2 = max({frontLeftTop.y(),frontRightTop.y(), frontLeftBottom.y(), frontRightBottom.y(),backLeftTop.y(), backRightTop.y(),  backLeftBottom.y(),  backRightBottom.y() });float z1 = min({frontLeftTop.z(),frontRightTop.z(), frontLeftBottom.z(), frontRightBottom.z(),backLeftTop.z(), backRightTop.z(),  backLeftBottom.z(),  backRightBottom.z() });float z2 = max({frontLeftTop.z(),frontRightTop.z(), frontLeftBottom.z(), frontRightBottom.z(),backLeftTop.z(), backRightTop.z(),  backLeftBottom.z(),  backRightBottom.z() });float center_x = (x1 + x2) / 2;float center_y = (y1 + y2) / 2;QVector3D pos(center_x, center_y, z2);QVector4D tmp = lightSpace_inverted * QVector4D(pos.x(), pos.y(), pos.z(), 1);pos = QVector3D(tmp.x(), tmp.y(), tmp.z()) / tmp.w();lightMatrix[i].setRow(0, {U.x(), U.y(), U.z(), -QVector3D::dotProduct(U, pos)}); // xlightMatrix[i].setRow(1, {V.x(), V.y(), V.z(), -QVector3D::dotProduct(V, pos)}); // ylightMatrix[i].setRow(2, {N.x(), N.y(), N.z(), -QVector3D::dotProduct(N, pos)}); // zlightMatrix[i].setRow(3, {0, 0, 0, 1});orthoZFar[i] = z2 - z1;lightOrtho[i].setToIdentity();// qDebug() << orthoZFar;lightOrtho[i].ortho(x1 - center_x, x2 - center_x, y1 - center_y, y2 - center_y, 0, orthoZFar[i]);}
}

生成阴影映射

接下来,我们执行多次阴影映射步骤。此处我使用了四个区域,因此也需要绑定四个shader函数,引用四个fragment shader,这个地方不得不说,OpenGL写起来还是不如DirectX的effect框架好用的:

void Object::GenShadowMap()
{for(int i = 0;i < Shadow_Layer; i++){GenShadowMap(i);}
}void Object::GenShadowMap(int i)
{QOpenGLFunctions* gl = QOpenGLContext::currentContext()->functions();int screenX = RenderCommon::Inst()->GetScreenX();int screenY = RenderCommon::Inst()->GetScreenY();string name = "ShadowMap" + to_string(i + 1);QOpenGLShaderProgram* shadowMapProgram = CResourceInfo::Inst()->GetProgram(name);shadowMapProgram->bind();auto shadowMapFrameBuffer = CResourceInfo::Inst()->CreateFrameBuffer("ShadowMap", screenX, screenY);gl->glActiveTexture(GL_TEXTURE0);gl->glBindTexture(GL_TEXTURE_2D, shadowMapFrameBuffer->vecTexId[0]);shadowMapProgram->setUniformValue("ShadowMap", 0);shadowMapProgram->setUniformValue("LightMatrix",   RenderCommon::Inst()->GetLightMatrix(i));shadowMapProgram->setUniformValue("OrthoMatrix", RenderCommon::Inst()->GetOrthoMatrix(i));shadowMapProgram->setUniformValue("zFar", RenderCommon::Inst()->GetOrthoZFarPlane(i));shadowMapProgram->setUniformValue("ModelMatrix",  modelMatrix);shadowMapProgram->setUniformValue("IT_ModelMatrix",  IT_modelMatrix);Draw(shadowMapProgram);
}void ObjectInfo::Render()
{int screenX = RenderCommon::Inst()->GetScreenX();int screenY = RenderCommon::Inst()->GetScreenY();// 更新位置glClearColor(1,1,1,1);for(const auto& obj : vecObjs){obj->UpdateLocation();}// 阴影绘制auto shadowMapFrameBuffer = CResourceInfo::Inst()->CreateFrameBuffer("ShadowMap", screenX, screenY);glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFrameBuffer->frameBuffer);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);for(const auto& obj : vecObjs){if(obj->m_bRender && obj->m_bCastShadow){obj->GenShadowMap();}}// ... others
}

shadowmap.vsh

#version 450 coreuniform mat4 ProjectMatrix;
uniform mat4 LightMatrix;
uniform mat4 ModelMatrix;in vec4 a_position;
in vec2 a_texcoord;out vec2 v_texcoord;
out vec2 v_depth;void main()
{v_texcoord = a_texcoord;gl_Position = ModelMatrix * a_position;gl_Position = LightMatrix * gl_Position;v_depth = gl_Position.zw;gl_Position = ProjectMatrix * gl_Position;
}

shadowmap1.fsh

这里我用了四个fsh,分别写入四个通道里的,这里就不一一列出了。这里还传入了ShadowMap参数是为了避免结果被多次写入覆盖了。

#version 450uniform sampler2D ShadowMap;
uniform float zFar;
in vec2 v_depth;
in vec2 v_texcoord;out vec4 fragColor;
void main()
{float fColor =  -(v_depth.x/v_depth.y)/zFar;fragColor.x = fColor;fragColor.y = texture(ShadowMap, v_texcoord).y;fragColor.z = texture(ShadowMap, v_texcoord).z;fragColor.w = texture(ShadowMap, v_texcoord).w;
}

后处理计算阴影

计算阴影的时候,我们需要根据当前的深度,到不同的shadowmap采样:

float depth_layer[5] = {0.0, 15.0/zFar, 30.0/zFar, 100.0/zFar, 200.0/zFar};
float GetShadow(vec4 worldPos, float depth)
{float fShadow = 0.0;int idx = 4;for(int i = 0;i < 4;i++){if(depth >= depth_layer[i] && depth < depth_layer[i + 1]){idx = i;break;}}mat4 LightMatrix;mat4 OrthoMatrix;float orthoZFar;if(idx == 0){LightMatrix = LightMatrix1;OrthoMatrix = OrthoMatrix1;orthoZFar = orthoZFar1;}else if(idx == 1){LightMatrix = LightMatrix2;OrthoMatrix = OrthoMatrix2;orthoZFar = orthoZFar2;}else if(idx == 2){LightMatrix = LightMatrix3;OrthoMatrix = OrthoMatrix3;orthoZFar = orthoZFar3;}else if(idx == 3){LightMatrix = LightMatrix4;OrthoMatrix = OrthoMatrix4;orthoZFar = orthoZFar4;}else{return 0.8;}vec4 lightPos = (LightMatrix * (worldPos));float fDistance = -lightPos.z / orthoZFar;lightPos = OrthoMatrix * lightPos;vec2 uv = lightPos.xy / lightPos.w * 0.5 + vec2(0.5, 0.5);uv.x = clamp(uv.x, 0, 1);uv.y = clamp(uv.y, 0, 1);float offset = 0.4 / orthoZFar;for(int i = -1; i <= 1; i++){for(int j = -1; j <= 1; j++){float fDistanceMap;if(idx == 0) fDistanceMap = texture(ShadowMap, uv + vec2(1.0 * i / ScreenX, 1.0 * j / ScreenY)).x;else if(idx == 1) fDistanceMap = texture(ShadowMap, uv + vec2(1.0 * i / ScreenX, 1.0 * j / ScreenY)).y;else if(idx == 2) fDistanceMap = texture(ShadowMap, uv + vec2(1.0 * i / ScreenX, 1.0 * j / ScreenY)).z;else if(idx == 3) fDistanceMap = texture(ShadowMap, uv + vec2(1.0 * i / ScreenX, 1.0 * j / ScreenY)).w;fShadow += fDistance - offset > fDistanceMap ? 0.2 : 0.8;}}fShadow /= 9.0;return fShadow;
}

[OpenGL] Cascade Shadowmap(层级阴影)相关推荐

  1. Directx11进阶教程之CascadeShadowMap(层级阴影)(上)

    好久没写博客了,紧接着前面的博客,不过这次读取纹理的借口换为了DXUT框架里面的接口. 程序结构 如果没有学会ShadowMap,PCF软阴影原理的建议回去回顾下面几个教程: Directx11教程三 ...

  2. OpenGL基础53:阴影映射(下)

    接上文:OpenGL基础52:阴影映射(上) 五.阴影失真 按照上文的计算的结果,一个很明显的问题是:对于参与计算深度贴图的物体,其表面可以看到这样的栅格状的阴影,这种常见的错误表现也叫做阴影失真(S ...

  3. OpenGL point shadow点阴影的实例

    OpenGL point shadow点阴影 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #inclu ...

  4. IOS – OpenGL ES 调节图像阴影 GPUImageHighlightShadowFilter

    目录 一.简介 二.效果演示 三.源码下载 四.猜你喜欢 零基础 OpenGL (ES) 学习路线推荐 : OpenGL (ES) 学习目录 >> OpenGL ES 基础 零基础 Ope ...

  5. Directx11进阶教程之CascadeShadowMap(层级阴影)(下)---解决阴影丢失和阴影抖动问题

    承接上一篇博客,我们探讨下阴影丢失和阴影抖动的原因和提出相应的解决办法. CSM算法的阴影丢失或者显示错乱问题: 再次看看试验场景的阴影丢失或者阴影显示错乱的现象: 错误的显示:(上个教程不完善的CS ...

  6. OpenGL基础52:阴影映射(上)

    参考于:https://learnopengl.com/#!Advanced-Lighting/Shadows/Shadow-Mapping 一.游戏中的阴影 阴影是光线被阻挡的结果,当一个光源的光线 ...

  7. OpenGL Deferred Shading延迟阴影实例

    OpenGL 延迟阴影实例 先上图,再解答. 按下2键 按下5键 完整主要的源代码 源代码剖析 先上图,再解答. 按下2键 按下5键 完整

  8. OpenGL基础54:点光源阴影

    前置: OpenGL基础53:阴影映射(下) 一.万象阴影贴图 之前成功实现了平行光阴影,生成阴影贴图时使用的矩阵是正交矩阵,若是想要实现点光源的阴影效果,那么理论上只需要修改投影矩阵为透视矩阵就好了 ...

  9. 【OpenGL ES】光影(光照与阴影)效果

    1 前言 Blinn改进的冯氏光照模型 中只展示了光照效果,本文将进一步展示阴影效果. 绘制阴影,需要用到深度纹理,即从光源角度看模型并绘制一张纹理图,纹理图的颜色代表了模型上的点离光源的深度,只有离 ...

最新文章

  1. Python学习六大路线,教你快速上手
  2. 【机器学习】快速入门简单线性回归 (SLR)
  3. u盘安装linux 提示no such device_树莓派学习笔记2-U盘挂载和系统备份
  4. [Leedcode][第215题][JAVA][数组中的第K个最大元素][快排][优先队列]
  5. ZZULIOJ 1108: 打印数字图形(函数专题)
  6. 三条中线分的六个三角形_解读三角形中的三边关系和三条线段的应用
  7. 二分图判定(涂色问题)
  8. Android RecyclerView网格布局动画
  9. 输出结果为16的python表达式_第一周作业(rayco)
  10. winform实现下拉框检索
  11. 【离散数学】数理逻辑 第二章 谓词逻辑(3) 谓词公式的逻辑等价与蕴含、谓词演算的永真公式
  12. linux 修改文件可执行,linux下用chmod修改文件为可执行文件
  13. 【推荐】智慧应急指挥调度中心信息化软件平台管理系统建设解决方案合集(共46份,790M)
  14. HDU 3533 Escape(BFS)
  15. Spring - 解决 SpringUtil getBean NPE 问题
  16. qq小程序绑定服务器,QQ小程序 用户信息
  17. 强化学习PARL——5. 基于连续动作空间上方法求解RL及大作业
  18. 【容斥】2017 ACM Arabella Collegiate Programming Contest
  19. 计算机领域英文单词怎么读
  20. 框架复习(一):不如写个tiny-Spring?(完整版)

热门文章

  1. MSP430定时器输出比较不稳定的解决方法
  2. 计算机多媒体技术及其应用论文,计算机多媒体技术在教学上的应用 毕业论文...
  3. 网传的Spring大漏洞
  4. Leaflet使用经验总结
  5. 小学计算机学科知识与能力,小学教师资格《教育教学知识与能力》知识点:信息化教学...
  6. ASP.NET 'Atlas' 概述
  7. jquery easyui iconcls(小图标)属性的设置
  8. Docker与flannel
  9. Java实现五子棋小游戏(附源码)
  10. Docker Dockerfile中文参考手册 (Dockerfile reference)