本文由@浅墨_毛星云 出品,首发于知乎专栏,转载请注明出处  

        文章链接: https://zhuanlan.zhihu.com/p/47866903

 

本文是【GPU精粹与Shader编程】系列的第八篇文章,全文共两万余字。文章盘点、提炼和总结了《GPU Pro 1》全书总计22章的核心内容。

题图来自《荒野大镖客2》。

 

 

全文内容目录

本文将对《GPU Pro 1》全书中游戏开发与渲染相关,相对更具含金量的5个部分,共22章的内容进行提炼与总结,详细列举如下:

  • Part I. 游戏渲染技术剖析 Game Postmortems
    • 一、《孢子(Spore)》中的风格化渲染 | Stylized Rendering in Spore
    • 二、《狂野西部:生死同盟》中的渲染技术 | Rendering Techniques in Call of Juarez: Bound in Blood
    • 三、《正当防卫2》中的大世界制作经验与教训 | Making it Large, Beautiful, Fast, and Consistent: Lessons Learned
    • 四、《矿工战争》中的可破坏体积地形 | Destructible Volumetric Terrain
  • Part II. 渲染技术 Rendering Techniques
    • 五、基于高度混合的四叉树位移贴图 | Quadtree Displacement Mapping with Height Blending
    • 六、使用几何着色器的NPR效果 | NPR Effects Using the Geometry Shader
    • 七、后处理Alpha混合 | Alpha Blending as a Post-Process
    • 八、虚拟纹理映射简介 | Virtual Texture Mapping 101
  • Part III. 全局光照 Global Illumination
    • 九、基于间接光照快速,基于模板的多分辨率泼溅 Fast, Stencil-Based Multiresolution Splatting for Indirect Illumination
    • 十、屏幕空间定向环境光遮蔽 Screen-Space Directional Occlusion (SSDO)
    • 十一、基于几何替代物技术的实时多级光线追踪 | Real-Time Multi-Bounce Ray-Tracing with Geometry Impostors
  • Part IV. 图像空间 Image Space
    • 十二、GPU上各项异性的Kuwahara滤波 | Anisotropic Kuwahara Filtering on the GPU
    • 十三、基于后处理的边缘抗锯齿 | Edge Anti-aliasing by Post-Processing
    • 十四、基于Floyd-Steinberg 半色调的环境映射 | Environment Mapping with Floyd-Steinberg Halftoning
    • 十五、用于粒状遮挡剔除的分层项缓冲 | Hierarchical Item Buffers for Granular Occlusion Culling
    • 十六、后期制作中的真实景深 | Realistic Depth of Field in Postproduction
    • 十七、实时屏幕空间的云层光照 | Real-Time Screen Space Cloud Lighting
    • 十八、屏幕空间次表面散射 | Screen-Space Subsurface Scattering
  • Part V. 阴影 Shadows
    • 十九、快速传统阴影滤波 | Fast Conventional Shadow Filtering
    • 二十、混合最小/最大基于平面的阴影贴图 | Hybrid Min/Max Plane-Based Shadow Maps
    • 二十一、基于四面体映射实现全向光阴影映射 | Shadow Mapping for Omnidirectional Light Using Tetrahedron Mapping
    • 二十二、屏幕空间软阴影 | Screen Space Soft Shadows

 

《GPU Pro 1》其书

《GPU Pro 1》全称为《GPU Pro : Advanced Rendering Techniques》,其作为GPU Pro系列的开山之作,出版于2010年,汇聚了当代业界前沿的图形学技术。全书共10个部分,41章。

一个有趣的细节是,《GPU Pro 1》是GPU Pro系列7本书中,页数最多的一本,共712页。

图 《GPU Pro 1》封面

 

《GPU Pro 1》书本配套源代码

