GAMES202 作业1解答

作业要求:

hw1.pdf

作业整体概述:

  • 本次作业主要分为三个部分:SM,PCF,PCSS。
  • 我们可以使用这个浏览器插件来监看实时帧率。(本人使用的是火狐浏览器)
  • 同时我们在实现阴影的过程中可以Ctrl+shift+ESC呼出任务管理器,实时监看CPU的负载(对于我这个电脑小白来说蛮有意思的)。

image.png


作业框架整体解读:

框架解读我参考的是这个老哥的,写的非常清楚了,非常感谢!!。
image.png

第三方库

gl-matrix-min.js
three.js
OrbitControls.js
  • three.js 的相机控件,可以实现场景的缩放、旋转等操作,之所以不在 three.js 中,而是单独存在于一个 js 文件中,是因为这个存在于 three.js 的 example 目录下的实例代码,并不属于 three.js 的发布库。
  • 使用示例和API可以参考官方文档
  • 当然,也有一些中文文章可以参考。
OBJLoader.js
MTLLoader.js
dat.gui.js
imgui.umd.js 以及 imgui_imp.umd.js
  • 这两个 GUI 的库在代码中并没有使用,删除这两个文件运行页面也不会报错。我猜测可能是开始想用这个库来做GUI,后来转向了 dat.GUI,然而文件并没有删除。
  • (再多些一点,与作业无关,不管兴趣的请跳过这个部分以及后面的部分)这个库很有意思,我也是第一次听说。IMGUI 也即 Immediate-Mode Graphical User Interface(立即模式GUI),是一种 GUI 的绘制模式,与 Retained Mode GUI 相区分。前者的绘制由客户端代码逻辑代码直接驱动,数据修改直接反映在UI 的显示上;后者的绘制由图形库驱动,与客户端逻辑代码分离,数据修改并不会直接影响UI,而是由图形库来决定绘制的时机。一般在游戏里,使用的都是 IMGUI。
  • Dear ImGui 是 IMGUI 模式的一个C++实现,目的是实现一个轻量级的GUI库,同时支持多种渲染后端。这篇文章有一些简要的介绍,或者参考一下知乎上的讨论imgui-js 则是 Dear ImGui 的 js 绑定,可以使用 js 开来发基于 Dear ImGui 的 UI。我觉得这也是没有使用 imgui-js 的一个方面,就是需要基于 Dear ImGui 进行开发,然而 Dear ImGui 并不是用于 WebGL 的一个 UI 框架。如果我理解的不对, 请指出。

原理

image.png
我们将可见性的哪一项从光照方程中拿出来
这就是阴影项:
image.png
这是颜色项:
image.png
最终体现在代码中便是
gl_FragColor = vec4(visibility * phongColor, 1.0);
这部分代码在homework1\src\shaders\phongShader\phongFragment.glsl

SM部分

image.png
image.png

两趟Pass计算阴影

**src\renderers\WebGLRenderer.js**
image.png
可以看到我们的第一趟pass我们在光源出生成一个shadow map存到shadowMeshes中然后再第二趟pass中我们调用着色器程序就可以将所有的网格连同阴影一并画出。

光源矩阵

src\lights\DirectionalLight.js
因为我们需要获取光源处看世界的坐标。所以在直射光中需要补充一个获得转换到光源处空间的矩阵
并且在矩阵转换过程中,主要考察的是我们的对空间坐标转换的理解。
我们可以调用API来快速生成translate和scale矩阵
关于lookAt()函数详见这篇文章
关于正交矩阵和透视矩阵的选取:投影矩阵,由于透视投影会产生深度精度问题,因此作业中选择正交投影。

CalcLightMVP(translate, scale) 
//传入了两个矩阵帮助我们构建光源矩阵
{
  let lightMVP = mat4.create();
  let modelMatrix = mat4.create();
  let viewMatrix = mat4.create();
  let projectionMatrix = mat4.create();
  
  // Model transform
  mat4.translate(modelMatrix,modelMatrix,translate);
  mat4.scale(modelMatrix,modelMatrix,scale);
  // View transform
  mat4.lookAt(viewMatrix,this.lightPos,this.focalPoint,this.lightUp);
  // Projection transform
  mat4.ortho(projectionMatrix,-150,150,-150,150,1e-2,400);
  
  
  mat4.multiply(lightMVP, projectionMatrix, viewMatrix);
  mat4.multiply(lightMVP, lightMVP, modelMatrix);
  
  return lightMVP;
    }

