Games202(3·作业1,第6章)
作业1
首先作业框架为我们绘制了场景,但是无阴影效果:
这是包含阴影的PBR 公式: V + L + B
框架解析:
- engine.js: 新增了一个人物模型,1个地面模型
- webglrender:
- 新增了shadowMeshes数组
- render中,变为了两次pass,首先为场景(使用阴影材质)渲染阴影mesh,然后为场景渲染颜色
- 平行光:(本次使用平行光而非点光源作阴影)
- 网格体
- CalcLightMVP: 返回光源的MVP
- shadow材质 继承主材质:
- 包括FBO的帧缓冲区,为了存储阴影相关数据的,
- 包括lightMVP 光视角的MVP
- 函数: buildShadowMaterial 构建阴影材质
- FBO.js,这个类的构造首先创建帧缓冲和设置属性,创建纹理和设置属性,纹理和缓冲关联,以及错误检查,解绑
- shadow glsl:(为场景渲染一张深度图)
- pack函数的作用是把一个(0,1)的float值储存到RGBA四个通道中
- gl_FragCoord是内置的视口变换后的坐标:其大小范围是x:[0, ScreenWidth],y:[0, ScreenHeight],z:[0, 1],返回它即返回深度值
JAVA语法
constructor(){ super(……); } 是构造函数,super部分是调用基类构造函数
阴影steps:
- 将world pos(经过model)* LightMVP矩阵变换到光空间,获得NDC[-1,1]标准化设备坐标
- 为了获取在光屏幕下的纹理坐标,通过/w(视口变换)-> 在+1/2映射到[0,1](因为纹理是0,0->1,1)
- 判定阴影:根据 当前depth(纹理坐标.z) >= map保存的值(texture2D采样),就返回0判定为阴影,否则返回1
普通阴影:
- 修改CalcLightMVP函数,
- 写出light MVP矩阵,
- 根据传入进来的参数SRT,构建Model矩阵,
- 根据光源属性构建view矩阵,
- 根据视锥体的左右上下近远,创建投影矩阵
- 完善glsl中的useShadowMap函数,
- 查询当前着色点在ShadowMap上记录的深度值,并与转换到light space的深度值比较后返回visibility项,
- 首先获取阴影贴图中的值,获取当前的深度值,如果当前 >= map保存的值,就返回0判定为阴影,否则返回1
- 修改phong glsl中的main
- 首先获取SM上的纹理坐标
- 调用useShadowMap返回visibility的值
- 让最后颜色返回gl_FragColor = vec4(phongColor * visibility, 1.0);
自遮挡:
有两种方法解决子遮挡,我们用自适应的方法
根据这个公式,我们添加一个getShadowBias函数,如图所示,
- A = (1 + 取整(在uv上的过滤半径大小))* (平截头体大小 / (阴影贴图大小 * 2))
- B = 1 - dot(法线,光线方向)
- Bias = 常数 * AB
修改useShadowMap函数,cur_depth - bias >= depth
pcf:
让shaderpoint和shadowmap一圈的范围比较,获取平均值
- 写fs中的pcf函数,
- 为了优化性能,使用poissonDiskSamples生成随机种子保存在poissonDisk数组中,作为offest位置,采样NUM_SAMPLES指定次数次,最后+=求和的结果求平均值,
- 其中filterRadiusUV过滤半径取:FILTER_RADIUS / SHADOW_MAP_SIZE得到uv(0–1)中的大小
- 这里有一点注意的地方,useShadowMap()应放在pcf()的上面,否则会调用失败
pcss:
步骤如下:
- STEP 1: avgblocker depth,找到平均block深度在findBlocker()中计算
- 首先确定在shadowmap中应该采样的区域,它和3个参数有关
- 光的大小(越大,block范围越大)
- 近平面距离shaderpoint的距离(距离越远,block范围越大)
- 光到shaderpoint的距离有关(距离越远,block范围越小)
- 然后在此范围内像pcf一样随机采样,取block平均值
- 首先确定在shadowmap中应该采样的区域,它和3个参数有关
- STEP 2: penumbra size,根据课上的公式计算penumbra过滤系数(即filterRadiusUV)(其实可以理解为,近处pcf程度低,远处pcf程度高)
- float penumbra = (zReceiver - avgBlockerDepth) * LIGHT_SIZE_UV / avgBlockerDepth;
- STEP 3: filtering,调用pcf函数即可,使用penumbra过滤系数,对pcf的过滤范围缩放,也就是penumbra的值越大,取样的offest越大,从而形成更模糊的效果
可以看到随着 遮挡物和被投射物的距离越远,越趋近软阴影
模型旋转:
首先设置rotate的初始值和需要传输和调用的参数
*//数据修改
- 在engine
- setTransform函数中添加模型变换_旋转部分,这是修改顶点数据(负责初始时应用的旋转)
- Add mesh中设置模型旋转初始值
- Mesh.js中也加入旋转,以便可以接受模型的初始旋转变换
- MeshRender.js中CameraParameters中添加旋转,要让相机接受模型的旋转数据
- //平行光MVP
- DirectionalLight.js
- 修改灯的顶点数据,虽然灯不用变换位置,但灯也用到了setTransform参数需要传入参数
- 也需要更改CalcLightMVP生成的光照矩阵,添加rotate部分, 以便传入模型的mvp
- //材质
- PhongMaterial
- 构造添加rotate参数,因为在这里会调用CalcLightMVP
- buildPhongMaterial也添加rotate参数
- 同样在ShadowMaterial中也如PhongMaterial
- loadOBJ.js中调用创建材质时,传入参数
实时:
为了随时间增加旋转角度,需要在mainloop中获取deltatime: 然后在render中接受时间,并让网格体旋转矩阵
实时阴影
SM的渲染正是在渲染更新中,因此是每帧都会重新渲染
可以看到模型在旋转,模型旋转我们更改了顶点数据,以及glsl中的model矩阵
但阴影没有变,是因为我们没有向shadow glsl中传入旋转后的模型,
光源旋转
- 灯光要实时旋转,还要更改灯光的MVP,
多光源
- 首先为光源添加lightIndex编号
- 在engine中创建新光源
- 在WebGLRenderer中,需要将所有光源渲染结果叠加,要开启混合gl.enable(gl.BLEND);
第6章
渲染方程数据存储方式
L,P,V项既可以用之前的cubmap存储,也可以用球谐函数存储
SH vs cubmap:
SH:存储量相对较小,主要是基于球谐函数的数学运算效率较高,效果表现次于cubmap
数学知识简述
傅里叶级数展开
任何一个函数可以写成常数和一系列基函数,基函数数量越多越接近于原函数的形状
定义在周期函数上,展开为一组正弦和余弦的基函数,主要用于求解一维问题
复数
复数是在实数(一维的有理和无理)上的扩张,包含虚数i,属于二维方向,i^2 = -1,sqrt(-1)= i,既然属于2维,复数的几何运算和向量操作几乎一致
对于实数运算,可以看作在一维轴上的平移操作,对于相反数也可以理解为由x旋转180°到-x的位置,对于虚数i相当于旋转90°,因此i^2相当于旋转180°
filtering过滤,频率频谱
频率:图像信号数值的变化是否剧烈
频谱:存储了指定频域的频率,其中频谱中心是低频内容,越往外越高频
原图 * 卷积核 == 原图频谱 * 卷积核频谱(几乎仅保留低频部分)
对于任意的两个函数先乘积在积分,们将其认为是做了一个卷积操作,其中积分之后的频率取决于积分前最低的频率
蒙特卡洛与重要性采样
蒙特卡洛积分是求解积分(特别适用于难以求解的积分)的一种近似方式,它是利用离散求解小长方体的平均值,半径 == (b-a)/n, 高度 == f(xi),那么积分 == 求和(半径 * 高度)
以上是均匀采样,它会随着样本数量增加结果越发精确,如何不增加样本提高收敛速度呢?我们需要有偏的采样
对于重要性采样的期望(加权平均),即:1/n * 求和(f(xi) / pdf(xi)),也就是说pdf概率密度函数,1 / pdf(xi) 它作为采样的权重,为什么是除以pdf呢?因为概率和 == 1,每份数值 / 对应的概率 == 估计整体数值
Spherical Harmonics球谐函数SH
同傅里叶级数近似,它同样也是由一组系数和基函数组成,但它的基函数是球面函数(使用球坐标系:天顶角方位角),它可以看作是傅里叶在球面上的类比,用于求解球面上的问题
正交性:能够较简单地投影/重建
旋转不变性:旋转SH的基函数 == 同阶的SH基函数线性组合
2阶SH图像表示
根据球坐标系,r == 1, r == costheta, r == sintheta
3阶SH图像表示
蓝色表示正数,黄色为负,颜色越接近于白色值越大,越接近于黑色值越小,每行表示阶数,每阶2l + 1个基函数,每个小组合都是一个基函数即一个数学公式
SH_投影和重建
通过投影操作可以确立系数,已知了bi和系数,就可以重建到原来的Fx获得结果
越高的阶可恢复的细节就越好,但一方面是因为更多的系数会带来更大的存储压力、计算压力,并且高阶容易出现Artifact
根据积分之后的频率取决于积分前最低的频率的性质,因此可以使用比较低频的基函数去描述,用3阶就可以基本重建出正确率99%的结果