渲染平行光阴影
阴影作用:
- 有了阴影的渲染,更容易地区分出物体之间的位置关系,
- 如何判断片段是否在阴影中?
普通思路:
- 以光的位置为视角进行渲染,我们绘制一条从光源出发的射线,测试更新射线经过的所有片段,与当前min最近片段比较,如果更远,就在阴影中
弊端:
- 射线上的成千上万个点进行遍历是个极端消耗性能,所以换种思路,利用深度缓冲
深度缓冲:
- 深度缓冲大小符合窗口的宽高,当运行深度测试时,每个像素的深度值和已经存储在这个像素的深度值(深度缓冲)进行比较,根据深度测试函数,决定片段是否丢弃
深度/阴影/shadow Mapping贴图:
- 从光源的透视图 来渲染场景,并把深度值的结果储存到纹理中,深度值因为会不断测试更新,最后的结果,就是最近的片段的深度值,通过纹理坐标,可以对它进行采样,获取深度值
T变换:
- 来自光源的视图投影矩阵,可以将任何三维位置(从世界坐标)转变到光源的可见坐标空间
如何利用深度贴图判断片段是否在阴影中?
- 首先T变换 到光源的坐标空间里,使用生成的深度贴图 来计算片段是否在阴影之中,z深度 如果 > 片段的深度缓冲的深度值,则在阴影中
运算:
创建新的帧缓冲,
- 因为要对深度值采样,所以应存储在纹理附件(非渲染缓冲对象附件),应用的纹理格式为GL_DEPTH_COMPONENT,
- 注意:不包含颜色缓冲的帧缓冲对象是不完整的,所以我们需要显式告诉OpenGL我们不适用任何颜色数据进行渲染。glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE);
平行光源空间的T变换矩阵:
-
LookAt()计算光(位置+看的方向)的view矩阵(实质,将物体坐标变换到相机坐标的视角下),
-
如果使用一个所有光线都平行的定向光,使用正交投影矩阵,否则点光源和聚光灯,使用透视投影矩阵(因为投影矩阵实质,是决定可见的范围,以及范围的形状,光空间的范围,即光线的散射组成的范围,被光覆盖的范围)
渲染到深度贴图:
- 启用帧缓冲,渲染场景,使用的新glsl中,将每个顶点变换到光空间,从而深度值保存到了深度纹理附件中,fragment什么都不用做,我们只需要深度缓冲(默认更新),也可以手动设置gl_FragDepth = gl_FragCoord.z;
//深度缓冲视觉化:
- 这次渲染quad,并应用深度纹理附件作为quad 的纹理。
- 但是注意:正交不受深度值影响,透视投影矩阵,当深度缓冲视觉化经常会得到一个几乎全白的结果,深度变成了非线性的深度值,只有距离很近,颜色亮度才会骤降。
- 解决方案就是变为线性深度值。
深度缓冲视觉化
渲染阴影:Fragment glsl
- 将要产生阴影的物体,在vs中将世界空间顶点位置转换为光空间,传入fs,计算出一个shadow值,当fragment在阴影中时是1.0,在阴影外是0.0
- 将(1 - shadow )* 其他冯氏颜色分量(由于当shadow = 1的时候(在阴影),结果颜色为0),由于阴影不会是全黑的,把ambient分量从乘法中剔除。
- 这样保证,当片断位于阴影,结果为ambient * 其他冯氏颜色分量,当非阴影 = ((ambient + 1 * 其他冯氏颜色分量
计算阴影():
- 光空间的片段位置(被判断的像素)和第一个渲染阶段得到的深度贴图(取深度值)
- 光空间片段位置转换为裁切空间的标准化设备坐标。
- 当运行到gl_Position时,OpenGL自动进行一个透视除法,x、y、z元素除以向量的w元素来实现,我们必须自己做透视除法
- 因为来自深度贴图的深度在0到1的范围,我们再次将NDC坐标变换为0到1的范围
- 从深度贴图,采样片段坐标,对应的深度值,如果片段的z高于采样的深度值,则表明片段在阴影中,返回1,否则返回0