完成该矩阵的输出以后我们就可以在片元着色器中获取到Shadow Map
image.png

使用SM

src\shaders\phongShader\phongFragment.glsl
image.png
image.png
在着色时我们使用了这个可见项对blinnPhong得到的颜色进行一个可见性衰减,我们在useShadowMap函数中可以看到我们需要使用到当前着色片元在光源坐标系下的坐标shadowCoord,这个坐标是在片元着色器前插值生成的。为了在贴图采样中使用该坐标,我们需要将向量的各个分量从( -1 , 1 ),强制转化到( 0 , 1 )。
image.png

float useShadowMap(sampler2D shadowMap, vec4 shadowCoord){
  float lightDepth = unpack(texture2D(shadowMap,shadowCoord.xy));
  float shadingDepth = shadowCoord.z;
  return lightDepth + EPS <= shadingDepth ? 0.0 : 1.0;//此处光照系数
}
float unpack(vec4 rgbaDepth) {
    const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0*256.0), 1.0/(256.0*256.0*256.0));
    return dot(rgbaDepth, bitShift);
}

如果简单的在光源处进行拍照,我们将得到一个具有色彩信息的包含rgba四通道的值,每一个通道包含了8bit数据我们在**src\shaders\shadowShader\shadowFragment.glsl**中可以看到深度到贴图中的颜色的一个映射关系image.png
unpack函数对应的是将一个四维的rgba颜色值转化为一个浮点数灰度值表示深度图,然后才方便下部一步与着色点深度的比较。

SM的问题

image.png

将会存在明显自遮挡锯齿,我们可以添加一个偏差来消除这种情况。
image.png

**就是简单的给shadow map中的z值加上一个偏移值,让这个值变小一点,偏离要渲染的平面一点,这样要渲染的平面就会都处于光照下,不会出现一部分在shadow中一部分在light中了。 **

image.png

image.png
image.png
image.png
同时也出现了课程中老师提到的脚处阴影丢失的问题,原因是添加的这个bias对于不同距离的阴影补偿的值是相同的。如果有办法实现自适应的bias()或许能够改进,此处不做展开。

PCF部分

image.png
image.png
我们通过SM生成了一个硬阴影,但是在实际生活中我们希望我们得到的是软阴影,而在我们刚才的硬阴影的计算中我们得到的visibility项非0即1.如果我们着色点周围的一圈像素进行一个加权平均,我们就可以得到一个相对来说较软的阴影——visibility项不再是非0即1。

采样函数

在工程中,框架为我们提供了两个采样的函数分别是
泊松圆盘采样函数
image.png
泊松圆盘长这样image.png
和我们所能想象到的方格采样,一个方格图
image.png

两个卷积核都会将坐标偏移值储存在数组中,我们可以在PCF中对着色点对应的SM坐标加上数组中的值来得到采样点的SM坐标

纹理尺寸

我们在**src\engine.js**** **可以找到纹理的分辨率要求
image.png
image.png
此处不在使用一开始的Bias()函数,懒得调参了(不是)
最终我们在main函数中启用PCF着色便可以得到一个还不错的软阴影
image.png

PCSS部分

image.png
image.png
首先我们需要实现一个寻找遮挡物的函数,在这个函数中我们只计数在着色点邻域内,SM上的深度比着色点浅的点的个数。(深度越深代表的Z值越大,深度越浅代表Z值越小)
image.png
在完善了findBlocker()函数以后我们按照教材的三步走战略,利用相似三角形来调节filter的大小。从而对远处的阴影产生模糊效果。同时我们基于物理的情形给我们的光源附加一个权值为10(此处调参比较难受,如果噪点太多说明采样率不够,如果阴影中有透光现象则是说明在此时Filter尺寸过大,需要减小一点)
image.png
image.png

注意事项

笔记仅供参考,不要直接套用。
如果对于里面有些原理和数学看不懂,建议可以先学习一下前置只是有关于坐标变换和纹理压缩的知识。
答主还是一个图形学小白,倘若本文有不对的地方希望可以友善地告诉我hhh,欢迎在评论区讨论啊!!!

拓展阅读

主流的阴影生成技术推荐:实时阴影1实时阴影2实时阴影3 shadow volume
本文的有参考知乎同学 @维客客 的教程,大佬写的好详细,我要向他学习。

  • 12
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值