类似之前的GPU Gems系列的源码收藏GitHub repo(https://github.com/QianMo/GPU-Gems-Book-Source-Code),我也维护了的一个名为“GPU-Pro-Books-Source-Code”的GitHub仓库,以备份GPU Pro系列珍贵的资源,也方便直接在GitHub Web端查看业界大牛们写的代码,链接如下:

https://github.com/QianMo/GPU-Pro-Books-Source-Code

 

Part I. 游戏渲染技术剖析 Game Postmortems

 

一、《孢子(Spore)》中的风格化渲染 | Stylized Rendering in Spore

《孢子(Spore)》是一款非常有创意的游戏。在游戏《孢子(Spore)》中,使用了可编程过滤链系统(scriptable filter chain system)在运行时对帧进行处理,以实现游戏整体独特的风格化渲染。(注,在本文中,filter按语境,译为滤波或者过滤)。

图 《孢子》封面图

图 《孢子》中的风格化渲染

过滤器链(filter chain)可以看作一系列按顺序应用的参数化的图像处理(image processing)着色器,即后处理链。《孢子》中的每一帧都使用此系统进行处理和合成。 除了《孢子》标准的艺术导向的视觉风格外,开发人员还创建了一组特有的滤波器,为游戏产生截然不同的视觉风格。而在这章中,作者讲到了一些在开发《孢子》时生成的视觉样式,并分享了关于《孢子》过滤器链系统的设计和实现的细节。

诸如模糊(blur),边缘检测(edge detection)等图像处理技术的GPU实现在图像处理领域较为常见。《孢子》的开发目标是构建一个具有此类过滤器的调色系统,美术师可以利用这些过滤器来创作不同的视觉样式。 下图显示了该系统在渲染管线中如何进行放置。

图 过滤器链系统总览

图 《孢子》中以油画方式进行渲染的飞机

下图显示了《孢子》中细胞阶段的过滤器链如何使用由渲染管线的其他阶段生成的多个输入纹理,并形成最终的合成帧。

图 《孢子》中细胞阶段游戏流体环境的复杂过滤器链

 

1.1 后处理过滤链系统的实现要点

过滤链系统实现的方面,分为两个要点:

  • 动态参数(Dynamic parameters)

  • 自定义过滤器(Custom filters)

 

1.1.1 动态参数(Dynamic parameters)

《孢子》中的动态环境需要调用按帧变化参数。所以,游戏中添加了可以通过任何过滤器访问的每帧更新的全局参数。例如,使用相机高度和当日时间作为行星大气过滤器的变化参数,如下图。

而在其他情况下,游戏需要在给定过滤器的两组不同参数值之间平滑插值。例如,每当天气系统开始下雨时,全局着色过滤器的颜色就会转换为阴天的灰色。在系统中也添加了支持游戏控制插值的参数,也添加了可以平滑改变滤波器强度的衰减器(fader)。

图 按当日时间驱动的颜色过滤器。这种经过彩色压缩的输出会进行模糊并以bloom的方式添加到场景中

 

1.1.2 自定义过滤器(Custom filters)

过滤链系统的一个重要补充是自定义过滤器,可以将其着色器指定为参数。这意味着程序员可以通过向现有构建添加新着色器来轻松添加新的图像技术。此外,程序员可以通过将多个过滤器折叠到一个实现相同视觉效果的自定义过滤器中来优化艺术家生成的过滤器链。

 

1.2 五种屏幕后处理Shader的实现思路

接着,介绍五种《孢子》中比较有意思的后处理效果。

1.2.1 油画后处理效果 Oil Paint Filter

对于油画过滤器(Oil Paint Filter),首先渲染画笔描边的法线贴图,用于对传入的场景进行扭曲。 然后使用相同的法线贴图结合三个光源照亮图像空间中的笔触(Brush stroke)。 而笔触可以通过带状的粒子特效驱动,使过滤效果变得动态,并且在时间上更加连贯。

图 《孢子》中的油画后处理效果

用于油画效果的像素着色器核心代码如下:

# Oil Paint Effect
# kDistortionScale 0.01, kBrighten 2.0
# kNormalScales (1.5, 2.5, 1.6)
# Get the normal map and bring normals into [-1,1] range
half4 pNormalMap = tex2D ( normalMap , fragIn .uv0 );
half3 nMapNormal = 2 * pNormalMap .rgb - half3( 1, 1, 1 );# Distort the UVs using normals ( Dependent Texture Read!)
half4 pIn = tex2D (sceneTex ,
saturate (uv - nMapNormal .xy * kDistortionScale) );# Generate the image space lit scene
half3 fakeTangN = nMapNormal .rbg * kNormalScales;
fakeTangN = normalize (fakeTangN );# Do this for 3 lights and sum , choose different directions
# and colors for the lights
half NDotL = saturate (dot (kLightDir , fakeTangN ));
half3 normalMappingComponent = NDotL * kLightColor ;# Combine distorted scene with lit scene
OUT .color .rgb = pIn .rgb * normalMappingComponent * kBrighten ;

 

1.2.2 水彩画后处理效果 Watercolor Filter

对于水彩画过滤器(watercolor filter)。首先,使用传入场景的简易Sobel边缘检测版本与原始场景相乘。 然后使用平滑滤波器(smoothing filter)的四个pass对结果进行平滑,且该平滑滤波器从四周的taps中找到每个pass的最亮值。 接着,基于边缘检测的轮廓添加一些在平滑过程中丢失的精确度。 具体核心代码如下,而offset和scales是可调的参数,允许我们改变绘制涂抹笔触的大小。

图 《孢子》中的水彩后处理效果

《孢子》中的水彩后处理效果像素着色器代码如下:

# Water Color Smoothing
# kScaleX = 0.5, kScaleY = 0.5
# offsetX1 = 1.5 * kScaleX offsetY1 = 1.5 * kScaleX
# offsetX2 = 0.5 * kScaleX offsetY2 = 0.5 * kScaleY# Get the taps
tap0 = tex2D (sceneTex , uv + float2 (-offsetX1 ,-offsetY1 ));
tap1 = tex2D (sceneTex , uv + float2 (-offsetX2 ,-offsetY2 ));
tap2 = tex2D (sceneTex , uv + float2 (offsetX2 , offsetY2 ));
tap3 = tex2D (sceneTex , uv + float2 (offsetX1 , offsetY1 ));# Find highest value for each channel from all four taps
ret0 = step(tap1 , tap0 );
ret1 = step(tap3 , tap2 );
tapwin1 = tap0* ret0 + tap1 * (1.0 - ret0);
tapwin2 = tap2* ret1 + tap3 * (1.0 - ret1);
ret = step(tapwin2 , tapwin1 );
OUT .color .rgb = tapwin1 * ret + (1.0 -ret) * tapwin2 ;

 

1.2.3 8位后处理效果 8-Bit Filter

图 8-Bit Filter

要创建一个8位滤波器(8-Bit Filter),可以使用像素着色器中的round函数,并通过点采样绘制到游戏分辨率大小1/4的低分辨率缓冲区中。 这是一个非常简单的效果,使游戏看起来像一个旧式8位游戏。

《孢子》中8-bit后处理效果的像素着色器代码如下:

# 8 Bit Filter
# kNumBits : values between 8 and 20 look good
half4 source = tex2D (sourceTex , fragIn .uv0 );
OUT .color .rgb = round (source .rgb * kNumBits) / kNumBits ;

1.2.4 黑色电影后处理效果 Film Noir Filter

在创建黑色电影后处理效果时,首先将传入的场景转换为黑白。 然后进行缩放和偏移。添加一些噪声,雨水颗粒效果是很好的画龙点睛。

图 《孢子》中黑色电影后处理效果

《孢子》中黑色电影后处理像素着色器代码如下,其中,kNoiseTile可用于调整粒度,而kBias和kScale用作线性对比度拉伸的参数:

# Film Noir filter
# kNoiseTile is 4.0
# kBias is 0.15, kScale is 1.5
# kNoiseScale is 0.12
pIn = tex2D (sourceTex , uv);
pNoise = tex2D (noiseTex , uv * kNoiseTile) ;# Standard desaturation
converter = half3 (0.23 , 0.66, 0.11);
bwColor = dot (pIn .rgb , converter );# Scale and bias
stretched = saturate (bwColor - kBias) * kScale ;# Add
OUT .color .rgb = stretched + pNoise * kNoiseScale ;

1.2.5 旧电影后处理效果 Old Film Filter

对于旧电影后处理效果,可以采用简单的棕褐色着色与锐化滤波器(sharpen filter)相结合。 且可以使用粒子效果进行划痕和渐晕的处理。

图 旧电影后处理效果

《孢子》中旧电影后处理效果像素着色器代码如下:

# Old Film Filter
# offsetX and offsetY are 2 pixels . With such wide taps , we
# get that weird sharpness that old photos have.
# kNoiseTile is 5.0, kNoiseScale is 0.18
# kSepiaRGB is (0.8, 0.5, 0.3)
# Get the scene and noise textures
float4 sourceColor = tex2D (sourceTex , uv);
float4 noiseColor = tex2D (noiseTex , uv * kNoiseTile );# sharpen filter
tap0 = tex2D (sceneTex , uv + float2 (0, -offsetY ));
tap1 = tex2D (sceneTex , uv + float2 (0, offsetY ));
tap2 = tex2D (sceneTex , uv + float2 (-offsetX , 0));
tap3 = tex2D (sceneTex , uv + float2 (offsetX , 0));
sourceColor = 5 * sourceColor - (tap0 + tap1 + tap2 + tap3 );# Sepia colorize
float4 converter = float4 (0.23 , 0.66, 0.11, 0);
float bwColor = dot (sourceColor , converter );
float3 sepia = kSepiaRGB * bwColor ;# Add noise
OUT .color = sepia * kTintScale + noiseColor * kNoiseScale ;

关于《孢子》更多的风格化渲染的教程,可以在这里找到:

http://www.spore.com/comm/tutorials

 

二、《狂野西部:生死同盟》中的渲染技术 | Rendering Techniques in Call of Juarez: Bound in Blood

 

《狂野西部:生死同盟》(Call of Juarez: Bound in Blood)是由Techland公司开发,育碧发行,并于2009年夏季在PS3,Xbox360和PC上发布的游戏。

图《狂野西部:生死同盟》封面

图《GPU Pro 1》的封面,即是采用的《狂野西部:生死同盟》的图片

图 《狂野西部:生死同盟》游戏截图

《狂野西部:生死同盟》基于ChromeEngine 4,游戏中大量用到了延迟着色(deferred shading)技术。

众所周知,延迟着色 [Hargreaves 04]是一种在屏幕空间使用存储了诸如漫反射颜色,法向量或深度值等像素信息的中间缓冲区(G-buffer)的技术。

G-buffer是一组屏幕大小的渲染目标(MRT),可以使用现代图形硬件在单个pass中生成,可以显着降低渲染负载。然后使用G-buffer作为着色算法的输入(例如光照方程),而无需浏览原始几何体(此阶段计算所需的所有信息,如三维世界空间中的像素的位置,可以从G-buffer中提取)。以这种方式,算法仅对可见像素进行操作,这极大地降低了照明计算的复杂性。

表 《狂野西部:生死同盟》中的MRT配置

延迟着色方法的主要优点是对渲染管线的简化,节省复杂着色器资源和计算的开销,以及能对复杂光照(如动态光源)进行简约而健壮的管理。

延迟着色技术在与后处理渲染效果的结合方面可以获得不错的化学反应。在《狂野西部:生死同盟》中,延迟渲染与诸如屏幕空间环境光遮蔽(SSAO),运动模糊(motion-blur),色调映射(tone mapping)以及用于改善最终图像质量的边缘抗锯齿(edge anti-aliasing)等后处理效果都可以很好的结合使用。

图 拥有动态光源和环境光遮蔽的室内场景

这章中还展示了不少《狂野西部:生死同盟》中自然现象效果的渲染方法,如雨滴,体积地面雾,light shafts,真实感天空和云彩,水面渲染,降雨效果,以及体积光的渲染技巧。以及色调映射相关的技术。

图 场景色调映射,在阴影区域和光照区域之间转换

 

 

三、《正当防卫2》中的大世界制作经验与教训 | Making it Large, Beautiful, Fast,and Consistent: Lessons Learned

 

《正当防卫2(Just Cause 2)》是Avalanche Studios为PC,Xbox 360和PLAYSTATION 3开发的沙盒游戏。游戏的主要风格是大世界,主要视觉特征是具有巨大渲染范围的巨型景观,森林、城市、沙漠、丛林各种环境不同的气候,以及昼夜循环技术。

图 《正当防卫2》封面

对于多动态光源的渲染,《正当防卫2》没有使用延迟渲染,而是提出了一种称作光源索引(Light indexing)的方案,该方案可以使用前向渲染渲染大量动态光源,而无需多个pass,或增加draw calls。

 

3.1 光照索引 Light indexing

光照索引(Light indexing)技术的核心思路是:通过RGBA8格式128 x 128的索引纹理将光照信息提供给着色器。

将该纹理映射到摄像机位置周围的XZ平面中,并进行点采样。 每个纹素都映射在一个4m x 4m的区域,并持有四个该正方形相关的光源索引。这意味着我们覆盖了512m × 512m的区域,且动态光源处于活动状态。

活动光源存储在单独的列表中,可以是着色器常量,也可以是一维纹理,具体取决于平台。虽然使用8位通道可以索引多达256个光源,但我们将其限制为64个,以便将光源信息拟合到着色器常量中。每个光源都有两个恒定的寄存器,保存位置(position),倒数平方半径(reciprocal squared radius)和颜色(color)这三个参数。

表 光源常量

此外,还有一个额外的“禁用(disabled)”光源槽位,其所有这些都设置为零。那么总寄存器计数会达到130。当使用一维纹理时,禁用的光源用边框颜色(border color)编码替代。 位置和倒数平方半径以RGBA16F格式存储,颜色以RGBA8格式存储。为了保持精度,位置存储在相对于纹理中心的局部空间中。

光源索引纹理在CPU上由全局光源列表生成。一开始,其位置被放置在使得纹理区域被充分利用的位置,最终以尽可能小的空间,放置在摄像机之后。

在启用并落入索引纹理区域内的光源中,根据优先级,屏幕上的近似大小以及其他因素来选择最相关的光源。每个光源都插入其所覆盖的纹素的可用通道中。如果纹理像素被四个以上的光源覆盖,则需要丢弃此光源。

如果在插入时纹理像素已满,程序将根据图块中的最大衰减系数检查入射光源是否应替换任何现有的光源,以减少掉光源的视觉误差。这些误差可能导致图块边框周围的光照不连续性。通常这些误差很小,但当四处移动时可能非常明显。而为了避免这个问题,可以将索引纹理对齐到纹素大小的坐标中。在实践中,光源的丢弃非常少见,通常很难发现。

图 轴对齐世界空间中的光照索引。 放置纹理使得尽可能多的区域在视锥体内。 图示的4m x 4m区域由两个由R和G通道索引的光源相交。 未使用的插槽表示禁用的光源。

 

3.2 阴影系统 Shadowing System

阴影方面,《正当防卫2》中采用级联阴影映射(cascaded shadow mapping)。并对高性能PC提供软阴影(Soft shadows)选项。虽然在任何情况下都不是物理上的准确,但算法确实会产生真正的软阴影,而不仅仅是在许多游戏中使用的恒定半径模糊阴影。

图 《正当防卫2》中的软阴影。注意树底部的锐利阴影逐渐变得柔和,以及注意,树叶投下了非常柔和的阴影。

此软阴影算法的步骤如下:

1、在阴影贴图中搜索遮挡物的邻域。

2、投射阴影的样本计为遮挡物。

3、将遮挡物中的中心样本的平均深度差用作第二个pass中的采样半径,并且在该半径内取多个标准PCF样本并取平均值。

4、为了隐藏有限数量的样本失真,采样图案以从屏幕位置产生的伪随机角度进行旋转。

实现Shader代码如下:

// Setup rotation matrix
float3 rot0 = float3(rot.xy, shadow_coord.x);
float3 rot1 = float3(float2(-1, 1) * rot.yx, shadow_coord.y);
float z = shadow_coord.z * BlurFactor;// Find average occluder distances .
// Only shadowing samples are taken into account .
[unroll] for (int i = 0; i<SHADOW_SAMPLES; i++)
{coord.x = dot(rot0 , offsets[i]);coord.y = dot(rot1 , offsets[i]);float depth = ShadowMap.Sample(ShadowDepthFilter, coord).r;de.x = saturate(z - depth* BlurFactor);de.y = (de.x > 0.0);dd += de;
}// Compute blur radius
float radius = dd.x / dd.y + BlurBias;
rot0.xy *= radius ;
rot1.xy *= radius ;// Sample shadow with radius
[unroll] for (int k = 0; k<SHADOW_SAMPLES; k++)
{coord.x = dot(rot0 , offsets[k]);coord.y = dot(rot1 , offsets[k]);shadow += ShadowMap.SampleCmpLevelZero(ShadowComparisonFilter, coord, shadow_coord.z).r;
}

 

3.3 环境光遮蔽 Ambient Occlusion

对于环境遮挡(AO),使用了三种不同的技术:

  • 美术师生成的AO(artist-generated AO)
  • 遮挡体(Occlusion Volumes)
  • SSAO [Kajalin 09]

其中,美术师生成的环境光遮蔽用于静态模型,由材质属性纹理中的AO通道组成。此外,美术师有时会在关键点放置环境遮挡几何。对于动态对象,使用遮挡体(OcclusionVolumes)在底层几何体上投射遮挡阴影,主要是角色和车辆下的地面。而SSAO是PC版本的可选设置,里面使用了一种从深度缓冲导出切线空间的方案。

其中,SSAO从深度缓冲区导出切线空间的实现Shader代码如下:

// Center sample
float center = Depth . Sample ( Filter , In. TexCoord . xy ). r;// Horizontal and vertical neighbors
float  x0  =  Depth.Sample ( Filter , In. TexCoord .xy , int2 (-1 , 0)). r; 
float  x1  =  Depth.Sample ( Filter , In. TexCoord .xy , int2 ( 1 , 0)). r; 
float  y0  =  Depth.Sample ( Filter , In. TexCoord .xy , int2 ( 0 , 1)). r; 
float  y1  =  Depth.Sample ( Filter , In. TexCoord .xy , int2 ( 0 , -1)). r;// Sample another step as well for edge detection
float ex0 = Depth . Sample ( Filter , In. TexCoord , int2 (-2 , 0)). r; 
float ex1 = Depth . Sample ( Filter , In. TexCoord , int2 ( 2 , 0)). r; 
float ey0 = Depth . Sample ( Filter , In. TexCoord , int2 ( 0 , 2)). r; 
float ey1 = Depth . Sample ( Filter , In. TexCoord , int2 ( 0 , -2)). r;// Linear depths
float lin_depth = LinearizeDepth ( center , DepthParams . xy ); 
float lin_depth_x0  =  LinearizeDepth (x0 , DepthParams .xy ); 
float lin_depth_x1  =  LinearizeDepth (x1 , DepthParams . xy ); 
float lin_depth_y0  =  LinearizeDepth (y0 , DepthParams . xy ); 
float lin_depth_y1  =  LinearizeDepth (y1 , DepthParams . xy );//   Local   position   ( WorldPos   -   EyePosition ) float3 pos = In. Dir * lin_depth ;
float3 pos_x0 = In. DirX0 * lin_depth_x0 ; 
float3 pos_x1 = In. DirX1 * lin_depth_x1 ; 
float3 pos_y0 = In. DirY0 * lin_depth_y0 ; 
float3 pos_y1 = In. DirY1 * lin_depth_y1 ;//   Compute   depth   differences    in   screespace    X   and   Y float dx0 = 2.0 f * x0 - center - ex0 ;
float dx1 = 2.0 f * x1 - center - ex1 ; 
float dy0 = 2.0 f * y0 - center - ey0 ; 
float  dy1  =  2.0 f  *  y1  -  center  -  ey1 ;// Select the direction that has the straightest
// slope and compute the tangent vectors float3 tanX , tanY ;
if ( abs ( dx0 ) < abs ( dx1 )) tanX = pos - pos_x0 ;
elsetanX = pos_x1 - pos ;if ( abs ( dy0 ) < abs ( dy1 )) tanY = pos - pos_y0 ;
elsetanY = pos_y1 - pos ;tanX = normalize ( tanX ); tanY = normalize ( tanY );
float3 normal = normalize ( cross ( tanX , tanY ));

 

3.4 其他内容

这一章的其他内容包括:

  • 角色阴影(Character Shadows)
  • 软粒子(Soft Particles)
  • 抖动错误:处理浮点精度(The Jitter Bug: Dealing with Floating-Point Precision)
  • 着色器常量管理(Shader constant management)
  • 伽马校正和sRGB混合相关问题
  • 云层渲染优化(Cloud Rendering Optimization)
  • 粒子修剪(Particle Trimming)
  • 内存优化(Memory Optimizations)

由于篇幅所限,这些内容无法展开讲解。感兴趣的朋友,不妨可以找到原书对应部分进行阅读。

 

 

 

四、《矿工战争》中的可破坏体积地形 | Destructible Volumetric Terrain

 

这篇文章中,主要讲到了游戏《矿工战争(Miner Wars)》中基于体素(voxel)的可破坏体积地形技术。

《矿工战争(Miner Wars)》游戏的主要特征是多维度地形的即时破坏,并且引擎依赖预先计算的数据。 每个地形变化都会实时计算,消耗尽可能少的内存并且没有明显的延迟。

图 《矿工战争》游戏截图

在游戏的实现中,体素是具有以米为单位的实际尺寸的三维像素。 每个体素都保存有关其密度的信息 – 是否全空,是否全满,或介于空和满之间,而体素的材质类型用于贴图,破坏以及游戏逻辑中。

文中将体素贴图(voxel map)定义为一组体素(例如,256 x 512 x 256)的集合。每个体素贴图包含体素的数据单元(data cells),以及包含静态顶点缓冲区和三角形索引的渲染单元(render cells)。

《矿工战争》的引擎不会直接渲染体素,相反,是将体素多边形化,在渲染或检测碰撞之前将其转换为三角形。使用标准的行进立方体(Marching Cubes , MC)算法 [“Marching”09]进行多边形化。

图 一艘采矿船用炸药进行隧道的挖掘

图 具有表示体素边界的虚线的体素图。 此图描绘了4 x 4个体素;

图中的小十字代表体素内的网格点; 实线代表三维模型。

 

 

 

 

Part II. 渲染技术 Rendering Techniques

 

 

五、基于高度混合的四叉树位移贴图 | Quadtree Displacement Mapping with Height Blending

 

 

这章中,介绍了当前表面渲染(surface rendering)技术的概述和相关比较,主要涉及在如下几种方法:

  • Relief Mapping | 浮雕贴图

  • Cone step mapping (CSM) | 锥步映射

  • Relaxed cone step mapping (RCSM) | 宽松锥步映射

  • Parallax Occlusion Mapping(POM) | 视差遮蔽贴图

  • Quadtree Displacement Mapping(QDM)| 四叉树位移贴图

内容方面,文章围绕表面渲染技术,分别介绍了光线追踪与表面渲染、四叉树位移映射(Quadtree Displacement Mapping)、自阴影(Self-Shadowing)、环境光遮蔽(Ambient Occlusion)、表面混合(Surface Blending)几个部分。为了获得最高的质量/性能/内存使用率,文章建议在特定情况下使用视差映射,软阴影,环境遮挡和表面混合方法的组合。

此外,文中还提出了具有高度混合的四叉树位移贴图。对于使用复杂,高分辨率高度场的超高质量表面,该方法明显会更高效。此外,使用引入的四叉树结构提出了高效的表面混合,软阴影,环境遮挡和自动LOD方案的解决方案。在实践中,此技术倾向于以较少的迭代和纹理样本产生更高质量的结果。

图 Parallax Occlusion Mapping(POM) 视差遮蔽贴图和Quadtree Displacement Mapping(QDM)四叉树位移贴图和的渲染质量比较。其中,左图为POM;右图为QDM。深度尺寸分别为:1.0,1.5,5.0。可以发现,在深度尺寸1.5以上时,使用POM(左图)会看到失真。

图 表面混合质量比较。上图:浮雕贴图(Relief Mapping),下图:带高度混合的视差遮蔽贴图(POM with height blending)

 

5.1 核心实现Shader代码

以下为视差遮蔽贴图(Parallax Occlusion Mapping,POM)核心代码:

float Size = 1.0 / LinearSearchSteps;
float Depth = 1.0;
int StepIndex = 0;
float CurrD = 0.0;
float PrevD = 1.0;
float2 p1 = 0.0;
float2 p2 = 0.0;while (StepIndex < LinearSearchSteps)
{Depth -= Size; // move the rayfloat4 TCoord = float2 (p+(v*Depth )); // new sampling posCurrD = tex2D (texSMP , TCoord ).a; //new heightif (CurrD > Depth ) // check for intersection{p1 = float2 (Depth , CurrD );p2 = float2 (Depth + Size , PrevD ); // store last stepStepIndex = LinearSearchSteps; // break the loop}StepIndex ++;PrevD = CurrD ;
}// Linear approximation using current and last step
// instead of binary search , opposed to relief mapping .
float d2 = p2.x - p2.y;
float d1 = p1.x - p1.y;return (p1.x * d2 - p2.x * d1) / (d2 - d1);

四叉树位移贴图(Quadtree Displacement Mapping ,QDM)使用mipmap结构来表示密集的四叉树,在高度场的基准平面上方存储最大高度。QDM会在在交叉区域使用细化搜索,以便在需要时找到准确的解决方案。以下为四叉树位移贴图(QDM)搜索的核心代码:

const int MaxLevel = MaxMipLvl ;
const int NodeCount = pow (2.0, MaxLevel );
const float HalfTexel = 1.0 / NodeCount / 2.0;
float d;
float3 p2 = p;
int Level = MaxLevel ;//We calculate ray movement vector in inter -cell numbers .
int2 DirSign = sign(v.xy);// Main loop
while (Level >= 0)
{//We get current cell minimum plane using tex2Dlod .d = tex2Dlod (HeightTexture , float4 (p2.xy , 0.0 , Level )). w;//If we are not blocked by the cell we move the ray .if (d > p2.z){//We calculate predictive new ray position .float3 tmpP2 = p + v * d;//We compute current and predictive position .// Calculations are performed in cell integer numbers .int NodeCount = pow (2, (MaxLevel - Level ));int4 NodeID = int4((p2.xy , tmpP2 .xy) * NodeCount );//We test if both positions are still in the same cell.//If not , we have to move the ray to nearest cell boundary .if (NodeID .x != NodeID .z || NodeID .y != NodeID .w){//We compute the distance to current cell boundary .//We perform the calculations in continuous space .float2 a = (p2.xy - p.xy);float2 p3 = (NodeID .xy + DirSign) / NodeCount ;float2 b = (p3.xy - p.xy);//We are choosing the nearest cell//by choosing smaller distance .float2 dNC = abs (p2.z * b / a);d = min (d, min (dNC .x, dNC .y));// During cell crossing we ascend in hierarchy .Level +=2;// Predictive refinementtmpP2 = p + v * d;}//Final ray movementp2 = tmpP2 ;}// Default descent in hierarchy// nullified by ascend in case of cell crossingLevel --;
}
return p2;

这章也引入了一种表面混合的新方法,能更自然地适合表面混合,并且保证了更快的收敛。

文中建议使用高度信息作为额外的混合系数,从而为混合区域和更自然的外观添加更多种类,具体实现代码如下:

float4 FinalH ;
float4 f1 , f2 , f3 , f4;//Get surface sample .
f1 = tex2D(Tex0Sampler ,TEXUV .xy).rgba;//Get height weight .
FinalH .a = 1.0 - f1.a;
f2 = tex2D(Tex1Sampler ,TEXUV .xy).rgba;
FinalH .b = 1.0 - f2.a;
f3 = tex2D(Tex2Sampler ,TEXUV .xy).rgba;
FinalH .g = 1.0 - f3.a;
f4 = tex2D(Tex3Sampler ,TEXUV .xy).rgba;
FinalH .r = 1.0 - f4.a;// Modify height weights by blend weights .
//Per -vertex blend weights stored in IN.AlphaBlends
FinalH *= IN.AlphaBlends ;// Normalize .
float Blend = dot (FinalH , 1.0) + epsilon ;
FinalH /= Blend ;//Get final blend .
FinalTex = FinalH .a * f1 + FinalH .b * f2 + FinalH .g * f3 + FinalH .r * f4;

在每个交叉点搜索(intersection search)步骤中,使用新的混合运算符重建高度场轮廓,实现代码如下所示:

d = tex2D (HeightTexture ,p.xy).xyzw;
b = tex2D (BlendTexture ,p.xy). xyzw;
d *= b;
d = max (d.x ,max (d.y,max (d.z,d.w)));

 

 

六、使用几何着色器的NPR效果 | NPR Effects Using the Geometry Shader

 

本章的内容关于非真实感渲染(Non-photorrealistic rendering ,NPR)。在这章中,介绍了一组利用GPU几何着色器流水线阶段实现的技术。

具体来说,文章展示了如何利用几何着色器来在单通道中渲染对象及其轮廓,并对铅笔素描效果进行了模拟。

单通道方法通常使用某种预计算来将邻接信息存储到顶点中[Card and Mitchell 02],或者使用几何着色器 [Doss 08],因为可能涉及到查询邻接信息。这些算法在单个渲染过程中生成轮廓,但对象本身仍需要第一个几何通道。

 

6.1 轮廓渲染(Silhouette Rendering)

轮廓渲染是大多数NPR效果的基本元素,因为它在物体形状的理解中起着重要作用。在本节中,提出了一种在单个渲染过程中检测,生成和纹理化模型的新方法。

轮廓渲染(Silhouette rendering)技术中, 两大类算法需要实时提取轮廓:

  • 基于阴影体积的方法(shadow volume-based approaches)

  • 非真实感渲染(non-photorealistic rendering)

而从文献中,可以提取两种不同的方法:

  • 对象空间算法(object-space algorithms)

  • 图像空间算法(image-space algorithms)

但是,大多数现代算法都在图像空间(image space)或混合空间(hybrid space)中工作。本章中主要介绍基于GPU的算法。GPU辅助算法可以使用多个渲染通道或单个渲染通道来计算轮廓。

为了一步完成整个轮廓渲染的过程,将会使用到几何着色器(geometry shader)。因为几何着色阶段允许三角形操作,能获取相邻三角形的信息,以及为几何体生成新的三角形。

轮廓渲染过程在流水线的不同阶段执行以下步骤:

  • 顶点着色器(Vertex shader)。 顶点以通常的方式转换到相机空间。

  • 几何着色器(Geometry shader)。 在该阶段中,通过使用当前三角形及其邻接的信息来检测属于轮廓的边缘,并生成相应的几何体。

  • 像素着色器(Pixel shader)。 对于每个栅格化片段,生成其纹理坐标,并根据从纹理获得的颜色对像素进行着色。

 

图 管线概述:顶点着色器(左)变换传入几何体的顶点坐标;第二步(几何着色器)为对象的轮廓生成新几何体。最后,像素着色器生成正确的纹理坐标。

几何着色器轮廓检测代码如下:

[maxvertexcount (21)]
void main( triangleadj VERTEXin input [6],
inout TriangleStream <VERTEXout > TriStream )
{// Calculate the triangle normal and view direction .float3 normalTrian = getNormal ( input [0].Pos .xyz ,input [2].Pos .xyz , input [4].Pos .xyz );float3 viewDirect = normalize (-input [0].Pos .xyz- input [2]. Pos .xyz - input [4].Pos .xyz );//If the triangle is frontfacing[branch ]if(dot (normalTrian ,viewDirect ) > 0.0f){[loop]for (uint i = 0; i < 6; i+=2){// Calculate the normal for this triangle .float auxIndex = (i+2)%6;float3 auxNormal = getNormal ( input [i].Pos .xyz ,input[i+1].Pos .xyz , input[auxIndex ].Pos .xyz );float3 auxDirect = normalize (- input[i].Pos .xyz- input [i+1].Pos .xyz - input[auxIndex ].Pos .xyz );//If the triangle is backfacing[branch ]if(dot (auxNormal ,auxDirect) <= 0.0f){// Here we have a silhouette edge.}}}
}

几何着色器轮廓生成代码如下:

// Transform the positions to screen space .
float4 transPos1 = mul (input [i].Pos ,projMatrix );
transPos1 = transPos1 /transPos1 .w;
float4 transPos2 = mul (input [auxIndex ].Pos ,projMatrix );
transPos2 = transPos2 /transPos2 .w;// Calculate the edge direction in screen space .
float2 edgeDirection = normalize (transPos2 .xy - transPos1 .xy);// Calculate the extrude vector in screen space .
float4 extrudeDirection = float4 (normalize (
float2 (-edgeDirection.y ,edgeDirection.x)) ,0.0f ,0.0f);// Calculate the extrude vector along the vertex
// normal in screen space.
float4 normExtrude1 = mul (input [i].Pos + input [i]. Normal
,projMatrix );
normExtrude1 = normExtrude1 / normExtrude1.w;
normExtrude1 = normExtrude1 - transPos1 ;
normExtrude1 = float4 (normalize (normExtrude1.xy),0.0f ,0.0f);
float4 normExtrude2 = mul (input [auxIndex ].Pos
+ input [auxIndex ].Normal ,projMatrix );
normExtrude2 = normExtrude2 / normExtrude2.w;
normExtrude2 = normExtrude2 - transPos2 ;
normExtrude2 = float4 (normalize (normExtrude2.xy),0.0f ,0.0f);// Scale the extrude directions with the edge size.
normExtrude1 = normExtrude1 * edgeSize ;
normExtrude2 = normExtrude2 * edgeSize ;
extrudeDirection = extrudeDirection * edgeSize ;// Calculate the extruded vertices .
float4 normVertex1 = transPos1 + normExtrude1;
float4 extruVertex1 = transPos1 + extrudeDirection;
float4 normVertex2 = transPos2 + normExtrude2;
float4 extruVertex2 = transPos2 + extrudeDirection;// Create the output polygons .
VERTEXout outVert ;
outVert .Pos = float4 (normVertex1 .xyz ,1.0f);
TriStream .Append (outVert );
outVert .Pos = float4 (extruVertex1.xyz ,1.0f);
TriStream .Append (outVert );
outVert .Pos = float4 (transPos1 .xyz ,1.0f);
TriStream .Append (outVert );
outVert .Pos = float4 (extruVertex2.xyz ,1.0f);
TriStream .Append (outVert );
outVert .Pos = float4 (transPos2 .xyz ,1.0f);
TriStream .Append (outVert );
outVert .Pos = float4 (normVertex2 .xyz ,1.0f);
TriStream .Append (outVert );
TriStream .RestartStrip();

在像素着色器中轮廓纹理映射的实现代码:

float4 main(PIXELin inPut ):SV_Target
{// Initial texture coordinate .float2 coord = float2 (0.0f,inPut.UV.z);// Vector from the projected center bounding box to//the location .float2 vect = inPut .UV.xy - aabbPos ;// Calculate the polar coordinate .float angle = atan(vect.y/vect.x);angle = (vect.x < 0.0 f)? angle+PI:(vect.y < 0.0f)?angle +(2* PI): angle ;// Assign the angle plus distance to the u texture coordinate .coord .x = ((angle /(2* PI)) + (length (vect)* lengthPer ))* scale;//Get the texture color .float4 col = texureDiff .Sample (samLinear ,coord );// Alpha test.if(col .a < 0.1 f)discard ;// Return color .return col ;
}

图 轮廓渲染算法的运行效果图,轮廓剪影的实时生成和纹理化。

完整的实现Shader源码可见: https://github.com/QianMo/GPU-Pro-Books-Source-Code/blob/master/GPU-Pro-1/03_Rendering%20Techniques/02_NPReffectsusingtheGeometryShader/NPRGS/NPRGS/Silhouette.fx

 

6.2 铅笔素描渲染(Pencil Rendering)

 

基于Lee等人[Lee et al. 06]铅笔渲染思路可以概括如下。

首先,计算每个顶点处的最小曲率(curvature)。然后,三角形和其曲率值作为每个顶点的纹理坐标传入管线。 为了对三角形的内部进行着色,顶点处的曲率用于在屏幕空间中旋转铅笔纹理。该铅笔纹理会在屏幕空间中进行三次旋转,每个曲率一次,旋转后的结果进行混合结合。不同色调的多个纹理,存储在纹理阵列中,同时进行使用。最终,根据光照情况在其中选择出正确的一个。

图 管线概述:顶点着色器将顶点转换为屏幕空间;几何着色器将三角形的顶点曲率分配给三个顶点。最后,像素着色器生成三个曲率的纹理坐标并计算最终颜色。

可以通过以下方式使用GPU管线实现此算法:

  • 顶点着色器(Vertex shader)。 顶点转换为屏幕坐标。顶点曲率也被变换,只有x和y分量作为二维向量传递。

  • 几何着色器(Geometry shader)。 将曲率值作为纹理坐标分配给每个顶点。

  • 像素着色器(Pixel shader)。 计算最终颜色。

 

几何着色器的实现代码如下:

[maxvertexcount (3)]
void main( triangle VERTEXin input [3],
inout TriangleStream <VERTEXout > TriStream )
{// Assign triangle curvatures to the three vertices .VERTEXout outVert ;outVert .Pos = input [0].Pos ;outVert .norm = input [0]. norm;outVert .curv1 = input [0]. curv;outVert .curv2 = input [1]. curv;outVert .curv3 = input [2]. curv;TriStream .Append (outVert );outVert .Pos = input [1].Pos ;outVert .norm = input [1]. norm;outVert .curv1 = input [0]. curv;outVert .curv2 = input [1]. curv;outVert .curv3 = input [2]. curv;TriStream .Append (outVert );outVert .Pos = input [2].Pos ;outVert .norm = input [2]. norm;outVert .curv1 = input [0]. curv;outVert .curv2 = input [1]. curv;outVert .curv3 = input [2]. curv;TriStream .Append (outVert );TriStream . RestartStrip();
}

像素着色器的实现代码如下:

float4 main(PIXELin inPut ):SV_Target
{float2 xdir = float2 (1.0f ,0.0f);float2x2 rotMat ;// Calculate the pixel coordinates .float2 uv = float2 (inPut .Pos .x/width ,inPut .Pos .y/height );// Calculate the rotated coordinates .float2 uvDir = normalize (inPut .curv1 );float angle = atan(uvDir .y/uvDir.x);angle = (uvDir .x < 0.0 f)? angle +PI:(uvDir .y < 0.0f)? angle +(2* PI): angle ;float cosVal = cos (angle );float sinVal = sin (angle );rotMat [0][0] = cosVal ;rotMat [1][0] = -sinVal ;rotMat [0][1] = sinVal ;rotMat [1][1] = cosVal ;float2 uv1 = mul (uv ,rotMat );uvDir = normalize (inPut.curv2 );angle = atan(uvDir .y/uvDir.x);angle = (uvDir .x < 0.0 f)? angle +PI:(uvDir .y < 0.0f)? angle +(2* PI): angle ;cosVal = cos (angle );sinVal = sin (angle );rotMat [0][0] = cosVal ;rotMat [1][0] = -sinVal ;rotMat [0][1] = sinVal ;rotMat [1][1] = cosVal ;float2 uv2 = mul (uv ,rotMat );uvDir = normalize (inPut .curv3 );angle = atan(uvDir.y/uvDir.x);angle = (uvDir .x < 0.0 f)? angle +PI:(uvDir .y < 0.0f)?angle +(2* PI): angle ;cosVal = cos (angle );sinVal = sin (angle );rotMat [0][0] = cosVal ;rotMat [1][0] = -sinVal ;rotMat [0][1] = sinVal ;rotMat [1][1] = cosVal ;float2 uv3 = mul (uv ,rotMat );// Calculate the light incident at this pixel.float percen = 1.0f - max (dot (normalize (inPut .norm),lightDir ) ,0.0);// Combine the three colors .float4 color = (texPencil .Sample (samLinear ,uv1 )*0.333 f)+( texPencil .Sample (samLinear ,uv2 )*0.333 f)+( texPencil .Sample (samLinear ,uv3 )*0.333 f);// Calculate the final color .percen = (percen *S) + O;color .xyz = pow (color .xyz ,float3 (percen ,percen ,percen ));return color;
}

最终的渲染效果:

图 铅笔渲染效果图

完整的实现Shader源码可见: https://github.com/QianMo/GPU-Pro-Books-Source-Code/blob/master/GPU-Pro-1/03_Rendering%20Techniques/02_NPReffectsusingtheGeometryShader/NPRGS/NPRGS/Pencil.fx

 

 

七、后处理Alpha混合 | Alpha Blending as a Post-Process

 

在这篇文章中提出了一种新的Alpha混合技术,屏幕空间Alpha遮罩( Screen-Space Alpha Mask ,简称SSAM)。该技术首次运用于赛车游戏《Pure》中。《Pure》发行于2008年夏天,登陆平台为Xbox360,PS3和PC。

图 《Pure》中的场景(tone mapping & bloom效果)

在《Pure》的开发过程中,明显地需要大量的alpha混合(alpha blending)操作。但是众所周知,传统的计算机图形学的难题之一,就是正确地进行alpha混合操作,并且往往在性能和视觉质量之间,经常很难权衡。

实际上,由于不愿意承担性能上的风险,一些游戏会完全去避免使用alpha混合。有关alpha混合渲染所带来的问题的全面介绍,可以参考[Thibieroz 08],以及[Porter and Duff 84]。

在这篇文章中,提出了一种新颖的(跨平台)解决方案,用于树叶的alpha混合,这种解决方案可以提高各种alpha测试级渲染的质量,为它们提供真正的alpha混合效果。

文中设计的解决方案——屏幕空间Alpha遮罩(Screen-Space Alpha Mask ,简称SSAM),是一种采用渲染技术实现的多通道方法,如下图。无需任何深度排序或几何分割。

在《Pure》中使用的SSAM技术对环境的整体视觉质量有着深远的影响。 效果呈现出柔和自然的外观,无需牺牲画面中的任何细节。

图 SSAM的技术思路图示

此解决方案可以产生与alpha混合相同的结果,同时使用alpha测试技术正确解决每个像素的内部重叠(和深度交集)。

文中使用全屏幕后处理高效地执行延迟alpha混合(deferred alpha blending),类似于将帧混合操作设置为ADD的帧缓冲混合;源和目标参数分别设置为SRCALPHA和INVSRCALPHA。

混合输入被渲染成三个单独的渲染目标(render targets),然后绑定到纹理采样器(texture samplers),由最终的组合后处理像素着色器引用。

在内存资源方面,至少需要三个屏幕分辨率的渲染目标,其中的两个至少具有三个颜色的通道(rtOpaque & rtFoliage),而另一个至少有两个通道(rtMask)和一个深度缓冲区(rtDepth) 。

下面列举一些SSAM的优点和缺点。

SSAM的优点:

  • 树叶边缘与周围环境平滑融合。

  • 使用alpha测试技术,在每像素的基础上对内部重叠和相互穿透的图元进行排序。

  • 该效果使用简单,低成本的渲染技术实现,不需要任何几何排序或拆分(只需要原始调度顺序的一致性)。

  • 无论场景复杂度和overdraw如何,最终的混合操作都是以线性成本(每像素一次)来执行运算。

  • 该效果与能渲染管线中的其他alpha混合阶段(如粒子等)完美集成。

  • 与其他优化(如将光照移到顶点着色器)以及优化每个通道的着色器等方法结合使用时,总体性能可能会高于基于MSAA(MultiSampling Anti-Aliasing,多重采样抗锯齿)的技术。

SSAM的缺点:

  • 需要额外的渲染Pass的开销。

  • 内存要求更高,因为需要存储三张图像。

  • 该技术不能用于对大量半透明,玻璃状的表面进行排序(或横跨大部分屏幕的模糊alpha梯度),可能会产生失真。

 

7.1 核心实现Shader代码

 

最终后处理合成的像素着色器实现代码:

sampler2D rtMask : register (s0);
sampler2D rtOpaque : register (s1);
sampler2D rtFoliage : register (s2);
half maskLerp : register (c0); // 0.85h
half4 main(float2 texCoord : TEXCOORD0) : COLOR
{half4 maskPixel = tex2D ( rtMask , texCoord );half4 opaquePixel = tex2D ( rtOpaque , texCoord );half4 foliagePixel = tex2D (rtFoliage , texCoord );half mask = lerp(maskPixel .x , maskPixel .w, maskLerp );return lerp(opaquePixel , foliagePixel , mask * mask);
}

 

 

八、虚拟纹理映射简介 | Virtual Texture Mapping 101

 

在这篇文章主要探讨了如何实现一个功能完备的虚拟纹理映射(Virtual Texture Mapping,VTM)系统。

首先,虚拟纹理映射(VTM)是一种将纹理所需的图形内存量减少到仅取决于屏幕分辨率的技术:对于给定的视点,我们只将纹理的可见部分保留在图形存储器中适当的MIP映射级别上,如下图。

图 使用单个的虚拟纹理渲染出独特的纹理地形

早期的纹理管理方案是针对单个大纹理设计的[Tanner et al. 98],文章发表期间的VTM系统则更加灵活,模仿了操作系统的虚拟内存管理的思路:将纹理分成小的图块(tiles),或者页(pages)[Kraus and Ertl 02, Lefebvre et al.04]。这些会根据渲染当前视点的需要自动缓存并加载到GPU上。但是,有必要将对缺失数据的访问重定向(redirect)到后备纹理。这可以防止渲染中出现“空洞”(加载请求完成前的阻塞和等待的情况)。

文中的实现的灵感来源于GDC上Sean Barrett[Barret 08]的演讲。如下图所示,在每帧开始,先确定哪些图块(tiles)可见,接着识别出其中没有缓存且没有磁盘请求的图块。在图块上传到GPU上的图块缓存之后,更新一个间接纹理(indrection texture,)或者页表(page table)。最终,渲染场景,对间接纹理执行初始查找,以确定在图块缓存中采样的位置。

图 渲染图块ID,然后识别并更新最近可见的图块到图块缓存中(图中的红色),并可能会覆盖不再可见的图块(图中的蓝色)。更新间接纹理并渲染纹理化表面(texturized surfaces)

间接纹理(indirection texture)是完整虚拟纹理的缩小版本,其中每个纹素都指向图块缓存(tile cache)中的图块。在文中的示例中,图块缓存只是GPU上的一个大纹理,包含小的,相同分辨率的正方形图块。

这意味着来自不同mip map级别的图块(tiles)会覆盖虚拟纹理的不同大小区域,但会大大简化图块缓存的管理。

 

 

8.1 核心实现Shader代码

 

8.1.1 MIP 贴图计算的Shader实现 | MIP Map Calculation

float ComputeMipMapLevel(float2 UV_pixels , float scale)
{float2 x_deriv = ddx (UV_pixels );float2 y_deriv = ddy (UV_pixels );float d = max (length (x_deriv ), length (y_deriv ));return max (log2(d) - log2(scale ), 0);
}

8.1.2 图块ID 的Shader实现 | Tile ID Shader

float2 UV_pixels = In.UV * VTMResolution ,float mipLevel = ComputeMipMapLevel(UV_pixels , subSampleFactor);
mipLevel = floor(min (mipLevel , MaxMipMapLevel));float4 tileID ;
tileID .rg = floor (UV_pixels / (TileRes * exp2(mipLevel )));
tileID .b = mipLevel ;
tileID .a = TextureID ;return tileID ;

8.1.3 虚拟纹理查找的Shader实现 | Virtual Texture Lookup

float3 tileEntry = IndTex .Sample (PointSampler , In.UV);
float actualResolution = exp2(tileEntry .z);
float2 offset = frac(In.UV * actualResolution) * TileRes ;
float scale = actualResolution * TileRes ;
float2 ddx_correct = ddx (In.UV) * scale;
float2 ddy_correct = ddy (In.UV) * scale;
return TileCache .SampleGrad (TextureSampler ,
tileEntry .xy + offset ,
ddx_correct ,
ddy_correct );

 

 

Part III、全局光照 Global Illumination

 

九、基于间接光照的快速,基于模板的多分辨率泼溅 Fast, Stencil-Based Multiresolution Splatting for Indirect Illumination

 

本章介绍了交互式即时辐射度(radiosity)解决方案的改进,该解决方案通过使用多分辨率泼溅(multiresolution splats)技术,显着降低了填充率(fill rate),并展示了其使用模板缓冲(stencil buffer)的一种高效实现。与最原始的多分辨率泼溅[Nichols and Wyman 09]不同的是,此实现不通过几何着色器执行放大,因此能保持在GPU快速路径(GPU fast path)上。相反,这章利用了GPU的分层模板剔除(hierarchical stencil culling)和Z剔除(z culling)功能,以在合适的分辨率下高效地进行光照的渲染。

其核心实现算法如下:

pixels ←FullScreenQuad();
vpls ← SampledVirtualPointLights();
for all ( v ∈ vpls ) dofor all ( p ∈ pixels ) doif ( FailsEarlyStencilTest( p ) ) thencontinue; // Not part of multiresolution splatend ifIlluminatePatchFromPointLight( p, v );end for
end for

图 多分辨率光照泼溅开始于直接光照的渲染(左图)。每个VPL产生一个全屏幕的图示,允许每个VPL为每个像素提供光线。每个VPL产生一个全屏的泼溅,允许每个VPL为每个像素提供光线。但根据本地照明变化的速度,这些图层会以多种分辨率呈现。伪彩色全屏泼溅(中图)显示了不同分辨率的区域,这些区域被渲染为不同的buffer(右图)

图 多分辨率片的迭代求精从统一的粗图像采样开始(例如,162个采样)。处理粗粒度片元,识别需要进一步求精的片元并创建四个更精细的分辨率片元。进一步的操作会进一步细化片元直到达到某个阈值,例如最大精度级别或超过指定的片元数量。

图 前一幅图中的,多分辨率泼溅可以进行并行计算而不是迭代计算(左图)。右图中的多分辨率buffer中的片元,都为并行处理。

 

9.1 实现思路小结

 

9.1.1 多分辨率泼溅的实现思路 | Multiresolution Splatting Implement

以下是多分辨率泼溅实现思路,其中VPL的全称是虚拟点光源(virtual point light):

1.	Render a shadow map augmented by position, normal, and color.
2.	Select VPLs from this shadow map.
3.	Render from the eye using only direct light.
4.	For each VPL:(a)	Draw a full-screen “splat.”(b)	Each splat fragment illuminates one texel (in one of the multiresolu- tion buffers in Figure 1.4) from a single VPL, though this texel may ultimately affect multiple pixels.(c)	Blend each fragment into the appropriate multiresolution buffer.
5.	Combine, upsample and interpolate the multiresolution buffers.
6.	Combine the interpolated illumination with the direct light.

9.1.2 设置可接受模糊 | Setting acceptable blur

patches ← CoarseImageSampling();for (i=1 to numRefinementPasses) dofor all (p ∈ patches) doif ( NoDiscontinuity( p ) ) thencontinue;end ifpatches ← (patches − {p});patches ← (patches ∪ SubdivideIntoFour( p ) );end for
end for

9.1.3 从虚拟点光源收集光照进行泼溅 | Gathering illumination from VPLs for splatting

patches ← IterativelyRefinedPatches();
vpls ← SampledVirtualPointLights();
for all ( v ∈ vpls ) dofor all ( p ∈ patches ) doTransformToFragmentInMultiresBuffer( p ); // In vertex shaderIlluminateFragmentFromPointLight( p, v ); // In fragment shaderBlendFragmentIntoMultiresBufferIllumination( p );end for
end for

9.1.4 降采样多分辨率照明缓存 | Unsampling the multiresolution illumination buffer.

coarserImage ← CoarseBlackImage();
for all ( buffer resolutions j from coarse to fine ) dofinerImage ← MultresBuffer( level j );for all ( pixels p ∈ finerImage ) doif ( InvalidTexel( p, coarserImage ) ) thencontinue; // Nothing to blend from lower resolution!end ifp1, p2, p3, p4 ← FourNearestCoarseTexels( p, coarserImage );ω1, ω2, ω3, ω4 ← BilinearInterpolationWeights( p, p1, p2, p3, p4);for all ( i ∈ [1..4] ) doωi = InvalidTexel( pi, coarserImage ) ) ? 0 : ωi;end forfinerImage[p] += (ω1p1 + ω2p2 + ω3p3 + ω4p4)/(ω1 + ω2 + ω3 + ω4)end forcoarserImage ← finerImage;
end for

9.1.5 并行泼溅求精 | Parallel splat refinement

for all (fragments f ∈ image) doif ( _ j such that f ∈ MipmapLevel( j ) ) thencontinue; // Fragment not actually in multires bufferend ifj ← GetMipmapLevel( f );if ( IsDiscontinuity( f, j ) ) thencontinue; // Fragment needs further subdivisionend ifif ( NoDiscontinuity( f, j + 1 ) ) thencontinue; // Coarser fragment did not need subdivisionend ifSetStencil( f );
end for

9.1.6 最终基于模板的多分辨率泼溅算法

pixels ←FullScreenQuad();
vpls ← SampledVirtualPointLights();
for all ( v ∈ vpls ) dofor all ( p ∈ pixels ) doif ( FailsEarlyStencilTest( p ) ) thencontinue; // Not part of multiresolution splatend ifIlluminatePatchFromPointLight( p, v );end for
end for

 

 

十、屏幕空间定向环境光遮蔽 Screen-Space Directional Occlusion (SSDO)

 

环境光遮蔽(AO)是全局光照的一种近似,由于其良好的视觉质量和简单的实现[Landis 02],其常常用于电影和游戏中。环境光遮蔽的基本思想是预先计算网格表面几个位置的平均可见性值。然后这些值在运行时与图形硬件提供的未遮挡光照相乘。

环境光遮蔽的一个缺点是它仅适用于静态场景。如果为每个顶点或纹理元素预先计算了可见性值,则在网格变形时这些值将无效。

动态场景的一些初步想法有[Bunnell 06]和[Hoberock and Jia07]通过用层次圆盘(hierarchy of discs)近似几何体的思路。处理动态场景的最简单方法是根据帧缓冲区中的信息计算环境光遮蔽,即所谓的屏幕空间环境光遮蔽(SSAO)。这里深度缓冲区用于在运行时计算平均可见度值而不是预先计算。这章内容发表期间的GPU算力已足以实时计算SSAO。此外,该方法不需要场景的任何特殊几何的表现,因为仅使用到帧缓冲器中的信息来计算遮蔽值。甚至不需要使用由多边形组成的三维模型,因为我们可以从产生深度缓冲区的任何渲染计算遮挡。

 图 屏幕空间环境光遮蔽(SSAO):对于帧缓冲器中的每个像素,检查一组相邻像素,并将一个极小的球状物体放置在相应的三维位置。为每个球体计算遮蔽值,并将所有这些值累积到一个环境遮蔽值中。最后,该值乘以来自所有方向的未被遮蔽的光照。

环境光遮蔽通常显示空腔暗化(darkening of cavities)和接触阴影(contact shadows),但忽略入射光的所有方向信息。发生这种情况是因为只有几何体用于计算环境光遮蔽,而忽略了实际光照。典型的问题情况如下图所示:在方向变化的入射光的情况下,环境光遮蔽将显示错误的颜色。因此,该章将SSAO扩展到称之为屏幕空间定向遮挡(SSDO)的更真实光照技术。

由于循环遍历片段程序中的许多相邻像素,因此可以为每个像素计算单独的可见性值,而不是将所有信息折叠为单个AO值。因此,基本思想是使用来自每个方向的入射光的可见性信息,并仅从可见方向照射,从而产生定向的光照。

为对SSDO的数据做进一步描述,假设有一个深度帧缓冲区,其中包含每像素的位置,法线和反射率值。

图 环境光遮蔽的典型问题示例。由于红色光源被遮挡而绿色光源照亮了点P,我们希望在这里看到一个绿色的阴影。但环境遮挡首先计算来自所有方向的光照,因此点P最初为黄色,然后通过某个平均遮挡值进行缩放,从而产生了不正确的棕色。

本章提出的SSDO算法具体可以总结如下:

  • 首先,在像素的三维点周围放置一个半球,该半球沿着表面法线定向。该半球的半径r_max是用户参数,其用于决定搜索阻挡物的本地邻域的大小。

  • 然后,将一些三维采样点均匀分布在半球内部。同样,采样点数N是用于时间质量平衡的用户参数。

  • 接着,测试每个采样方向的光照是否被阻挡或可见。因此,我们将每个采样点反投影到深度帧缓冲区。在像素位置,可以读取表面上的三维位置,并将每个点移动到表面上。如果采样点朝向观察者移动,则它最初位于表面下方并且被分类为被遮挡。如果它远离观察者,它最初在表面上方并且被分类为可见。

在下图的示例中,点A,B和D在表面下方并被分类为遮挡物。只有样本C可见,因为它在表面上方。因此,仅从方向C计算光照。

图 SSDO屏幕空间定向环境光遮蔽。左图:为了计算点P处的方向遮挡,在半球中创建一些均匀分布的采样点,并将它们反投影到深度帧缓冲区中。(最初)在表面下方的每个点被视为遮挡物。 右图:仅从可见点计算光照。在这里,假设每个采样方向的立体角,并使用模糊环境贴图传入光亮度。

图 屏幕空间定向环境光遮蔽(Screen-Space Directional Occlusion,SSDO)效果图

 

10.1 核心实现Shader代码

 

10.1.1 屏幕空间定向环境光遮蔽SSDO 的Shader源码

// Read position and normal of the pixel from deep framebuffer .
vec4 position = texelFetch2D(positionTexture ,
ivec2 ( gl_FragCoord.xy), 0);
vec3 normal = texelFetch2D(normalTexture ,
ivec2( gl_FragCoord.xy), 0);// Skip pixels without geometry .
if(position .a > 0.0) {vec3 directRadianceSum = vec3 (0.0);vec3 occluderRadianceSum = vec3 (0.0);vec3 ambientRadianceSum = vec3 (0.0);float ambientOcclusion = 0.0;// Compute a matrix that transform from the unit hemisphere .// along z = -1 to the local frame along this normalmat3 localMatrix = computeTripodMatrix(normal );// Compute the index of the current pattern .// We use one out of patternSize * patternSize// pre -defined unit hemisphere patterns (seedTexture ).// The i’th pixel in every sub -rectangle uses always// the same i’th sub -pattern .int patternIndex = int (gl_FragCoord.x) % patternSize +(int ( gl_FragCoord.y) % patternSize) *patternSize ;// Loop over all samples from the current pattern .for (int i = 0; i < sampleCount ; i++) {// Get the i’th sample direction from the row at// patternIndex and transfrom it to local space .vec3 sample = localMatrix * texelFetch2D(seedTexture ,ivec2(i, patternIndex), 0). rgb ;vec3 normalizedSample = normalize (sample );// Go sample -radius steps along the sample direction ,// starting at the current pixel world space location .vec4 worldSampleOccluderPosition = position +sampleRadius * vec4(sample .x, sample .y , sample .z, 0);// Project this world occluder position in the current// eye space using the modelview -projection matrix .// Due to the deferred shading , the standard OpenGL// matrix can not be used.vec4 occluderSamplePosition = (projectionMatrix *modelviewMatrix) * worldSampleOccluderPosition ;// Compute the pixel position of the occluder :// Do a division by w first (perspective projection ),// then scale /bias by 0.5 to transform [-1,1] -> [0 ,1].// Finally scale by the texure resolution .vec2 occluderTexCoord = textureSize2D(positionTexture ,0)* (vec2 (0.5) + 0.5 * ( occluderSamplePosition .xy /occluderSamplePosition .w));// Read the occluder position and the occluder normal// at the occluder texture coordinate .vec4 occluderPosition = texelFetch2D(positionTexture ,ivec2 (occluderTexCoord), 0);vec3 occluderNormal = texelFetch2D(normalTexture ,ivec2 (occluderTexCoord), 0);float depth = (modelviewMatrix *worldSampleOccluderPosition ).z;// Compute depth of corresponding (proj.) pixel position .float sampleDepth = (modelviewMatrix *occluderPosition).z + depthBias ;// Ignore samples that move more than a// certain distance due to the projection// (typically singularity is set to hemisphere radius ).float distanceTerm = abs (depth - sampleDepth) <singularity ? 1.0 : 0.0;// Compute visibility when sample moves towards viewer .// We look along the -z axis , so sampleDepth is// larger than depth in this case.float visibility = 1.0 - strength *(sampleDepth > depth ? 1.0 : 0.0) * distanceTerm;// Geometric term of the current pixel towards the// current sample directionfloat receiverGeometricTerm = max (0.0,dot (normalizedSample , normal ));// Compute spherical coordinates (theta , phi )// of current sample direction .float theta = acos(normalizedSample.y);float phi = atan( normalizedSample.z ,normalizedSample.x);if (phi < 0) phi += 2*PI;// Get environment radiance of this direction from// blurred lat /long environment map .vec3 senderRadiance = texture2D (envmapTexture ,vec2( phi / (2.0* PI), 1.0 - theta / PI ) ).rgb ;// Compute radiance as the usual triple product// of visibility , radiance , and BRDF.// For practical reasons , we post -multiply// with the diffuse reflectance color.vec3 radiance = visibility * receiverGeometricTerm *senderRadiance;// Accumulate the radiance from all samples .directRadianceSum += radiance ;// Indirect light can be computed here// (see Indirect Light Listing )// The sum of the indirect light is stored// in occluderRadianceSum}// In case of a large value of -strength , the summed// radiance can become negative , so we clamp to zero here.directRadianceSum = max (vec3(0), directRadianceSum);occluderRadianceSum = max (vec3 (0), occluderRadianceSum );// Add direct and indirect radiance .vec3 radianceSum = directRadianceSum + occluderRadianceSum;// Multiply by solid angle and output result .radianceSum *= 2.0 * PI / sampleCount ;gl_FragColor = vec4(radianceSum , 1.0);} else {// In case we came across an invalid deferred pixelgl_FragColor = vec4 (0.0);
}

 

10.1.2 SSDO间接光计算Shader实现代码

间接光计算的源代码。 此时,从SSDO计算中已知像素位置和遮挡物位置/纹理坐标。这段代码可以包含在上述SSDO实现代码的循环结尾处。

// Read the (sender ) radiance of the occluder .
vec3 directRadiance = texelFetch2D(directRadianceTexture ,
ivec2( occluderTexCoord), 0);// At this point we already know the occluder position and
// normal from the SSDO computation . Now we compute the distance
// vector between sender and receiver .
vec3 delta = position .xyz - occluderPosition.xyz ;
vec3 normalizedDelta = normalize (delta );// Compute the geometric term (the formfactor ).
float unclampedBounceGeometricTerm =
max (0.0, dot (normalizedDelta , -normal )) *
max (0.0, dot (normalizedDelta , occluderNormal)) /
dot (delta , delta );// Clamp geometric term to avoid problems with close occluders .
float bounceGeometricTerm = min (unclampedBounceGeometricTerm ,
bounceSingularity);// Compute the radiance at the receiver .
vec3 occluderRadiance = bounceStrength * directRadiance *
bounceGeometricTerm ;// Finally , add the indirect light to the sum of indirect light .
occluderRadianceSum += occluderRadiance;

 

 

十一、基于几何替代物技术的实时多级光线追踪 | Real-Time Multi-Bounce Ray-Tracing with Geometry Impostors

 

 

在实时应用中渲染反射和折射物体或它们的焦散(caustics)是一个具有挑战性的问题。其需要非局部着色,这对于光栅化渲染管线来说比较复杂,其中片段着色器只能使用局部插值顶点数据和纹理来查找曲面点的颜色。

物体的反射、折射和其焦散效果通常需要光线跟踪进行渲染,但光线跟踪通常不具备与光栅化渲染相同的性能。

而通常,使用基于纹理的特殊tricks可以将光线跟踪效果加入到实时场景中。这些技术通常假设场景中只有一个反射或折射物体,并且仅考虑一次或两次反射光就足够了。在这章中,遵循了类似的实践原理,但是除去这些限制,以便能够渲染布满玻璃碎片的完整棋盘,甚至折射物体浸没在动画液体中等场景。

这章中扩展了先前基于环境距离替代物技术(environment distance impostors)的近似光线追踪技术,以便在当时硬件条件的限制下,实时渲染具有多个反射和折射物体的场景。

有两个关键的思路。

首先,文章改为使用距离替代物(distance impostor)方法,不将内部光线(internal rays)与封闭的环境几何体相交,而是将外部光线(external rays)与物体相交。另外,这章展示了如何高效地追踪二次反射和折射光线,还研究了可以适应相同的任务的其他类型的几何替代物技术 – 如几何图像(geometry images)[Carr et al. 06]和高度场(height fields)[Oliveira et al. 00, Policarpo et al. 05]。

第二个思路是静态和动态对象的分离。经典的距离替代物(distance impostors)技术可以用于静态环境,只需要在每一帧中更新移动对象的环境替代物(environment impostors)。通过搜索几何替代物(geometry impostors)可以找到穿过移动物体的光路。

图(a)环境距离替代物技术(environment distance impostor)(b)具有搜索投影前两步策略的物体距离替代物(Object distance impostor)

 图 左:整个测试场景。 右:使用高度图替代物进行双折射。

这章中扩展了先前基于环境距离替代物技术(environment distance impostors)的近似光线追踪技术,以便在当时硬件条件的限制下,实时渲染具有多个反射和折射物体的场景。

当然,随着技术的发展,2018年已经有了RTX技术,实时光线追踪已经不在话下。以下便是一个能展现实时光线追踪魅力的NVIDIA RTX Demo:

https://www.youtube.com/watch?v=KJRZTkttgLw

 

 

Part IV. 图像空间 Image Space

 

 

十二、 GPU上的各项异性的Kuwahara滤波 | Anisotropic Kuwahara Filtering on the GPU

 

这章中介绍一种各向异性的Kuwahara滤波器[Kyprianidis et al. 09]。各向异性的Kuwahara滤波器是Kuwahara滤波器的一种广义上的变体,通过调整滤波器的形状,比例和方向以适应输入的局部结构,从而避免了失真。由于这种适应性,定向图像特征被更好地保存和强调,得到了整体更清晰的边缘和更具特色的绘画效果。

图 原始图像(左),对各向异性的Kuwahara滤波输出(右)。沿着局部特征方向产生绘画般的增强效果,同时保留形状边界。

 

12.1 Kuwahara滤波器(Kuwahara Filtering)

 

Kuwahara滤波器背后的一般思想是将滤波器内核分成四个重叠一个像素的矩形子区域。滤波器的响应由具有最小方差的子区域的平均值来定义。

图 Kuwahara滤波器将滤波器内核分成四个矩形子区域。然后过滤器响应由具有最小方差的子区域的平均值来定义

图 Kuwahara滤波器的输出效果图

 

12.2 广义Kuwahara滤波器(Generalized Kuwahara Filtering)

 

而广义Kuwahara滤波器,为了克服不稳定次区域选择过程的局限性,定义了一个新的标准。结果被定义为次区域平均值的加权总和,而不是选择一个单独的次区域。权重是根据子区域的差异来定义的。 这导致区域边界更平滑并且失真更少。为了进一步改善这一点,矩形子区域被扇区上的平滑权重函数所取代:

图 广义的Kuwahara滤波器使用定义在光盘扇区上的加权函数。滤波过滤器响应被定义为局部平均值的加权总和,其中对具有低标准偏差的那些平均值赋予更多的权重。

图 广义Kuwahara滤波器的输出效果图

 

12.3 各向异性Kuwahara滤波器(Anisotropic Kuwahara Filtering)

 

广义的Kuwahara滤波器未能捕获定向特征并会导致集群的失真。而各向异性的Kuwahara滤波器通过使滤波器适应输入的局部结构来解决这些问题。在均匀区域中,滤波器的形状应该是一个圆形,而在各向异性区域中,滤波器应该变成一个椭圆形,其长轴与图像特征的主方向一致。

图 各向异性Kuwahara滤波器图示

 

十三、基于后处理的边缘抗锯齿 | Edge Anti-aliasing by Post-Processing

 

抗锯齿是高质量渲染的关键之一。例如,高质量的CG优先考虑抗锯齿的质量,而用于打印和品宣的游戏截图通常会采用人为高水平的超级采样来提高图像质量。

硬件多采样抗锯齿(Multi-Sampled Anti-Aliasing ,MSAA) [Kirkland 99]支持的是实现抗锯齿的标准方法,但是它是实现高质量抗锯齿的一种非常昂贵的方式,并且对抗锯齿与后处理效果提供的帮助甚微。

本章介绍了一种通过选择性像素混合对边缘进行抗锯齿的新方法。其仅需要MSAA所需空间的一小部分,并且与后处理效果相兼容。此抗锯齿方法的执行分为两个阶段。

首先,图像是没有任何多采样(multisampling)方法或超采样(super-sampling)方法的作用下渲染的。作为关于邻近边缘轮廓的近似细小的渲染提示被写出到帧缓冲区。然后应用后处理的pass,该通道使用这些细小的渲染提示来更新边缘像素,以提供抗锯齿。而在延迟效果(deferred effects)之后应用后处理(post-process),表示它们会接收边缘消除锯齿。

这种方法的核心部分为像素着色器提供了一种计算最近轮廓边缘位置的高效方法。这种技术也可以应用于阴影贴图放大,并提供了保持锐利边缘的放大方法。

下图显示了该方法的实际应用。

图 本章中的抗锯齿方法的效果图特写

图 复杂背景的抗锯齿效果演示。每个放大部分的左侧为4 MSAA的抗锯齿效果。右侧为本章方法(edge-blur render,边缘模糊抗锯齿)

 

十四、基于Floyd-Steinberg半色调的环境映射 | Environment Mapping with Floyd-Steinberg Halftoning

 

这章中提出了一种使用GPU计算重要性采样的算法。该算法巧妙地应用了经典的半色调技术,可用于加速高质量环境映射照明中的重要性采样步骤。

这章想传达的最重要的信息是半色调(halftoning)算法和重要性采样(importance sampling)是等价的,因此我们可以在重要性采样中使用半色调算法。文中研究了Floyd-Steinberg半色调方法在环境映射中的应用,并得出结论认为,该方法可以比随机抽样更好地对样本进行分配,所以,对的样本计算的积分也会更准确。

图 左图为随机采样加权环境贴图(Sampling weighted environment maps);右图为弗洛伊德 - 斯坦伯格采样半色调环境映射(Floyd-Steinberg halftoning)

图 光源采样结果。随机采样基于Floyd-Steinberg半色调映射通过方向光源对兔子模型的漫反射和镜面光照。

 

14.1 核心实现Shader代码

 

在几何着色器中实现的Floyd-Steinberg采样器Shader代码:

[maxvertexcount (32)]
void gsSampler ( inout PointStream <float4 > samples ) {uint M = 32; float S = 0;[loop]for (uint v = 0; v < R.y; v++)[loop]for (uint u = 0; u < R.x; u++)S += getImportance(uint2(u, v));float threshold = S / 2 / M;float4 cRow[RX4 ]={{0,0,0,0},{0 ,0 ,0 ,0},{0 ,0,0,0} ,{0,0,0 ,0}};float cPixel = 0, cDiagonal = 0, acc [4];[loop]for (uint j = 0; j < R.y; j++) {uint kper4 = 0;[loop]for (uint k = 0; k < R.x; k += 4) {for (uint xi = 0; xi < 4; xi++) {float I = getImportance(uint2 (k+xi , j));float Ip = I + cRow[kper4 ][xi] + cPixel ;if(Ip > threshold) {float3 dir = getSampleDir(uint2 (k+xi , j));samples .Append ( float4 (dir , I / S) );Ip -= threshold * 2;}acc [xi] = Ip * 0.375 + cDiagonal ;cPixel = Ip * 0.375;cDiagonal = Ip * 0.25;}cRow[kper4 ++] = float4 (acc [0], acc [1], acc [2], acc [3]);}j++; kper4 --;[loop]for (int k = R.x -5; k >= 0; k -= 4) {for (int xi = 3; xi >= 0; xi--) {float I = getImportance(uint2 (k+xi , j));float Ip = I + cRow[kper4 ][xi] + cPixel ;if(Ip > threshold ) {float3 dir = getSampleDir(uint2 (k+xi , j));samples .Append ( float4 (dir , I / S) );Ip -= threshold * 2;}acc [xi] = Ip * 0.375 + cDiagonal ;cPixel = Ip * 0.375;cDiagonal = Ip * 0.25;}cRow[kper4 --] = float4 (acc [0], acc [1], acc [2], acc [3]);}}
}

 

 

十五、用于粒状遮挡剔除的分层项缓冲 | Hierarchical Item Buffers for Granular Occlusion Culling

 

剔除(Culling)算法是许多高效的交互式渲染技术的关键,所有剔除算法的共同目标都是从渲染管线的几乎所有阶段减少工作量。

最常用的算法是在应用阶段采用视锥剔除(frustum culling)和视口剔除(portal culling)来排除不可见的几何体,通常按层次数据结构组织。更复杂的算法会在昂贵的前期流程中预计算整个可见集,以实现高效的实时可见性计算。

这章提出了一种直接在GPU上运行的剔除方法,该方法对应用程序完全透明且实现起来非常简单,特别是在下一代硬件和图形API上。文中表明,只需很少的开销,每帧的渲染时间可以显着减少,特别是对于昂贵的着色器或昂贵的渲染技术。该方法特别针对像几何着色器这样的早期着色器阶段,且应用目标是多方面。例如,[Engelhardt and Dachsbacher 09]展示了这种技术的应用,以加速每像素位移映射,但它也为基于可见性的LOD控制和曲面细分着色器中的剔除提供了可能性。

图 分层项缓冲区(Hierarchical Item Buffers)图示(a)确定可见度的实体;(b)光栅化后的项缓冲区(item buffer);(c)项缓冲区的直方图。实体3没有计算任何内容,因此是不可见的。

 

 

十六、后期制作中的真实景深 | Realistic Depth of Field in Postproduction

 

景深(Depth of field,DOF)是一种典型的摄影效果,其结果是根据摄像机与摄像机的距离而产生不同的聚焦区域。

这章中,提出了一种交互式GPU加速的景深实现方案,其扩展了现有方法的能力,具有自动边缘改进和基于物理的参数。散焦效应通常由模糊半径控制,但也可以由物理特性驱动。此技术支持在图像和序列上使用灰度深度图图像和参数,如焦距,f-stop,subject magnitude,相机距离,以及图像的实际深度。

另外,景深实现中额外的边缘质量改进会产生更逼真和可信的图像。而局部邻域混合算法的缺点是二次计算能力,但这其实可以通过GPU进行补偿。

图 景深效果图

图 模拟曝光的光圈孔径形状示例

 

十七、实时屏幕空间的云层光照 | Real-Time Screen Space Cloud Lighting

 

在创造逼真的虚拟环境时,云是一个重要的视觉元素。实时渲染美丽的云可能非常具有挑战性,因为云在保持交互式帧率的同时会呈现出难以计算的多重散射(multiple scattering)。

目前的问题是,大多数游戏都无法承担精确计算物理上正确的云层光照的计算成本。

本章介绍了一种可以实时渲染真实感的云层的非常简单的屏幕空间技术。这种技术已经在PS3上实现,并用于游戏《大航海时代Online》(Uncharted Waters Online)中。这项技术并不关注严格的物理准确性,而是依靠重新创建云层的经验外观。另外需要注意的是,此技术适用于地面场景,玩家可以在地面上观看,并且只能从远处观看云层。

光照是创造美丽和真实感云彩最重要的方面之一。当太阳光穿过云层时,被云层中的粒子吸收,散射和反射。下图展示了一个典型的户外场景。

图 一个典型的户外场景。 最靠近太阳的云显示出最大的散射并且看起来最亮

如图所示,从图中所示的视图看云层时,最靠近太阳的云显得最亮。这种现象是由于太阳的光线到达云层的后方,然后通过多次散射,在云的前部(最靠近观察者)重新出现。这一观察结果是这章所介绍技术的关键部分。为了再现这种视觉提示,屏幕空间中的简单点模糊或方向模糊足以模仿通过云层的光散射。

 

17.1 实现方案

 

这章的云层渲染技术可以分为三个pass执行:

  • 首先,渲染云密度(cloud density)为离屏渲染目标(RT),且云密度是可以由艺术家绘制的标量值。

  • 其次,对密度贴图(density map)进行模糊处理。

  • 最终,使用模糊的密度贴图来渲染具有散射外观的云层。

图 基于这章技术实现的demo截图

在demo中,云层被渲染为一个统一的网格。 云层纹理在每个通道中包含四个密度纹理。每个通道代表不同的云层,根据第一个通道中的天气在像素着色器中混合。并且也通过滚动纹理坐标UV来实现动画。

总之,这章提出了一种实时渲染的真实感天空的技术。由于云的形状与光源分离,程序化云的生成和程序化动画都可以支持。

需要注意的是,此方法忽略了大气的某些物理特性,以创建更高效的技术。例如,不考虑大气的密度,但这个属性对于创造逼真的日落和日出是必要的。也忽略了进入云层的光的颜色。在日落或日出的场景中,只有靠近太阳的区域应该明亮而鲜艳地点亮。有必要采取更基于物理的方法来模拟太阳和云之间的散射,以获得更自然的结果。

 

17.2 核心实现Shader代码

 

以下为云层光照像素着色器核心代码;注意,常数为大写。此着色器可以通过适当设置SCALE和OFFSET常量来提供平行线或点的模糊:

// Pixel shader input
struct SPSInput 
{float2 vUV : TEXCOORD0 ;float3 vWorldDir : TEXCOORD1 ;float2 vScreenPos : VPOS;
};// Pixel shader
float4 main( SPSInput Input ) 
{// compute direction of blur.float2 vUVMove = Input .vScreenPos * SCALE + OFFSET ;// Scale blur vector considering distance from camera .float3 vcDir = normalize ( Input .vWorldDir );float fDistance = GetDistanceFromDir( vcDir );vUVMove *= UV_SCALE / fDistance ;// Limit blur vector length .float2 fRatio = abs ( vUVMove / MAX_LENGTH );float fMaxLen = max ( fRatio .x, fRatio .y );vUVMove *= fMaxLen > 1.0 f ? 1.0 f / fMaxLen : 1.0 f;// Compute offset for weight .// FALLOFF must be negative so that far pixels affect less.float fExpScale = dot ( vUVMove , vUVMove ) * FALLOFF ;// Blur density toward the light.float fShadow = tex2D ( sDensity , Input.vUV ).a;float fWeightSum = 1.0 f;for ( int i = 1; i < FILTER_RADIUS; ++i ) {float fWeight = exp ( fExpScale * i );fShadow +=fWeight * tex2D(sDensity , Input .vUV +vUVMove *i).a;fWeightSum += fWeight ;}fShadow /= fWeightSum ;// 0 means no shadow and 1 means all shadowed pixel .return fShadow ;
}

 

十八、屏幕空间次表面散射 | Screen-Space Subsurface Scattering

 

这章提出了一种能够以后处理的方式,将渲染帧的深度-模板和颜色缓冲区作为输入,来模拟屏幕空间中的次表面散射的算法。此算法开创了皮肤渲染领域的基于屏幕空间的新流派。其具有非常简单的实现,在性能,通用性和质量之间取得了很好的平衡。

图 在纹理空间中执行模糊处理,如当前的实时次表面散射算法(上图)所做的那样,直接在屏幕空间完成模糊(下图)

该算法转换了从纹理到屏幕空间的扩散近似的计算思路。主要思想并不是计算辐照度图并将其与扩散剖面进行卷积,而是将卷积直接应用于最终渲染图像。下显示了此算法的核心思想。

图 屏幕空间次表面散射流程图

需要注意的是,要在屏幕空间中执行此工作,需要输入渲染该帧的深度模板和颜色缓冲区。

图 屏幕空间次表面散射示例。与纹理空间方法不同,屏幕空间的方法可以很好地适应场景中的对象数量(上图)。在不考虑次表面散射的情况下进行渲染会导致石头般的外观(左下图);次表面散射技术用于创建更柔和的外观,更能好地代表次表面散射效果(右下图)。

本章提出的次表面散射算法当物体处于中等距离时,提供了与Hable等人的方法[Hable et al.09]类似的性能,并且随着物体数量的增加能更好地胜任工作。且此方法更好地推广到其他材质。在特写镜头中,其确实需要用一些性能去换取更好的质量,但它能够保持原来d'Eon方法的肉感(fleshiness)[d’Eon and Luebke 07]。但是,在这些特写镜头中,玩家很可能会密切关注角色的脸部,因此值得花费额外的资源来为角色的皮肤提供更好的渲染质量。

 

18.1 核心实现Shader代码

 

进行水平高斯模糊的像素着色器代码:

float width ;
float sssLevel , correction , maxdd;
float2 pixelSize ;
Texture2D colorTex , depthTex ;float4 BlurPS (PassV2P input) : SV_TARGET {float w[7] = { 0.006 , 0.061 , 0.242 , 0.382 ,0.242 , 0.061 , 0.006 };float depth = depthTex .Sample (PointSampler ,input .texcoord ).r;float2 s_x = sssLevel / (depth + correction *min (abs (ddx (depth )), maxdd ));float2 finalWidth = s_x * width * pixelSize *float2 (1.0, 0.0);float2 offset = input .texcoord - finalWidth ;float4 color = float4 (0.0, 0.0, 0.0, 1.0);for (int i = 0; i < 7; i++) {float3 tap = colorTex .Sample (LinearSampler , offset ).rgb ;color .rgb += w[i] * tap ;offset += finalWidth / 3.0;}return color ;
}

 

Part V. 阴影 Shadows

 

阴影方面,作为次核心章节,仅进行更精炼的小篇幅的总结。

 

十九、快速传统阴影滤波 | Fast Conventional Shadow Filtering

 

这章介绍了如何减少常规阴影贴图过滤的硬件加速百分比邻近过滤(percentage closer filtering,PCF)纹理操作次数。现在只需要16次PCF操作就可以执行通常使用49次PCF纹理操作进行的均匀8×8过滤器。由于纹理操作的数量通常是传统阴影滤波的限制因素,因此实现的加速效果比较显著。PS:文中附带了大量的shader实现源码。

图 效果截图

 

二十、混合最小/最大基于平面的阴影贴图 | Hybrid Min/Max Plane-Based Shadow Maps

 

这章介绍了如何从常规的深度阴影贴图(depth-only shadow map)导出二次贴图。这种二次纹理可以用来大大加快昂贵的阴影滤波与过大的过滤器性能占用。它以原始阴影图中二维像素块的平面方程或最小/最大深度的形式存储混合数据。,被称为混合最小/最大平面阴影贴图(hybrid min/max plane shadow map,HPSM)。该技术特别适用于在大型过滤区域和前向渲染的情况下加速阴影过滤,例如,当阴影过滤成本随着场景的深度复杂度而增加时。

图 一个最小/最大阴影贴图像素(即有噪声的四边形)可以映射到许多屏幕上的像素。

 

二十一、基于四面体映射实现全向光阴影映射 | Shadow Mapping for Omnidirectional Light Using Tetrahedron Mapping

阴影映射(Shadow mapping)是用于三维场景渲染阴影的一种常用方法。William的原始Z-缓冲器阴影映射算法〔[Williams 78]是用于方向光源的,需要一种不同的方法来实现全向光(Omnidirectional Light)的阴影。

有两种流行的方法来接近全向光:一个是立方体映射(cube mapping )[Voorhies and Foran 94]和另一个是双抛物面映射(dual-paraboloid mapping)[Heidrich and Seidel 98]。而在本章中,提出了一种全新的使用四面体映射(tetrahedron mapping)的全向光阴影映射技术。

图 四个点光源,并使用四面体阴影映射与模板缓冲和硬件阴影映射得到渲染效果。二维深度纹理尺寸为1024×1024

 

 

二十二、屏幕空间软阴影 | Screen Space Soft Shadows

 

这章中提出了一种基于阴影映射的半影(penumbrae)实时阴影渲染的新技术。该方法使用了包含阴影与其潜在遮挡物之间距离的屏幕对齐纹理,其用于设置在屏幕空间中应用的各向异性高斯滤波器内核的大小,从而平滑标准阴影创建半影(penumbra)。考虑到高斯滤波器是可分离的,创建半影的样本数量会远低于其他软阴影方法。因此,该方法获得了更高的性能,同时也能得到外观正确的半影。

图 不同光源尺寸和不同光源颜色的半影的示例

 

The End.

下次更新,《GPU Pro 2》全书核心内容提炼总结,再见。

With best wishes.

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 浩辰ICAD电气软件IDq2003i.rar

    浩辰ICAD电气软件IDq2003i.rarAccelrys.Materials.Studio.v5.0.WiN32\AnyCasting 入门学习资料_教程_视频学习教程\Electronics Workbench Multisim v9.0.155 (EWB电路仿真与绘制)\Feflow5.3 有限元地下水流系统\Mentor Graphics DFT 2009.1.10 Linux\Mentor.Graphics.VeSys.v2…...

    2024/4/19 14:13:54
  2. java之十一 Java GUI

    窗口基本原理 视频课堂:https://edu.csdn.net/course/play/8222AWT根据类的层次定义窗口,并在每一层添加了特定的功能。在这些窗口中,用得最普遍的是在小应用程序派生于Panel类的窗口和派生于Frame类的独立窗口。这些窗口的功能大多数来自于它们的父类。因此,与Panel和Fra…...

    2024/4/11 19:43:00
  3. 阅读qt贪吃蛇代码、学习

    学qt只有两天而已,感觉qt真的很好入门。比mfc容易的很多。 学习qt短短时间,感觉自己可以仿照别人的代码来写些自己的桌面东西了。 不过,没有驱动,只是兴趣的学习下。可能到此为止了 主要收获:了解了,像贪吃蛇这样的游戏 的算法。存储结构是 char xx[200][2] 存储蛇的身体…...

    2024/4/19 21:54:33
  4. Tomcat 配置keystoreFile和keystorePass 启动异常解决

    可能是根本版本有关系 我的版本是:apachetomcat6.0.39 错误配置:<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"maxThreads="150" scheme="https" secure="true"clientAuth="false&quo…...

    2024/4/27 1:00:20
  5. c++获取字符串长度的方法

    字符串的长度通常是指字符串中包含字符的数目,但有的时候人们需要的是字符串所占字节的数目。常见的获取字符串长度的方法包括如下几种。后面有源码和最终效果图1.使用sizeof获取字符串长度sizeof的含义很明确,它用以获取字符数组的字节数(当然包括结束符0)。对于ANSI字符串…...

    2024/4/17 18:11:28
  6. 信息安全从业指南

    作者:赵武 链接:https://zhuanlan.zhihu.com/p/35753603 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。原本以为这辈子我都不会对这个话题进行专门的讨论,因为这个话题实在太无聊了,就好像你小孩问你怎么拿筷子一样。你可能会说还好…...

    2024/5/3 2:54:53
  7. tomcat 的最大连接数

    前提说明为了确保服务不会被过多的http长连接压垮,我们需要对tomcat设定个最大连接数,超过这个连接数的请求会拒绝,让其负载到其它机器。达到保护自己的同时起到连接数负载均衡的作用。动手去做一开始根据故障todoList提供的参数MaxKeepAliveRequests,进行验证,我们将tomc…...

    2024/4/20 6:52:06
  8. 2009国内最著名的程序员黑客名单及联系方式

    2009国内最著名的程序员黑客名单及联系方式   以下排名是按活跃度所整理。   小榕:netxeyes@live.hk http://www.netxeyes.com/ ;   被他的fans称为榕哥,流光等黑客工具的开发者。他开发的软件简单易用,但威力极大,一会儿后就能让一个小菜鸟轻易黑掉许多大网站。不过…...

    2024/4/17 18:10:22
  9. Linux--shell中获取字符串长度的常用方法

    求字符串长度的操作在shell脚本中很常用,下面归纳、汇总了求字符串的几种常用方法:【方法一】:利用{#str}来获取字符串长度root@ubuntu:/home/fl# str="ABCDEF" root@ubuntu:/home/fl# echo ${#str} 6【方法二】:利用awk的length方法root@ubuntu:/home/fl# str=&q…...

    2024/4/17 18:11:34
  10. 无线系列-无线通信系统组成

    1.前言为什么要写这些文章?无线通信整体来讲是个涉及很多知识的综合学科,之前零散的写过一些关于无线的文章。对于整个无线通信而言,只是冰山一角。思考了许久,决定打造一个完整的无线系列开发闭环教程。当然,这里讲的无线,包括WiFi、蓝牙、NFC等常用的无线通信软硬件。对…...

    2024/4/17 18:11:11
  11. VS2015贪吃蛇代码分析(10-6)

    点我下载代码2.5 开始游戏开始游戏实际上就是贪吃蛇开始接收键盘的控制。通过自定义函数StartGame()实现。2.5.1 判断按键信息通过Windows API函数GetAsyncKeyState()判断按键信息。GetAsyncKeyState()函数的作用是判断在调用该函数时,某个按键是否被按下。该函数的格式是SHOR…...

    2024/4/19 11:08:10
  12. IntelliJ IDEA 上配置Tomcat的教程?

    IntelliJ IDEA Tomcat配置教程第一步:进入编辑配置第二步:点击绿色加号---> Tomcat Server --->Local第三步:重复第一步,然后设置新添加的Tomcat服务器的参数注:①表示配置的服务器名称(可随意设置) ②表示本地的的Tomcat的的服务器,点击右侧的配置添加的T…...

    2024/4/17 18:11:04
  13. sql 获取字符串长度SQL字符串操作汇总

    --将字符串中从某个字符开始截取一段字符,然后将另外一个字符串插入此处 select stuff(hi,world!,4,4,****) --返回值hel****orld! --返回从指定位置开始指定长度的字符串 select substring(Hello,World!,2,10) --返回值ello,World --将字符串中某段字符替换为指定的字符串 se…...

    2024/4/19 11:16:11
  14. 2005年博客与web2.0十大最拽的武侠人物

    2005年,网络江湖被博客和web2.0炒得沸沸扬扬高潮迭起。在乱世当中,谁是武林中的主角?西门吹草今天就胡扯几段,评出2005年博客与web2.0十大最拽的武侠人物。究竟这些人是英雄还是小人,是善还是奸?这就要诸位看官自己去评判了。本次评选纯属虚构,如有雷同,姑且当为笑谈。…...

    2024/4/20 14:14:57
  15. 【资料分享】工程师必备物联网资料合集 电子书PDF

    对于许多电子工程师来说,各种电路资料,学习资料,新新技术资料等等,都有越多越好的。本篇帖子就为大家整理了一些比较受工程师欢迎的一些电路资料。如果你有心动的话,不妨就来搜集一波吧! 《Deep Learning》 《机器人学导论》(原书第3版) 《人工智能标准化白皮书(2018版…...

    2024/4/20 17:26:38
  16. Lua中获取字符串长度整理

    在Lua中,获取字符串长度我们一般使用#str(不建议使用string.len(str))!local str = "abc" local len = #str print(len) -- 3str = "你们好" len = #str print(len) -- 9这里就出现了一个问题:为啥"abc"的长度为3,而"你们好"的…...

    2024/4/27 10:39:34
  17. cocos2d-x学习笔记(贪吃蛇代码)

    方向键控制蛇运动:上↑;右→;下↓;左←。 百度网盘链接:https://pan.baidu.com/s/1c1FSXaw 提取密码:u1kr转载于:https://www.cnblogs.com/jiajunjie/p/7115943.html...

    2024/4/20 10:12:32
  18. 解决Tomcat配置正确,项目运行却报404问题,坑爹。。。

    JSP/html 新建时没注意放在了WEB-INF下。。。 浏览器是不能读取WEB-INF下的文件的...

    2024/4/27 3:30:48
  19. 这个城市---那条街---那个人....

    这个城市---那条街---那个人....2004年的3月我去了一家网吧上班,来到这个我现在才认为没前途的地方上班。 4月,在我工作中第一次认识你。 刚认识你的时候,我不敢看你。 因为我的心在颤抖,我喜欢你的时尚和美丽,还有你温柔的身影。 我不敢跟你说我的想法,我怕你会拒绝。 我…...

    2024/4/19 20:44:19
  20. 微信小程序 截取字符串长度

    index.wxs 代码//截取字符串长度>=14 var util = {toSubString: function (value) {if (value.length>=14){var str = value.substring(0, 14)return str + "..."} else {return value}}} module.exports = {toSubString: util.toSubString }index.wxml:代码:…...

    2024/4/19 15:57:57

最新文章

  1. 智慧能源数据监控平台

    随着科技的飞速发展&#xff0c;能源管理已逐渐从传统的粗放型向精细化、智能化转变。在这个转型过程中&#xff0c;HiWoo Cloud平台的智慧能源数据监控平台以其独特的技术优势和创新理念&#xff0c;正引领着能源管理的新潮流。 一、智慧能源数据监控平台的概念 智慧能源数据…...

    2024/5/3 3:34:43
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 怎么保证缓存与数据库的最终一致性?

    目录 零.读数据的标准操作 一.Cache aside Patten--旁路模式 二.Read/Write Through Pattern--读写穿透 三.Write Back Pattern--写回 四.运用canal监听mysql的binlog实现缓存同步 零.读数据的标准操作 这里想说的是不管哪种模式读操作都是一样的&#xff0c;这是一种统一…...

    2024/5/2 16:12:59
  4. 策略模式图

    策略模式 小小的图解 主要的三个角色 Strategy—抽象策略角色ConcreateStrategy—具体策略角色Context—上下文角色 封装了对具体策略的调用可以使用set的依赖注入也可以使用构造方法 核心是上下文角色 只要调用上下文角色就行&#xff0c;实现解耦 策略 工厂 将上下文角…...

    2024/5/1 13:35:23
  5. ARM FVP平台的terminal窗口大小如何设置

    当启动ARM FVP平台时&#xff0c;terminal窗口太小怎么办&#xff1f;看起来非常累眼睛&#xff0c;本博客来解决这个问题。 首先看下ARM FVP平台对Host主机的需求&#xff1a; 通过上图可知&#xff0c;UART默认使用的是xterm。因此&#xff0c;我们需要修改xterm的默认字体设…...

    2024/5/1 10:19:14
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57