Chapter 15. Blueprint Rendering and "Sketchy Drawings"developer.nvidia.com
Steps in Creating an Edge Map
The Z-Buffer, Normal Buffer, and Edge Map for Each Layer
Applying Uncertainty in Image Space
Different Styles of Sketchy Rendering
介绍
本章介绍两种硬件加速的图像空间下的NPR(non-photorealistic rendering)技术:蓝图渲染(blueprint rendering)和草图绘制(sketchy drawing)。蓝图渲染勾勒出3D物体视觉上重要的可见和不可见的边缘,由轮廓表现半透明的渲染特性,由此使得复杂的物体空间结构易于理解。草图绘制用于传达视觉概念,粗略表达3D物体的重要边和内部颜色面片,使其像是潦草的绘画。
基本原理
蓝图渲染和草图绘制都基于三个基本原理:保存场景几何中间渲染的图像;实现边缘增强技术;应用深度子图像渲染。
- 中间渲染结果(Intermediate Rendering Results)
从3D几何体获取2D数据并渲染进纹理称为中间渲染结果,为此需要使用G-buffer来存储几何属性,此处要用到法线缓冲和z缓冲来生成边缘。在多pass渲染中,一旦生成了中间渲染结果就可以对它们重复使用。
- 边缘增强(Edge Enhancement)
为了逐物体提取视觉上重要的边,我们使用一种图像空间的边缘增强技术(Nienhaus and Döllner 2003)。视觉上重要的边包括轮廓边、边界和折痕边。通过对相邻像素采样来检测法线缓存和z缓存的不连续性,从而获得这些边。检测不连续性产生的强度值渲染进一张纹理,称为边缘贴图(edge map)。
- 深度子图像渲染(Depth Sprite Rendering)
深度子图像是在每个像素上为深度测试提供额外z信息的2D图像,用来正确地解决图像空间渲染的可见性问题。实现步骤如下:
- 渲染一张和屏幕对齐的方格,纹理填充为包含z值的高精度纹理
- 使用来自纹理的z值取代原本光栅化生成的片元z值,同时丢弃掉z值为1的片元来优化填充率
- 继续用深度测试来进行渲染,如果片元通过测试那么帧缓冲就会被来自深度子图像的颜色和z值填充。
蓝图渲染
对于蓝图渲染,需要提取可见边和不可见边。可见边是可以直接被摄像机看见的边,不可见边是被几何面片遮挡的边。由此需要结合深度剥离(depth peeling)技术和边缘增强技术。
- 深度剥离
深度剥离是按深度排序的逐片元提取2D图层的技术。一般的深度测试只能检测每个像素的最小z值,而无法决定第二层、第三层深度,因此需要额外的深度检测来提取剩余n层。提取n层需要n个pass。将每个唯一深度复杂度的图层称为深度层(depth layer),该层的z缓冲生成的高精度纹理称为深度层贴图(depth layer map),对应的颜色缓冲生成的纹理则称为颜色层贴图(color layer map)。
在第一个渲染pass中执行原来的深度测试,把z缓冲和颜色缓冲捕获到一个深度层图和颜色层图中。后续的渲染pass中(i>0),执行额外的深度测试,以上一个pass的深度层图作为纹理确定对应坐标在上一个pass的z值,如果当前的z值比它大则继续执行深度测试来获得次级的深度,处理结束后生成新的深度层图和颜色层图。此外可以利用遮挡查询来高效地实现终止条件。伪代码如下:
procedure depthPeeling(G <- 3DGeometry) begin
i=0
do
F <- rasterize(G)
/* Perform single depth test in the first rendering pass */
if(i==0) begin
for all fragment ∈ F begin
bool test <- performDepthTest(fragment)
if(test) begin
fragment.depth -> z-buffer
fragment.color -> color buffer
end
else reject fragment
end
end
else begin
/* Perform two depth test */
for all fragment ∈ F begin
if(fragment.depth > fragment.valuedepthLayerMap(i-1)) begin
/* First test */
bool test <- performDepthTest(fragment)
if(test) begin
/* Second test */
fragment.depth -> z-buffer
fragment.color -> color buffer
end
else reject fragment
end
else reject fragment
end
end
depthLayerMap(i) <- capture(z-buffer)
colorLayerMap(i) <- capture(color buffer)
/* Edge intensities */
edgeMap(i) <- edges(depthLayerMap(i),colorLayerMap(i))
i++
while(occlusionQuery() != 0 ) /* Termination condition */
end
- 提取可见边和不可见边
为每个深度层构建边缘贴图来补充深度剥离技术。法线缓冲和z缓冲的不连续性组成了可见边,此外由于深度剥离,所以不可见的边也会按可见边处理。上面的组图已经展示了深度剥离后的法线缓冲、z缓冲和生成的边缘贴图。
需要注意的是可能会出现在不同深度层中边缘重复计算的情况,比如遮挡物和被遮挡物体有共享边的情况以及某物体被部分遮挡时剥离第一层后第二层仍然不连续的情况。
- 合成蓝图
按深度顺序来把边缘贴图合成为蓝图。使用一张和屏幕对齐的方格作为渲染输出,将深度层图作为输入实现深度子图像,使用从边缘贴图获取的边缘强度值来计算片元的RGB值,再根据边缘强度设置片元的alpha值。
- 使用深度蒙版(depth masking)来优化
深度蒙版可以用来改变终止条件来优化渲染pass数,即给定某个阈值,当深度层图中通过深度测试的片元数目低于该阈值时则终止计算,否则继续深度剥离。
procedure depthPeeling(G <- 3DGeometry, C <- geometryOfOccludedComponent) begin
depthMask <- depthTexture(C)
quad <- createTexturedScreenAlignedQuad(depthMask)
renderDepthSprite(quad) /* Render quad as depth sprite */
int Q = occlusionQuery() /* Number of fragments of the components */
. . .
do
. . .
renderDepthSprite(quad)
int R = occlusionQuery() /* Number of visible fragments of the component */
while(R<fraction(Q)) /* Termination condition */
end
草图绘制
草图绘制使用不确定性来将重要边和表面颜色绘制成不均匀的草图。就像蓝图渲染一样,首先生成表示边和颜色的中间渲染结果,再在图像空间中应用不确定值来非均匀地绘制纹理,最后以同样方式调整深度信息使得草图绘制可以和3D场景融合在一起。
- 边缘和色块
使用边缘增强技术来将3D几何的边缘存储为边缘贴图,同时用3D几何的表面颜色来生成覆盖表面细节的色块,从而达到卡通效果,将色块的纹理称为着色贴图(shade map) - 应用不确定性
使用边缘贴图和着色贴图来填充和屏幕对其的方格,并需要额外一张存储了不确定值的纹理。为了草图边缘的连续性和帧之间的连贯,本文中使用Perlin噪声贴图使得相邻的不确定值有相关性。噪声贴图作为边缘贴图和着色贴图的偏移纹理,即使用噪声值扰动片元的纹理坐标。此外引入不确定度来控制扰动的程度,不确定度是自定义的2x2矩阵,乘以噪声贴图的不确定值来统一权重得到最终偏移。
为了更好的效果,需要分别不同地扰动边缘贴图和着色贴图的纹理坐标,为它们分别应用不同地不确定度矩阵即可。最后将扰动后的边缘贴图和着色贴图合成到一起。
- 调整深度
此时,该方法会出现严重的缺点是z值在某些情况下无法和片元对应。于是需要根据之前的扰动来生成屏幕对齐的高精度的深度子图像,分别用边缘贴图的扰动和着色贴图的扰动对原本的深度贴图进行扰动并取最小值来作为最终的z值,由本节最开始的图可看出。
以下介绍草图绘制的两个变种:
- 粗糙轮廓和颜色变化:之前的方法就像在平滑表面的铅笔绘制,可以粗糙化轮廓和颜色来模拟不同的绘画工具及媒介比如粗糙表面上的粉笔画,为此可以随机选择噪声值来减少相邻片元的不确定性的相关性,产生了模糊和磨损的边缘和颜色变化,如上图(b),粗糙度和粒度的变化仿佛是粉笔画中压力的变化造成的。
- 重复边缘:手绘的一个特点是重复的绘制边缘来起草布局,可以通过重复绘制重要边来模拟该特点。为此,对边缘贴图多次使用不同的不确定度(和不同的边缘颜色),结果如上图(c)。当然也要用它们对应的不确定度来多次处理深度贴图。
此方法的另外一个问题是,不确定度在图像空间保持不变且与原始几何属性无明显关联,会导致雨窗效应(shower-door effect),即动画时绘制的草图像是会游泳,我们像是隔着一道布满水珠的透明玻璃观看被玻璃扭曲的景色。为了解决该问题,需要完成如下手段:
- 保存几何属性来控制不确定度:将几何属性如位置、法线等存进G-buffer,在计算不确定度时将几何属性作为参数加入计算,即f (s, t) = p(s, t, g(s, t))。
- 扩大几何体:通过在模型空间烟顶点法线移动顶点来扩大几何体,使得图像空间中靠近几何体的位置也有几何信息,从而扰动生成的外部区域也可以用几何信息来计算不确定度。
整体流程如下图: