关于计算机图形学:游戏中的动态阴影下

本篇分为高低两篇,上篇内容请关注:游戏中的动静暗影(上) 六、基于Shadowmap实现软暗影1. Percentage-Closer Filtering(PCF)采样Shadowmap时,咱们往往这样来实现一些软暗影的成果:在指标采样点四周,进行四次采样,而后取平均值,作为最终后果。留神这里的取平均值,并不是取平均值后进行比拟,而是对四个采样点,别离进行深度测试,而后每个采样点的0或1的后果值进行均匀,这样在半影区域就能失去软暗影成果。 这种将采样后果进行均匀的形式叫做Percentage-Closer Filtering(PCF)[6],PCF通过将指标点左近的采样后果均匀,来模拟出半影的成果。 当初的硬件都间接提供四周四点采样的加权PCF深度测试,比方OpenGL中的sampler2DShadow,DirectX中的SampleCmp。这种采样的加权形式相似于一般像素采样时的双线性采样,在指标地位左近2X2像素中,逐像素进行深度比拟,失去后果值0或1,而后将后果依照绝对四周像素地位进行加权均匀。 间接应用硬件PCF,只能采样到2X2的像素点,失去的半影过渡,往往不够柔和。如果想要更加柔和的暗影过渡,或者把半影区域扩充,就须要将采样点散布范畴扩充,也须要减少采样点的个数。 简略的形式,是间接在指标点四周依照Grid模式进行采样,然而这样往往会在半影中看到分层的瑕疵。 因而咱们更加罕用的形式,是应用预计算好的Possion散布的采样点,来进行采样。为了使后果进一步平滑,咱们还能够应用逐像素的噪声值,对采样点地位进行旋转,这样每两个相邻的像素点,采样的模式都是不同的,能够无效地平滑半影区域。从左到右顺次是:4X4的Grid采样12点Possion采样12点Possion采样+旋转Possion分布图 2. PCF软暗影的Bias问题在后面咱们曾经讲过Bias的问题,在PCF采样中,因为PCF采样Shadowmap的范畴会比拟大,因而会进一步暴露出Shadow Acane的问题。当然咱们也有响应的伎俩来解决这些问题。 一种简略的形式,是依据PCF filter kernel的大小,来动静扭转Shadow Bias的大小,当然这样做的毛病也很显著,就是PCF kernel越大,就会损失越多的暗影精度信息。 另外一种形式是Bias Cone,依据以后采样点到采样核心的地位,来缩放Bias的大小,如下图右边所示。是一种绝对简略无效的缓解Bias问题的计划。左:Bias Cone;右:Receiver Plane Depth Bias 上图左边显示的一种逐采样点来做准确Bias的算法:Receiver Plane Depth Bias。这种形式须要假设承受暗影的是一个立体,而后会依据每个暗影采样点到核心的地位,来计算偏移。个别能产生十分好的后果[7]。 3. Percentage-Closer Soft Shadows(PCSS)PCF暗影的一个毛病,就是半影的宽度十分固定,无论产生暗影的地位间隔光照有多远,半影的宽度都是一样的。 PCSS[8]通过判断半影到遮挡物和半影到光源的间隔,来动静确定半影的宽度。半影宽度越大,采样暗影的模式散布也越大,就能失去越柔和的暗影。这样就能失去如下图左边所示的,随间隔变动的暗影成果。左:PCF硬暗影,中:PCF软暗影,右:PCSS暗影 PCSS算法分成这样几个步骤: 计算出区域内均匀Blocker深度;依据Blocker深度,计算出须要的半影宽度;用半影宽度,作为PCF kernal的大小,计算出暗影。PCSS的计算其实很简略,就是依据三角形类似,来计算出采样所需的散布间隔,而后将间隔内的采样值进行均匀。 不过当半影宽度十分大时,就须要十分多的采样点,这样采样Shadowmap的开销也会变大。因而PCSS是一种不太稳固的软暗影计划,在游戏中的理论利用并不是特地多。 七、基于Shadowmap的逐物体暗影/Per Obejct Shadow1. Modulated shadow的实现后面咱们讲到的立体暗影,只能投射暗影到立体上,在应用Shadowmap保留深度后,就能够将暗影投射到任意的曲面上,具体放办法如下: 首先咱们失去须要渲染暗影物体的AABB,而后将AABB转换到Light Sapce,失去新的 Orthogonal Light Space ABB。而后咱们将物体的深度渲染到一张Shadowmap中。 咱们将Light Sapce的AABB沿着光照方向进行缩短,就失去了一个Shadow Volume。 接下来咱们就能够应用这个Shadow Volume来失去投射暗影了。将Shadow Volume作为几何体进行渲染,在Shader中读取以后地位的Depth值,反算出世界坐标,再通过投影矩阵算出光照空间下的深度值,在Shadowmap中进行采样,失去暗影。将最终输入后果的混合形式为DstColor Zero,这样,被遮挡区域有暗影的地位,色彩都这样乘以一个暗影系数,失去一个染色的成果,也就实现了Modulated shadow。 留神,为了避免在不须要暗影的区域渲染出暗影,咱们须要在代码中进行clip,如果计算出Shadowmap中对应的uv坐标超出0~1的范畴,就不再渲染暗影。在Unity中实现的Shader代码大抵如下: float4 frag (v2f i) : SV_Target{float2 uv = i.vertex.xy * (_ScreenParams.zw - 1);float depth = tex2D(_CameraDepthTexture, uv).r;#if !UNITY_REVERSED_Zdepth = depth * 2 - 1;#endif#if UNITY_REVERSED_Zuv.y = 1 - uv.y;#endiffloat4 clipPos = float4(2.0f * uv - 1.0f, depth, 1.0);////反算出世界空间坐标float4 worldSpacePos = mul(UNITY_MATRIX_I_VP, clipPos);worldSpacePos /= worldSpacePos.w;////失去shadowmap中uv坐标float4 projectorPos = mul(_WorldToProjector, worldSpacePos);#if UNITY_REVERSED_ZprojectorPos.z = clamp(projectorPos.z, 0.0001, 1);#elseprojectorPos.z = clamp(projectorPos.z, 0, 0.9999);#endif// uv不在0~1范畴内,不须要暗影clip(projectorPos.xy);clip(1 - projectorPos.xy);projectorPos.xy = projectorPos.xy * _ShadowmapTex_ST.xy + _ShadowmapTex_ST.zw;float shadow = SAMPLE_TEXTURE2D_SHADOW(_H3D_GroundShadowmapTex, sampler_H3D_GroundShadowmapTex, projectorPos.xyz).r;return shadow;}Modulated shadow有这样两个显著的毛病: ...

May 5, 2023 · 2 min · jiezi

关于计算机图形学:游戏开发中的渲染加速算法总结

提纲1、罕用空间数据结构 • 档次突围盒• BSP树• 八叉树• 场景图2、各种裁剪技术 • 反面裁剪• 视锥裁剪• 遮挡剔除• 档次视锥裁剪• 入口裁剪• 细节裁剪3、各种档次细节(LOD,Level of Detail)技术 · 几种LOD切换技术· 几种LOD选取技术4、大型模型的渲染5、点渲染 空间数据结构1、概念空间数据结构是将几何体组织在N维空间中的一系列数据结构2、利用场景治理、裁剪算法、相交测试、光线追踪、碰撞检测3、留神 · BSP树和八叉树(四叉树的三维推广)都是时机空间细分(将整个空间合成)的数据结构,他们将整个场景空间进行细分并编码到数据结构中。其中八叉树的划分是规定的,而BSP则大都是不规则的。· 档次突围盒不是空间细分构造(不须要笼罩整个场景),它仅将汇合物体四周的空间包围起来。【1】档次突围盒(Bounding Volume Hierarchies ,BVH)核心思想· 应用体积略大而几何特色简略的突围盒来近似的形容简单的几何对象,从而只须要对突围盒重叠的对象进行进一步的相交测试· 层层相套,单个物体能够作为子节点,多个蛋物体子节点造成一个多物体子节点,最初的根节点蕴含场景中所有的物体。如图所示: 【2】BSP树(二叉空间宰割树,Binary Space Partitioning)· 常用语判断对象可见性的数据结构,相似于画家算法,BSP能够不便的将外表由后往前地在屏幕上渲染进去,特地实用于场景中对象固定不变,仅视点挪动的状况。(补充为什么)· BSP树的构造方法补充。。。· 轴对齐BSP树 K-d树· 多边形对齐BSP树【3】八叉树· 概念:形容三维空间的树状数据结构,每个节点示意一个正方形的体积元素,每个节点分为八个字节点,这八个子节点所示意的体积元素相加等于父节点的体积,个别中心点作为节点的分叉核心。 【4】场景图(Scene Graphs)·概念组织和治理三维虚构场景的一种数据结构,是一个有向无环图,由一棵一深度优先遍从来渲染整个场景的树来示意。·开源的场景图Open Scene GraphOpenSG

October 19, 2020 · 1 min · jiezi

关于计算机图形学:高级着色BRDF及相关技术

提纲BRDF的前置常识:数学篇BRDF的前置常识:辐射度量学篇BRDF的定义与了解BRDF的性质BRDF的模型分类基于物理的BRDF前置常识【1】BRDFBidirectional Reflectance Distribution Function双向反射散布函数形容的是:物体外表将光能从任何一个入射方向反射到任何一个视点方向的反射个性,即入射光线通过某个外表反射后如何在各个出射方向上散布,单位是每球面度。BRDF模型是绝大多数图形学算法中用于形容光反射景象的根本模型【2】前置常识:数学篇 • 球面坐标 r: 示意向量的长度 :示意向量和Z轴的夹角 :示意向量在x-y立体的投影和x轴的逆时针夹角 • 立体角立体角形容了从原点向一个球面区域张成的视线大小,能够看成是弧度的三维扩张立体角度量三维角度,用符号示意,单位为平面弧度(也叫球面度,写为sr),等于立体角在单位球上对应的区域的面积1.首先了解二维立体的弧度: 圆心角的大小与弧长没有特定的关系,圆心角的掂量只有在单位圆上时才与弧长大小无关,比方一个同心圆,穿过两个大小不一的圆形时,对应的弧长不同,然而角度是雷同的,因而角度是形容张开的范畴。 2.立体角定义: 与圆心角雷同,掂量立体角应以单位球为根底,形容的在单位球上面积张开的大小,他的形态能够很多样化,他是形容这块投影面积占整个单位球面面积的比例大小。当这个所占比例雷同时,则立体角雷同(形态能够不同)因而,所有立体角能够拼凑为一个球的外表,各个立体角所对应的面积块能够大小不一,形态怪异【3】前置常识:辐射度量学篇 【4】BRDF的定义与了解 • BRDF的定义式定义:BRDF是用来形容外表如何反射光线的方程,即形容光如何从给定的两个方向(入射光方向l和出射方向v)在外表进行反射的函数。示意:出射辐射率的微分和入射辐照度的微分之比:∫(1,v)=(dL_0 (v))/(dE(l))当光源为非区域光源时,如点光源或方向光源,这种状况下BRDF能够用非微分的模式表白 • BRDF能够用n个非区域光源来拟合着色方程【5】BRDF的模型分类• 可逆性交互入射光和反射光,并不会扭转BRDF的值• 能量守恒性质【6】BRDF的分类教训模型: 定义:应用基于试验提出的公式对BRDF做疾速预计 • Phone模型 • Lambert模型 • Bling-Phone模型 • 疾速Phone模型 • 可逆Phone模型 数据驱动模型: 定义:采集真是资料外表在不同光照角度和察看角将BRDF依照实测数据建设查找表,记录在数据库中,以便于疾速的查找和计算。 该办法因为采样点密集,数据量宏大,因而适宜离线渲染不适宜实时渲染基于物理的BRDF模型: 定义:通过蕴含材质的各种汇合及光学性质来尽可能准确的近似事实世界中的资料。 • 必须满足两个个性 一:能量守恒(出射的光洁不能大于入射的光洁) 二:可逆性(你在注视深渊,深渊也在注视你,亮度比例雷同,但不肯定亮度雷同) • 基于物理的渲染(PBR)是用数学建模的办法模仿物体外表各种材质散射光线的属性从而渲染照片帧是图片的技术。 • Cook-Torrance BRDF 模型(微立体着色模型,基于物理着色的规范模型之一) 微立体:立体不止一个法线,其实应该有很多法线,此时不应思考各个方向上的法线比例。 • Ward BRDF 模型【7】菲涅尔反射(Fresnel Reflectance)定义:是一组用于形容光在两种不同折射率的介质中流传时的反射和折射的光学方程 。即当光入射到折射率不同的两个材质的分界面时(如水和空气),一部分光会被反射,而咱们所看到的光线会依据咱们的察看角度以不同强度反射的景象。除了金属之外,其余物质均有不同水平的菲涅尔反射成果。 • 眼帘垂直于外表时,反射较弱,而过后先并非垂直外表时,夹角越小,反射越显著。 • 影响菲涅尔效应的要害参数是(1)每个微立体的法向量(2)入射光线的角度,因而咱们在宏观层面看到的实际上是微立体的菲涅尔效应的一个均匀后果。举例:从水池垂直向下看能够看到池底,但如果从靠近平行于水面的方向看上来则看不到池底。【8】法线散布函数(Normal Distribution Function,NDF)定义:D(h)来形容组成外表一点的所有微立体的法线散布概率。即向NDF输出一个朝向h,NDF会返回朝向是h的为外表数占微外表总数的比例,比方有8%的微外表朝向是h,则有8%的微外表可能将光线反射到v方向。次外表散射和漫反射是一样的。不同之处在于光线在被排汇或流传回去之前能够在外表之下的扩散距离. ...

October 19, 2020 · 1 min · jiezi

常见图片格式了解

前言作为一个客户端开发,对于图片格式一直没有一个清晰的了解,这里简单的罗列出各种图片格式的区别,文章中有部分是他人的引用,会在底部放上链接,望轻喷。概念了解有损压缩 & 无损压缩有损压缩(lossy compression):有损压缩算法是一种数据压缩方法,经过此方法压缩、解压的数据会与原始数据不同但是非常接近。它是与无损数据压缩相对的压缩方法。有损数据压缩又称破坏性资料压缩、有损压缩、有损压缩、不可逆压缩。其原理是借由将次要的信息数据舍弃,牺牲一些质量来减少数据量、提高压缩比。这种方法经常用于压缩多媒体数据(音频、视频、图片)。根据各种格式设计的不同,有损数据压缩都会有代间损失——每次压缩与解压文件都会带来渐进的质量下降。无损压缩(Lossless Compression):指数据经过压缩后,信息不受损失,还能完全恢复到压缩前的原样。无损压缩通常用于严格要求“经过压缩、解压缩的数据必须与原始数据一致”的场合。典型的例子包括文字文件、程序可执行文件、程序源代码。有些图片文件格式,例如PNG和GIF,使用的是无损压缩。索引色 & 直接色索引色:索引颜色是一种以有限的方式管理数字图像颜色的技术,以节省计算机内存和文件存储,同时加速显示刷新和文件传输。即用一个数字来代表(索引)一种颜色,在存储图片的时候,存储一个数字的组合,同时存储数字到图片颜色的映射。这种方式只能存储有限种颜色,通常是256种颜色,对应到计算机系统中,使用一个字节的数字来索引一种颜色。索引色常见有1位(即黑白),8位(即灰阶/256色),16位(即高彩),24位(即真彩),30/36/48位(即全彩),更多详细参考该百科。 直接色:使用四个数字来代表一种颜色,这四个数字分别代表这个颜色中红色、绿色、蓝色以及透明度(即rgba)。现在流行的显示设备可以在这四个维度分别支持256种变化,所以直接色可以表示2的32次方种颜色。当然并非所有的直接色都支持这么多种,为压缩空间使用,有可能只有表达红、绿、蓝的三个数字,每个数字也可能不支持256种变化之多。位图 & 矢量图:位图:位图[bitmap],也叫做点阵图,栅格图像,像素图,简单的说,就是最小单位由像素构成的图,缩放会失真。构成位图的最小单位是像素,位图就是由像素阵列的排列来实现其显示效果的,每个像素有自己的颜色信息,在对位图图像进行编辑操作的时候,可操作的对象是每个像素,我们可以改变图像的色相、饱和度、明度,从而改变图像的显示效果。举个例子来说,位图图像就好比在巨大的沙盘上画好的画,当你从远处看的时候,画面细腻多彩,但是当你靠的非常近的时候,你就能看到组成画面的每粒沙子以及每个沙粒单纯的不可变化颜色。矢量图:矢量图[vector],也叫做向量图,简单的说,就是缩放不失真的图像格式。矢量图是通过多个对象的组合生成的,对其中的每一个对象的纪录方式,都是以数学函数来实现的,也就是说,矢量图实际上并不是象位图那样记录画面上每一点的信息,而是纪录了元素形状及颜色的算法,当你打开一幅矢量图的时候,软件对图形相对应的函数进行运算,将运算结果[图形的形状和颜色]显示给你看。无论显示画面是大还是小,画面上的对象对应的算法是不变的,所以,即使对画面进行倍数相当大的缩放,其显示效果仍然相同[不失真]。举例来说,矢量图就好比画在质量非常好的橡胶膜上的图,不管对橡胶膜怎样的常宽等比成倍拉伸,画面依然清晰,不管你离得多么近去看,也不会看到图形的最小单位。图片类型BMPBMP取自位图BitMaP的缩写,也称为DIB(与设备无关的位图),是一种与显示器无关的位图数字图像文件格式。BMP同时支持索引色和直接色,但是其几乎没有压缩,所以通常图片非常的大,也导致了其几乎没有用武之地,现在除了在Windows操作系统中还比较常见之外,我们几乎看不到它。再加上浏览器的不支持,所以作为web开发,更加少于看到BMP。这里简单描述一下BMP解析成二进制时的结构:位置含义bmp文件头(bmp file header)提供文件的格式、大小等信息位图信息头(bitmap information)提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息调色板(color palette)(如果有的话)如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表位图数据(bitmap data)则图片数据GIF全称Graphics Interchange Format,采用LZW压缩算法进行编码。是无损的、采用索引色的、点阵图。GIF是无损的,采用GIF格式保存图片不会降低图片质量。但得益于数据的压缩,GIF格式的图片,其文件大小要远小于BMP格式的图片。文件小,是GIF格式的优点,同时,GIF格式还具有支持动画以及透明的优点。但,GIF格式仅支持8bit的索引色,即在整个图片中,只能存在256种不同的颜色。简单介绍下GIF使用的LZW压缩算法,详细可参考该文章:LZW编码 (Encoding) 的核心思想其实比较简单,就是把出现过的字符串映射到记号上,这样就可能用较短的编码来表示长的字符串,实现压缩。 比如: 我们可以将ABCDEFG 转成 1 来代表, 这样数据就会减少很多。再加上,LZW编码是自解释 (self-explaining) 的,即映射字典不会写到压缩数据里,他是在解码的过程中还原出编码时用的字典。JPEGJPEG是有损的、采用直接色的、点阵图。JPEG也是一种针对照片影像而广泛使用的有损压缩标准方法。JPEG图片格式的设计目标,是在不影响人类可分辨的图片质量的前提下,尽可能的压缩文件大小。这意味着JPEG去掉了一部分图片的原始信息,也即是进行了有损压缩。JPEG的图片的优点,是采用了直接色,得益于更丰富的色彩,JPEG非常适合用来存储照片,用来表达更生动的图像效果,比如颜色渐变。JPEG的算法比较复杂, 如果有兴趣可以参考该文章 其大概分为三步:把数据分为“重要部分”和“不重要部分”滤掉不重要的部分保存PNG便携式网络图形(英语:Portable Network Graphics,缩写:PNG)是一种无损压缩的位图图形格式,支持索引、灰度、RGB三种颜色方案以及Alpha通道等特性。PNG的开发目标是改善并取代GIF作为适合网络传输的格式而不需专利许可,所以被广泛应用于互联网及其他方面上。PNG-8PNG-8是无损的、使用索引色的、点阵图。PNG是一种比较新的图片格式,PNG-8是非常好的GIF格式替代者,在可能的情况下,应该尽可能的使用PNG-8而不是GIF,因为在相同的图片效果下,PNG-8具有更小的文件体积。除此之外,PNG-8还支持透明度的调节,而GIF并不支持。 现在,除非需要动画的支持,否则我们没有理由使用GIF而不是PNG-8。当然了,PNG-8本身也是支持动画的,只是浏览器支持得不好,不像GIF那样受到广泛的支持。PNG-24PNG-24是PNG的直接色版本。PNG-24是无损的、使用直接色的、点阵图。无损的、使用直接色的点阵图,听起来非常像BMP,是的,从显示效果上来看,PNG-24跟BMP没有不同。PNG-24的优点在于,它压缩了图片的数据,使得同样效果的图片,PNG-24格式的文件大小要比BMP小得多。当然,PNG24的图片还是要比JPEG、GIF、PNG-8大得多。虽然PNG-24的一个很大的目标,是替换JPEG的使用。但一般而言,PNG-24的文件大小是JPEG的五倍之多,而显示效果则通常只能获得一点点提升。所以,只有在你不在乎图片的文件体积,而想要最好的显示效果时,才应该使用PNG-24格式。另外,PNG-24跟PNG-8一样,是支持图片透明度的。PNG-32PNG32也是PNG的直接色版本。其表现与PNG-24差不多。三者的区别在于:PNG-32每个像素的深度为32bits,其中RGBA四个通道各占8bits。所谓的RGBA四个通道,就是红,绿,蓝,透明 这四种色值各自的大小,都用8bits来表示(0~255)。PNG-24的像素深度为24bits,其中RGB三个通道各占8bits。PNG-8则是使用8位的索引色。SVGSVG是很多种矢量图中的一种,它的特点是使用XML来描述图片。借助于前几年XML技术的流行,SVG也流行了很多。使用XML的优点是,任何时候你都可以把它当做一个文本文件来对待,也就是说,你可以非常方便的修改SVG图片,你所需要的只需要一个文本编辑器。如果你是一个前端开发,那你应该对其了解比较多。图片比较与场景应用类型优点缺点应用场景BMP无损压缩,图质最好,支持索引色和直接色文件过大目前仅存于WINDOWS系统GIF无损压缩,支持动画及透明仅支持256种颜色,画质差需要动画的需求JPEG文件小有损压缩,画质损失不考虑过好画质且需响应速度较快, 如大背景图PNG-8无损压缩, 支持透明画质中等应用于大多数中小图且要求画质比较好的需求SVG支持放大缩小而不影响画质编写麻烦,性能差多应用于ICON之类一图胜前言待补充总结了解各种图片的格式,有助于我们与设计同事的沟通,与大家共勉。引用 & 参考图片格式那么多,哪种更适合你?

March 14, 2019 · 1 min · jiezi

Stencil Test的应用总结

前言一直以来,对Stencil的Operation知其然而不知其所以然,不太明白提供这些Operation更新Stencil有什么用。而GPU的Stencil更新机制其实是根据应用的需求才这么设计的,理解好Stencil的应用情况,才能理解好Stencil Test的更新机制。因此,本文将对其主要的应用做下梳理,增强对Stencil Test的认知。1. Stencil Test简介在OpenGL/Direct3D的流水线中,Stencil Test被归入Pixel Shader之后的Output Merger Stage,其处理单位是像素(如果MSAA打开,则是Sample)。Stencil Test的有两个要点:Stencil值的测试,用于剔除像素Stencil值的更新,用于产生实现特定效果的Stencil值Stencil值的测试很简单——从Stencil Buffer 里读出该像素的Stencil值(8bit的UINT)与参考值比较,满足比较条件则pass最终画出(假设能通过Depth Test或其他剔除),否则fail直接剔除。比较函数以及参考值都是通过API设定,例如OpenGL的glStencilFunc(GLenum func, GLint ref, GLuint mask)函数。与Depth Test的比较函数类似,Stencil Test的比较函数包括NEVER, LESS, LEQUAL, GREATER, GEQUAL, EQUAL, NOTEQUAL和ALWAYS。 通过Stencil值的测试我们可以限制渲染的区域,比如下面的例子把渲染区域限制为Stencil值等于1的区域。 图1 给定中间图片中的Stencil值,将比较条件设为EQUAL,参考值设为1时,左侧图片的color通过Stencil Test后。 我们看到,只要Stencil Buffer里存储了期望的Stencil值,我们就可以通过Stencil Test剔除像素来画出期望的区域,正如Stencil本身的含义(模板)。而事实上问题重点常在于如何构造出期望的Stencil值,除了少数应用使用特定已知的模板外,大部分是在渲染过程中产生需要的模板,这就是要讲的第二个要点——Stencil值的更新,它是实现各种效果的关键。在OpenGL中,写Stencil Buffer的开启与否是通过函数glStencilMask(GLuint mask)设置的,这个函数的参数mask对应Stencil值的各个bit是否允许写入,当mask设为0表示完全关闭写Stencil Buffer。在开启写Stencil Buffer的情况下,无论像素是否被Stencil Test或Depth Test剔除,GPU都会执行Stencil值的更新。更新方式是跟Stencil Test和Depth Test的测试结果紧密联系的,OpenGL/D3D把测试结果分为三种情况:sfail: Stencil Test faildpfail: Stencil Test pass但Depth Test faildppass: Stencil Test pass且Depth Test pass通过API glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)可以分别为这三种测试结果指定更新该像素Stencil数值的方式,可选的方式包括ActionDescriptionGL_KEEP当前的Stencil值保持不变GL_ZERO将Stencil值更新为0.GL_REPLACE将Stencil值替换为参考值GL_INCR若当前Stencil值小于最大值,则加1GL_INCR_WRAPStencil值加1,若超过最大值则wrap为0GL_DECR若当前Stencil值大于最小值,则减1GL_DECR_WRAPStencil值减1,若小于0则wrap为最大值GL_INVERT按位反转当前Stencil值GPU在执行Stencil Test和Depth Test(没有Enable Depth Test的话将一直pass),按照测试结果(sfail,dpfail,dppass)对应的方式算出新的Stencil值,如有发生变化则写回Stencil Buffer里。 正是有上面的多种更新方式,以及Depth Test和Stencil Test的紧密联系使得Stencil Test能通过多个pass实现多种效果。2. Stencil Test的应用从上面可以看出,Stencil应用的过程大概是这样:开启写Stencil Buffer渲染物体,更新Stencil Buffer的内容关闭写Stencil Buffer渲染(其他)物体,通过Stencil Buffer的内容把部分像素剔除掉。我们看下不同的更新机制如何实现特定需求的。2.1 轮廓给物体添加轮廓的思路很简单——把同一个物体画两遍,其中第一遍正常地渲染物体,第二遍将原物体做微小拉伸(比原来多出轮廓),并让Pixel Shader输出轮廓颜色。同时要使第一遍所画的像素位置上在第二遍渲染中不会再被画出新的像素,即需要使用一种剔除方法,使第二次渲染时只保留两次渲染物体的非重叠部分。一开始我们可能会想到用Depth Test——第一次渲染时打开Depth Write,在第二遍渲染时在Vertex Shader给构成网格的每个顶点设一个足够大的深度值,这样第二次渲染时重叠部分会在GPU的Depth Test中因为遮挡而被剔除。然而,当场景里存在其他背景物体时,轮廓也会被遮挡住。因此,Depth Test并不是过滤像素区域的好方法,而这样的需求场景,本来就是Stencil Test的舞台。利用Stencil Test画轮廓的大概步骤是这样的:1)将sfail, dfail, dpass的更新方式分别设为KEEP,KEEP,REPLACE glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);2)关闭写Stencil Buffer,按正常方式渲染背景。 glStencilMask(0x00); //draw the background …3)开启写Stencil Buffer,比较函数为ALWAYS,Stencil Test参考值设为1。渲染物体,这样渲染后物体每个像素的Stencil值将等于1 glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilMask(0xFF); //draw the object …4)关闭写Stencil Buffer,比较函数设为NOTEQUAL,关闭Depth Test。将物体做微小拉伸并渲染物体,Pixel Shader输出轮廓颜色 glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilMask(0x00); glDisable(GL_DEPTH_TEST); //draw the scaled object …图 2 轮廓渲染这个方法的思想很简单:第一次渲染物体后,最终所有画出的像素对应的Stencil值均为1,而第二次渲染时只画出Stencil值不等于1的轮廓,从而实现了期望的效果。图2是用learnopengl教程在Stencil这一章中画出的例子,个人觉得这个网站的教程很适合初学OpenGL,里面对第三方库怎样build和使用有详细的解释,并且从最基本的例子开始展开循序渐进,最重要的是每个例子都有代码可参考。2.2 Dissolve在Graphics或Video领域,Dissolve用于描述一种过渡效果——一张图片渐渐地褪去,在同时另一张图片替换原来的图片。Dissolve可使用Stencil Buffer实现,在一开始将Stencil Buffer清零,通过设置不同的比较函数,使第一张图片全部画出,而第二张图片全部不画。接着逐帧改变Stencil Buffer,逐渐增加1的个数,并以同样的方式画两张图片,直到最后Stencil Buffer全为1,只画出了第二张图片的所有像素。实现Dissolve的其中一帧的过程大概是1) 开启Stencil,并将stencil比较函数设为GL_NEVER,参考值设为1,将sfail的更新方式设为GL_REPLACE, glStencilFunc(GL_NEVER, 1, 1) glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP)2)通过画几何体或glDrawPixels函数往Stencil Buffer里写入特定的Dissolve样式,由于Stencil Test一直fail,所有这些像素不会被画出 3)关闭写Stencil Buffer,将比较函数设为GL_EQUAL,参考值设为0,并画第一张图片,这样只有模板上为0值的地方才画出这张图片的像素 glStencilFuncGL_EQUAL, 0, 1(GL_EQUAL, 0, 1). //draw the 1st image …4)改变比较的参考值为1,并画第二张图片 glStencilFuncGL_EQUAL, 1, 1(GL_EQUAL, 1, 1). //draw the 2nd image …2.3 Shadow Volume以上的更新机制比较简单,这里我们继续看一个相对较复杂的应用——Shadow Volume,Shadow Volume最早是Frank Crow于1977年提出的一种为3D场景添加阴影的算法,后来也有其他研究者独立地提出一些变种算法。The Theory of Stencil Shadow Volumes给出了Shadow Volume的详细介绍。Shadow Volume算法旨在光栅化的渲染中,确认出所渲染物体上那些受遮挡影响未能被光源照到像素,生成一个模板,然后剔除对应的像素不做lighting,从而实现阴影效果。该算法的第一步是构造一个Shadow Volume(这里不是指算法名字了,而是一个图3那样的Volume),其基本步骤是以光源为视点,找出遮挡物的所有轮廓边(那些同时被正面三角形和反面三角形包含的边)将轮廓边上的每一点向光源与其连线的方向延伸,所有边构成的多边形形成一个立体(即Shadow Volume,图3的阴影部分)的四周表面。另外可能要加上Front Cap或Back Cop,从而形成封闭的Shadow Volume。加何种Cap因不同算法而异。图3 遮挡物在光源的延伸方向上形成的Shadow Volume在构造Shadow Volume完成后,渲染过程大概如下:按无光照渲染整个场景,即所有物体都出于阴影中对于每个光源,执行以下步骤:渲染构造好的Volume,利用深度信息构造出一个模板,使出于光照中的像素在模板上有不同的Stencil值按有光照渲染整个场景,利用步骤1构造的模板区分阴影区域,使用额外的Blending把渲染结果添加到已有场景中按照构造模板方法分类,Shadow Volume算法可分为两类Depth passDepth failDepth pass和Depth fail分别在dppass和dpfail两种测试结果更新Stencil值。Wiki里还提到Exclusive-or的方法,这种方法也是在Depth pass时更新Stencil值,但它只采用了1bit的Stencil值,更新方式为INVERT,因此并不适用于有多个Shadow Volume重叠的情况。下面着重看戏这两种方法对于Stencil Buffer的使用,对两者的优缺点暂不做讨论。2.3.1 Depth passDepth pass的思路是分两次分别渲染Shadow Volume的正面和反面,并用Stencil值记录位于物体前方的次数。如果正面和反面的次数相等,那么该位置出于光照中。如果正面的次数比反面多,那么该位置出于阴影中。因为Stencil值是在通过depth测试时更新的,所以这种方法较Depth pass。Depth pass构造应用模板的步骤为:关闭写Depth Buffer和Color Buffer,设置back-face culling,将dppass的更新方式设为GL_INCR.渲染Shadow Volume,由于Culling,只画了Shadow Volume的正面.设置front-face culling,将dppass的更新方式设为GL_DECR渲染Shadow Volume,由于Culling.只画了Shadow Volume的反面图4 Depth pass Shadow volume如图4,箭头末端的数字分别对应每个位置经过以上步骤后最终在Stencil Buffer里的数值,可以看到,出于阴影中的位置最终为1,因为它出于Shadow Volume的正面和反面之间,正面未被物体遮住depth pass之后Stencil值增1,而反面被物体遮住depth测试失败未能将Stencil值减1。当一个位置与眼睛的连线未闯过Shadow Volume(从左到右的第1条连线)或者穿过正反面(第2和第4条连线),那么意味着该位置在光照中。2.3.2 Depth fail另一种方法Depth fail通过在dpfail时更新Stencil值来构造模板,Depth fail的步骤为:关闭写Depth Buffer和Color Buffer,设置front-face culling,将dpfail的更新方式设为GL_INCR.渲染Shadow Volume,由于Culling,只画了Shadow Volume的正面.设置back-face culling,将dpfail的更新方式设为GL_DECR渲染Shadow Volume,由于Culling.只画了Shadow Volume的反面Depth fail其实是depth pass的一个“翻转版本”——depth pass算出正面和反面在物体前方的次数,而depth fail则算反面和正面在物体后方的次数。这种差异导致了两者在实际应用中有各自的优势和不足,这些超出本文范围,就不深入了。这里是一个提供代码的depth pass例子:Shadow Volume。2.3.3 Two-Sided Stencil以上Shadow Volume的正反面是分两次渲染的,这无疑增加了Vertex Shader的带宽。事实上可以利用Two-Sided Stencil功能,对于OpenGL可通过下面两个函数分别为Front和Back设置不同的更新方式,那么整个Shadow Volume实际上只需要画一次,同时画正面和背面,由GPU根据三角形的Face去选择更新Stencil值的方式。 void glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask); void glStencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass);2.3.4 总结Shadow Volume算法是将Stencil Buffer的数值当做计数器来使用,用于统计物体每个位置的正面和反面的数量,以之判断物体与Shadow Volume的关系。本质上,Stencil Buffer使用来记录物体与Shadow Volume两个面的遮挡关系,这也解释了Stencil值的更新为什么要跟Depth Test的结果绑定在一起。2.4 其他除了上述提到的应用外,Wiki中提到的Stencil Test其他应用还有Decaling,portal rendering,Reflections,intersection highlighting等,留待慢慢消化。

March 9, 2019 · 2 min · jiezi