文章

Games202(3·作业1,第6章)

作业1

首先作业框架为我们绘制了场景,但是无阴影效果:

1736590619431

这是包含阴影的PBR 公式: V + L + B

1736655532337

框架解析:

  • 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);

1736667896409

自遮挡:

有两种方法解决子遮挡,我们用自适应的方法

1736671443331

根据这个公式,我们添加一个getShadowBias函数,如图所示,

  • A = (1 + 取整(在uv上的过滤半径大小))* (平截头体大小 / (阴影贴图大小 * 2))
  • B = 1 - dot(法线,光线方向)
  • Bias = 常数 * AB

修改useShadowMap函数,cur_depth - bias >= depth

1736671464792

pcf:

让shaderpoint和shadowmap一圈的范围比较,获取平均值

  • 写fs中的pcf函数,
    • 为了优化性能,使用poissonDiskSamples生成随机种子保存在poissonDisk数组中,作为offest位置,采样NUM_SAMPLES指定次数次,最后+=求和的结果求平均值,
    • 其中filterRadiusUV过滤半径取:FILTER_RADIUS / SHADOW_MAP_SIZE得到uv(0–1)中的大小
    • 这里有一点注意的地方,useShadowMap()应放在pcf()的上面,否则会调用失败

1736671481108

pcss:

步骤如下:

  • STEP 1: avgblocker depth,找到平均block深度在findBlocker()中计算
    • 首先确定在shadowmap中应该采样的区域,它和3个参数有关
      • 光的大小(越大,block范围越大)
      • 近平面距离shaderpoint的距离(距离越远,block范围越大)
      • 光到shaderpoint的距离有关(距离越远,block范围越小)
    • 然后在此范围内像pcf一样随机采样,取block平均值
  • STEP 2: penumbra size,根据课上的公式计算penumbra过滤系数(即filterRadiusUV)(其实可以理解为,近处pcf程度低,远处pcf程度高)
    • float penumbra = (zReceiver - avgBlockerDepth) * LIGHT_SIZE_UV / avgBlockerDepth;
  • STEP 3: filtering,调用pcf函数即可,使用penumbra过滤系数,对pcf的过滤范围缩放,也就是penumbra的值越大,取样的offest越大,从而形成更模糊的效果

可以看到随着 遮挡物和被投射物的距离越远,越趋近软阴影

1736675710806

模型旋转:

首先设置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

数学知识简述

傅里叶级数展开

1737558031198

任何一个函数可以写成常数和一系列基函数,基函数数量越多越接近于原函数的形状

定义在周期函数上,展开为一组正弦和余弦的基函数,主要用于求解一维问题

复数

复数是在实数(一维的有理和无理)上的扩张,包含虚数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图像表示

1737558031198

根据球坐标系,r == 1, r == costheta, r == sintheta

3阶SH图像表示

1737558031198

蓝色表示正数,黄色为负,颜色越接近于白色值越大,越接近于黑色值越小,每行表示阶数,每阶2l + 1个基函数,每个小组合都是一个基函数即一个数学公式

SH_投影和重建

通过投影操作可以确立系数,已知了bi和系数,就可以重建到原来的Fx获得结果

越高的阶可恢复的细节就越好,但一方面是因为更多的系数会带来更大的存储压力、计算压力,并且高阶容易出现Artifact

根据积分之后的频率取决于积分前最低的频率的性质,因此可以使用比较低频的基函数去描述,用3阶就可以基本重建出正确率99%的结果

本文由作者按照 CC BY 4.0 进行授权
本页总访问量