本文是OpenGL 4.0 Shading Language Cookbook的学习笔记。

Shadowmap的基础实现结合PCF可以产生软阴影。但是,如果想要更大宽度的软阴影,这种方法需要增加大量采样。对于完全位于阴影中的片段,以及完全位于阴影之外的片段,这显然造成了大量浪费。

在本文,我们介绍的技术基于GPU Gems 2, edited by Matt Pharr and Randima Fernando, Addison-Wesley Professional, 2005 第17章。它解决了软阴影对宽度的不同要求,同时避免了对位于阴影中和阴影外的片段的不必要的纹理采样。

它的基本思想如下:

  • 使用一个固定的偏移集合进行采样,我们这里使用的是一个随机的,圆形范围内的偏移集合,而不是在片段的周围(Shadowmap空间)进行采样。
  • 首先对接近圆形边缘的位置进行采样,判断片段是否完全位于阴影中或阴影外,从而确定是否还有继续采样的必要。

下图是一个采样位置的可视化表示。图的中心为片段在Shadowmap中的位置,每个x代表一个采样位置。采样位置随机分布在以片段位置为中心的圆中。

我们通过预先计算的采样模板来设置采样位置。我们计算出随机的采样位置,并将它存放在纹理中。然后在片段着色器,我们使用偏移纹理中的偏移值对片段在Shadowmap中的位置进行偏移采样。然后对采样结果求平均值。

下面左图使用PCF渲染,右图使用随机采样技术渲染。

我们将偏移数据存储在三维纹理中(大小为nxnxd),每个(s,t)位置处包含了一个二维随机偏移集合。每个RGBA颜色值包含了两个二维纹理偏移。R和G分量是第一个偏移值,B和A分量是第二个偏移值。比如,位置(1,1,3)包含了位置(1,1)处的第6个和第7个偏移值。(s,t)位置处的所有偏移值构成了整个偏移集合。

片段在偏移纹理中的位置由片段的坐标除以纹理大小后的余数决定。比如,如果片段的坐标为(10.0,10.0),纹理大小为(4,4),那么片段在偏移纹理中的对应位置为(2,2)。

实现

我们从之前的专栏文章《阴影贴图的GLSL实现》中的代码开始。

添加下面3个Uniform变量:

  • OffsetTexSize:偏移纹理的宽度,高度和深度数据。注意这个深度值和每个片段的采样个数的一半相同。
  • OffsetTex:指向偏移纹理的纹理单元句柄。
  • Radius:以像素为单位的模糊半径除以Shadowmap纹理的大小得到(假设Shadowmap的形状是正方形)。这个参数可以被作为阴影的软度。

我们采取下面的步骤使用随机采样生成软阴影。我们首先生成偏移纹理,然后在片段着色器中使用它:

1. 使用下面的代码创建偏移纹理(只需要在初始化时执行一次):

void buildOffsetTex(inttexSize, intsamplesU, intsamplesV) {int size = texSize;int samples = samplesU * samplesV;int bufSize = size * size * samples * 2;float *data = new float[bufSize];for( int i = 0; i< size; i++ ) {for(int j = 0; j < size; j++ ) {for( int k = 0; k < samples; k += 2 ) {int x1,y1,x2,y2;x1 = k % (samplesU);y1 = (samples - 1 - k) / samplesU;x2 = (k+1) % samplesU;y2 = (samples - 1 - k - 1) / samplesU;vec4 v;// Center on grid and jitterv.x = (x1 + 0.5f) + jitter();v.y = (y1 + 0.5f) + jitter();v.z = (x2 + 0.5f) + jitter();v.w = (y2 + 0.5f) + jitter();// Scale between 0 and 1v.x /= samplesU;v.y /= samplesV;v.z /= samplesU;v.w /= samplesV;// Warp to diskint cell = ((k/2) *size*size + j *size + i) * 4;data[cell+0] = sqrtf(v.y) * cosf(TWOPI*v.x);data[cell+1] = sqrtf(v.y) * sinf(TWOPI*v.x);data[cell+2] = sqrtf(v.w) * cosf(TWOPI*v.z);data[cell+3] = sqrtf(v.w) * sinf(TWOPI*v.z);}}}glActiveTexture(GL_TEXTURE1);GLuint texID;glGenTextures(1, &texID);glBindTexture(GL_TEXTURE_3D, texID);glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA32F, size, size, samples/2, 0, GL_RGBA, GL_FLOAT, data);glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);delete [] data;
}
// Return random float between -0.5 and 0.5
float jitter() {return ((float)rand() / RAND_MAX) - 0.5f;
}

2. 在片段着色器添加下面的Uniform变量:

uniform sampler3D OffsetTex;
uniform vec3 OffsetTexSize; // (width, height, depth)
uniform float Radius;

3. 在片段着色器使用下面的代码作为shadeWithShadow函数的代码:

subroutine (RenderPassType)
void shadeWithShadow()
{vec3 ambient = Light.Intensity * Material.Ka;vec3 diffAndSpec = phongModelDiffAndSpec();ivec3 offsetCoord;offsetCoord.xy = ivec2( mod( gl_FragCoord.xy,OffsetTexSize.xy ) );float sum = 0.0;int samplesDiv2 = int(OffsetTexSize.z);vec4 sc = ShadowCoord;for( int i = 0 ; i< 4; i++ ) {offsetCoord.z = i;vec4 offsets = texelFetch(OffsetTex,offsetCoord,0) *Radius*ShadowCoord.w;sc.xy = ShadowCoord.xy + offsets.xy;sum += textureProj(ShadowMap, sc);sc.xy = ShadowCoord.xy + offsets.zw;sum += textureProj(ShadowMap, sc);}float shadow = sum / 8.0;if( shadow != 1.0 && shadow != 0.0 ) {for( int i = 4; i< samplesDiv2; i++ ) {offsetCoord.z = i;vec4 offsets = texelFetch(OffsetTex, offsetCoord,0)  *Radius * ShadowCoord.w;sc.xy = ShadowCoord.xy + offsets.xy;sum += textureProj(ShadowMap, sc);sc.xy = ShadowCoord.xy + offsets.zw;sum += textureProj(ShadowMap, sc);}shadow =sum/float(samplesDiv2 * 2.0);}FragColor = vec4(diffAndSpec * shadow + ambient, 1.0);
}

原理

函数buildOffsetTex用于创建三维随机偏移纹理。它的第一个参数texSize用于指定纹理的高度和宽度。对于前面的图像,我们使用的值为8.第2个和第3个参数samplesU和samplesV用于指定在u方向和v方向上的采样个数。我们在这里使用的值是4和8,总共32个样本。下图可以帮助我们理解:

初始时,偏移值被定义在samplesU x samplesV(图中是4x4)的每个格子的中心。接着,我们对每个样本点进行在它所处的格子中进行随机位置改变。然后,将网格转换成上面右图的形式。

最后一步,通过将v坐标作为采样点到圆心的距离,u坐标作为旋转的角度的比例完成。下面的式子可以完成这一步:

式子中的

就是转换后的坐标。到此为止,我们得到了一个围绕原点的偏移集合。采样时,我们首先在圆的边缘进行,逐步向圆心移动,这样就可以避免完全处于阴影内或阴影外的样本的不必要采集。

我们将两个偏移数据包装在一个纹素中。这样做不是必须的,但可以节约内存,虽然它让代码变得复杂了些。

在片段着色器,我们分开计算光照模型的环境光成分和散射光,镜面光成分。我们使用片段的屏幕空间坐标(gl_FragCoord)访问偏移纹理。我们使用片段坐标对偏移纹理大小进行取余运算,然后将结果存储在变量offsetCoord的前两个分量中。这样做后,相邻片段就会使用不同的偏移集合。

最后,我们使用变量shadow来对散射光和环境光成分进行衰减。

优化

使用随机采样技术可以产生比PCF技术效果更好的阴影模糊边缘。但是,在阴影边缘仍然会出现走样现象,这是因为纹理的大小是有限的,偏移仍然会在部分像素重复。我们可以通过在片段着色器中随机旋转偏移纹理或在着色器中随机计算偏移来减少这个问题。

并不是所有的阴影都需要有一个模糊的边缘。比如和遮光物体直接相邻的阴影边,不应该是模糊的。模糊边缘使遮挡物看起来飘在表面上一样。但这个问题,修复起来比较困难。

unity3d软阴影和硬阴影的原理_使用随机采样创建软阴影相关推荐

  1. unity3d软阴影和硬阴影的原理_人像摄影的重要技法:控制、利用阴影

    刚接触人像摄影时,很多人对人脸上阴影十分 "忌惮",甚至因此偏好使用顺光,或通过个方位补光来完全消除阴影. 这样的操作,往往会拍出明亮.干净,对比度小的图像.或许这符合一些题材的要 ...

  2. unity3d软阴影和硬阴影的原理_在广告摄影中阴影和高光的重要作用和控制技巧...

    投影和高光在大多数广告摄影画面中是不可避免的(除非像无投影布光或被摄体为非光洁表面),但又往往是影像的重要的组成部分. 投影和高光的存在,既有助于造型和质感的表现,但处理不当时,又会破坏画面和使人感到 ...

  3. unity3d软阴影和硬阴影的原理_手术无影灯的原理

    手术无影灯是将灯的角度或者抛光反射面的角度调节成一种环形光照,从而达到照射部位结构凸凹形成的暗影或死角,成为亮度均匀的画片,为手术区域提供照明的专用设备. 手术无影灯是利用多点光源效应的原理设计的,即 ...

  4. unity3d软阴影和硬阴影的原理_Unity3D中两种默认阴影的实现

    Unity3D中两种阴影的实现 传统的ShadowMap ShadowMap说起来十分简单,把摄像机和光源的位置重叠,那么场景中该光源的阴影区域就是那些摄像机看不到的地方,主要应用在前向渲染路径中. ...

  5. python随机森林特征重要性原理_用随机森林进行特征重要性度量,筛选出来的重要特征是否只对该随机森林来说是重要的,而对其他模型不一定?...

    你的问题可以给出肯定的回答,重要性本来就是一个依赖于模型的指标,一些指标对模型A重要,但对B未必重要. 举个很简单的例子,在欧式距离中,量纲很重要,数值大距离就大数值小距离就小.而在余弦夹角表示距离时 ...

  6. 2d shader unity 阴影_【Unity Shader】平面阴影(Planar Shadow)

    来介绍一种适用于移动平台的高性能实时阴影解决方案--平面阴影(Planar Shadow). 由于Unity内置的实时阴影实现方式是屏幕空间阴影贴图(Screen Space Shadow Map)非 ...

  7. 视频教程-2020年软考信息安全工程师_基础知识精讲软考视频培训课程-软考

    2020年软考信息安全工程师_基础知识精讲软考视频培训课程 河北师范大学软件学院优秀讲师,项目经理资质,担任操作系统原理.软件工程.项目管理等课程教学工作.参与十个以上百万级软件项目管理及系统设计工作 ...

  8. (八)unity自带的着色器源码剖析之——————Unity3D的全局光照和阴影:下篇(unity3D中的球谐光照和SH球谐函数、unity实时阴影抗锯齿解决方案)

    一.探针基于球谐函数的全局光照 球谐光照是基于预计算辐射度传输理论实现的一种实时渲染技术.预计算辐射度传输技术能够重现在区域面光源照射下的全局照明效果.这种技术通过在运行前对场景中光线的相互作用进行预 ...

  9. ps拨号服务器原理_呼叫中心的原理和功能

    呼叫中心作为一个市面上很成熟的产品已经非常稳定,现在各呼叫中心供应方越来越多研究人工智能方向的功能并应用到各类企业的业务场景上,为企业赋能.那么接下来,我以成都添闰通讯技术有限公司的产品:添添呼呼叫中 ...

最新文章

  1. 深度 | 解决真实世界问题:如何在不平衡类上使用机器学习?
  2. mfc获取鼠标在其他窗口中坐标_C井编程,稍加修改,将之前“会跑的按钮”改成“会跑的窗口”...
  3. MAC电脑数据迁移方法
  4. SAP Cloud for Customer里的Sales Lead和Lead
  5. 用pycharm搭建odoo 12, 11,10 开发调试环境
  6. dw选项卡怎么设置_EXCEL入门之设置
  7. FineUI分组显示弹框最新的在最上边
  8. 开源考试系统 -微信小程序开发
  9. ADAS/AD控制器模块开发03 - 系统架构设计及通用需求定义
  10. 作业三-读书app原型设计
  11. 三节锂电池充电芯片,IC设计模块的几种电路
  12. 计算机flash听课记录范文,听课记录范文
  13. 你见过马化腾18年前编写的代码吗?
  14. Android 三大图片缓存原理、特性对比
  15. 分享一个精灵盛典辅助工具挂机方案
  16. Thinkbook16+ 2022 安装Ubuntu20.04
  17. 搭建LNMP平台加NFS文件共享部署wordpress博客
  18. 如何在iPad,iPad mini,iPad Air和iPad Pro之间进行选择?
  19. xmanager 5下载安装
  20. 戴尔p2314ht显示器拆解,清洁

热门文章

  1. SVN的使用及MyEclipse的集成
  2. 基于Redis的微博的注册
  3. c++游戏开发案例源代码_1人开发千万下载,爆款游戏TENKYU调优案例
  4. 【答辩问题】计算机专业本科毕业设计答辩问题
  5. HALCON 20.11:深度学习笔记(7)---术语表
  6. 解决 Qt5 报错 This application failed to start because it could not find or load the Qt platform plugin
  7. 防伪拉线 CCD 纠偏控制器
  8. VC静态加载DLL和动态加载DLL
  9. Halcon 学习总结——电子加密狗字符检测(ocr_dongle)
  10. MaxCompute 图计算用户手册(下)