Bloom

Bloom

高斯模糊effect有各種各樣的應用。比如,可以用於區分場景中背景objects(通過模糊處理使得objects呈現出一種失去焦點的現象)和前景objects(沒有模糊處理)。另外還可以用於bloom(曝光) effect中,一種bloom effect通過增強場景中明亮的區域來模擬真實世界中的照相機。圖18.6顯示了在一個場景中使用曝光(上圖)和不使用曝光effect的輸出結果。


圖18.6 A scene rendered with (top) and without (bottom) a bloom effect. (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)
使用如下的步驟可以創建一種bloom effect:
1、把場景繪製到一個off-screen render target中。
2、從場景圖像中提取明亮的斑點保存到一個off-screen render target(創建一個glow map)中。
3、對glow map進行模糊處理並保存到一個off-screen render target中。
4、把經過模糊處理後的glow map與原始的scene texture進行合併,並把結果渲染到屏幕上。
列表18.12列出了Bloom.fx effect的代碼。其中包含了三個獨立的techniques:一個用於從輸入的texture中提取出明亮的區別,創建一個曝光貼圖(glow map),另一個是把模糊處理後的glow map與原始的scene texture進行合併,第三個是渲染未經過修改的原始scene texture。在Bloom.fx effect中沒有複製高斯模糊的代碼,因爲可以在應用程序中直接使用前面編寫的高斯模糊component對提取出的glow map進行模糊處理。

列表18.12 The Bloom.fx Effect

/************* Resources *************/

static const float3 GrayScaleIntensity = { 0.299f, 0.587f, 0.114f };

Texture2D ColorTexture;
Texture2D BloomTexture;

cbuffer CBufferPerObject
{
    float BloomThreshold = 0.3f;
    float BloomIntensity = 1.25f;
    float BloomSaturation = 1.0f;
    float SceneIntensity = 1.0f;
    float SceneSaturation = 1.0f;
};

SamplerState TrilinearSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

/************* Data Structures *************/

struct VS_INPUT
{
    float4 Position : POSITION;
    float2 TextureCoordinate : TEXCOORD;
};

struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float2 TextureCoordinate : TEXCOORD;  
};

/************* Utility Functions *************/

float4 AdjustSaturation(float4 color, float saturation)
{
    float intensity = dot(color.rgb, GrayScaleIntensity);
    
    return float4(lerp(intensity.rrr, color.rgb, saturation), color.a);
}

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;
    
    OUT.Position = IN.Position;
    OUT.TextureCoordinate = IN.TextureCoordinate;
    
    return OUT;
}

/************* Pixel Shaders *************/

float4 bloom_extract_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);

    return saturate((color - BloomThreshold) / (1 - BloomThreshold));
}

float4 bloom_composite_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 sceneColor = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
    float4 bloomColor = BloomTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
    
    sceneColor = AdjustSaturation(sceneColor, SceneSaturation) * SceneIntensity;
    bloomColor = AdjustSaturation(bloomColor, BloomSaturation) * BloomIntensity;
    
    sceneColor *= (1 - saturate(bloomColor));	

    return sceneColor + bloomColor;
}

float4 no_bloom_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    return ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
}

/************* Techniques *************/

technique11 bloom_extract
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, bloom_extract_pixel_shader()));
    }
}

technique11 bloom_composite
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, bloom_composite_pixel_shader()));
    }
}

technique11 no_bloom
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, no_bloom_pixel_shader()));
    }
}


在bloom_extract_pixel_shader中,將紋理採樣的顏色值與一個從應用程序中傳遞過來的threshold變量值進行比較,如果顏色值小於threshod,輸出的pixel就是黑色的。然後對threshold進行取反並用於調整顏色值。這並不是創建一個glow map的唯一方法:還可以使用紋理採樣的強度值與曝光的threshod值進行比較。例如:

float4 bloom_extract_pixel_shader(VS_OUTPUT IN) : SV_Target
{
	float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
	float intensity = dot(color.rgb, GrayScaleIntensity);
	return (intensity > BloomThreshold ? color : float4(0, 0, 0, 1));
}


圖18.7中顯示了使用這兩種pixel shaders從一個場景中提取的glow map結果。


圖18.7 Glow maps extracted from a scene using the original method (top) and an alternate method (bottom). (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)
通過使用高斯模糊組件對glow map進行模糊處理,使得該glow map顯示出了一種“流血”的效果就像光被其實的pixels吸收了一樣。然後把模糊處理後的glow map與原始的scene texture結合得到最終的結果,但是你也可以使用強度(intensity)和飽和度(saturation)值進一步調整最終的scene顏色值。本書的配套網站上提供了一個示例程序,支持在運行時動態調整強度和飽和度值。通過使用不同的值將會生成一些有趣的結果。

Distortion Mapping

本章要討論的最後一種post-processing technique是失真貼圖(distortion mapping)。Distortion mapping類似於第9章“Normal Mapping and Displacement Mapping”所講的移位貼圖(displacement mapping) effect。但不同的是,displacement mapping是通過改一個object的vertices,而distortion mapping是改變pixels。更具體地說,就是首先經過紋理採樣(一個distortion map)得到水平和垂直方向的偏移量,然後把這些偏移量用作在scene texture查找紋理的UV座標。

A Full-Screen Distortion Shader

列表18.13列出了一個full-screen post-processing distortion effect的pixel shader的代碼。

列表18.13 A Full-Screen Distortion Mapping Pixel Shader

cbuffer CBufferPerObjectComposite
{
	float DisplacementScale = 1.0f;
}

Texture2D SceneTexture;
Texture2D DistortionMap;

SamplerState TrilinearSampler
{
	Filter = MIN_MAG_MIP_LINEAR;
	AddressU = CLAMP;
	AddressV = CLAMP;
};

float4 displacement_pixel_shader(VS_OUTPUT IN) : SV_Target
{
	float4 OUT = (float4)0;
	float2 displacement = DistortionMap.Sample(TrilinearSampler, IN.TextureCoordinate).xy - 0.5;
	OUT = SceneTexture.Sample(TrilinearSampler, IN.TextureCoordinate + (DisplacementScale * displacement));
	return OUT;
}


在pixel shader中,通過採樣兩個通道值(x和y)得到displacement變量的水平和垂直方向值。這些通道值都是8-bit,由範圍[0, 255]映射到浮點數範圍[0.0, 1.0]。但是pixel的displacement值可以是正數也可以是負數,因此需要擴展這些範圍。具體範圍由所創建的displacement maps決定,但是在本書的這個例子中使用範圍[-0.5, 0.5]。從一個美術設計師的角度看,通道值爲127表示(almost幾乎)不進行移位displacement。之所以說幾乎(almost)不移位,是因爲127 / 255 ≈ 0.49804,因此有一個0.5 - 0.49804 = 0.00196的誤差值。通過在displaceent計算中加上該誤差值,就可以抵銷這個誤差。例如:

static const float ZeroCorrection = 0.5f / 255.0f;
float2 displacement = DistortionMap.Sample(TrilinearSampler, IN.TextureCoordinate).xy - 0.5 + ZeroCorrection;

渲染這種distortion mapping shader與color filtering shadrs一樣,就是一個正常的post-processing。首先把場景渲染到一個off-screen render target中,然後使用distortion mapping shader把一個full-screen quad渲染到back buffer中。圖18.8顯示了displacement shader的渲染輸出結果,其中使用了一種看起來像毛玻璃效果的distortion map。


圖18.8 Output of the full-screen distortion mapping shader using a distortion map resembling warped glass. (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)

使用毛玻璃效果的distortion map所產生的變化是如此細微,以至於無法簡單地區分失真的干擾(即使是在一個放大的圖像中)。因此,在displacement shader中使用一種包含“Direct3D !”文字的distortion map(如圖18.9下圖所示),可以得到如圖18.9上圖所示的輸出結果。該distortion map呈現出一種淡黃色,因爲在該map中每一個pixel只填充了red和green通道值。


圖18.9 Output of the full-screen distortion mapping shader (top) and associated distortion map (bottom). (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)

A Masking Distortion Shader

使用full-screen distortion mapping shader可以產生一些有趣的效果,如果動態處理displacenment偏移值(通過使用一個持續變化的值(如時間值)來調整該偏移值),則會使渲染結果更有趣。但是,我們可能並不想讓整個場景都失真。例如,通過使圖像局部失真可以模擬一團火焰上(或者發熱的路面上)的煙霧,並在該特定區域獨立使用這種effect。

實現這種效果的一種方法是創建一個運行時的distortion mask(失真蒙板),在一個full-screen distortion map上裁剪一塊將要進行失真處理的區域。圖18.10顯示了一個使用毛玻璃的失真貼圖以及一個sphere創建的distortion mask。


圖18.10 A distortion mask made with a sphere.
要創建這種distortion mask,需要在一個off-screen render target中首先ClearRenderTarget爲黑色,然後渲染objects。但是,我們可以從一個distortion map中獲取偏移值,而不是從一個object的color texture中採樣顏色值。因此,在最終創建的distortion mask中要麼是黑色的區域要麼是distortion偏移值。創建完mask之後,就可以在scene texture和distortion mask之間執行post-processing操作。首先,從distortion mask中採樣偏移值,如果該值表示黑色,那麼採scene texture就不需要移位紋理的UV座標。否則,就使用從mask中採樣的偏移值對紋理的UV座標進行偏移。列表18.14列出了distortion mapping effect的完整代碼。

列表18.14 A Masking Distortion Mapping Effect

/************* Resources *************/
static const float ZeroCorrection = 0.5f / 255.0f;

cbuffer CBufferPerObjectCutout
{
    float4x4 WorldViewProjection : WORLDVIEWPROJECTION;	
}

cbuffer CBufferPerObjectComposite
{
    float DisplacementScale = 1.0f;
}

Texture2D SceneTexture;
Texture2D DistortionMap;

SamplerState TrilinearSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = CLAMP;
    AddressV = CLAMP;
};

/************* Data Structures *************/

struct VS_INPUT
{
    float4 Position : POSITION;
    float2 TextureCoordinate : TEXCOORD;
};

struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float2 TextureCoordinate : TEXCOORD;
};

/************* Cutout *************/

VS_OUTPUT distortion_cutout_vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;
    
    OUT.Position = mul(IN.Position, WorldViewProjection);
    OUT.TextureCoordinate = IN.TextureCoordinate;

    return OUT;
}

float4 displacement_cutout_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float2 displacement = DistortionMap.Sample(TrilinearSampler, IN.TextureCoordinate).xy;

    return float4(displacement.xy, 0, 1);
}

/************* Compositing *************/

VS_OUTPUT distortion_composite_vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;
    
    OUT.Position = IN.Position;
    OUT.TextureCoordinate = IN.TextureCoordinate;
    
    return OUT;
}

float4 distortion_composite_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 OUT = (float4)0;

    float2 displacement = DistortionMap.Sample(TrilinearSampler, IN.TextureCoordinate).xy;
    if (displacement.x == 0 && displacement.y == 0)
    {
        OUT = SceneTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
    }
    else
    {
        displacement -= 0.5f + ZeroCorrection;
        OUT = SceneTexture.Sample(TrilinearSampler, IN.TextureCoordinate + (DisplacementScale * displacement));
    }

    return OUT;
}

float4 no_distortion_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    return SceneTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
}

/************* Techniques *************/

technique11 displacement_cutout
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, distortion_cutout_vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, displacement_cutout_pixel_shader()));
    }
}

technique11 distortion_composite
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, distortion_composite_vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, distortion_composite_pixel_shader()));
    }
}

technique11 no_distortion
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, distortion_composite_vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, no_distortion_pixel_shader()));
    }
}


在該shader中包含了三種techniques: distortion_cutout:調用vertex和pixel shader用於創建distortion mask。在vertex shader中,輸入參數的vertex position應該位於object space,因此需要對position執行變換。在pixel shader中,使用vertices的紋理座標從distortion map採樣顏色值,然後把x和y通道值作爲輸出。
distortion:定義了在scene texture和distortion map之間post-processing的執行步驟。在vertex shader中要求輸入的vertex positions已經位於screen space,而且不執行變換(與所有的post-processing effects一樣)。在pixel shader中根據採樣得到的偏移值,判斷在採樣scene texture時是否要使用displacement偏移值。
no_distortion:不執行失真操作,直接輸出scene texture。

渲染masking distortion shader需要使用兩個render targets,一個用於渲染無失真的背景objects,另一個用於創建distortion mask。在本書的配套網站上提供一個完整示例程序。圖18.11中顯示了使用masking distortion shader的輸出結果(上圖),該shader中使用了一種人形軀幹模型創建distortion mask(下圖)。


圖18.11 Output of the distortion masking shader (top) and associated distortion mask (bottom). (Skybox texture by Emil Persson. 3D model by Nick Zuccarello, Florida Interactive Entertainment Academy.)

總結

本章介紹了post-processing主題相關的知識,post-processing是指在渲染完成後的場景中使用一些圖形技術。編寫了大量的post-processing effects用於表示color filtering,Gaussian blurring,bloom/glow以及distortion mapping。並在C++渲染引擎中使用full-screen render target和quad component集成了這些effects,最後通過演示程序練習了這些effects的使用。
在下一章,我們將會學習projective texture mapping(投影紋理貼圖)和shadow mapping(陰影貼圖)。

Exercises

1. Experiment with all the effects and demo applications from this chapter. Vary the shader
inputs, and observe the results.
2. Create your own distortion maps for the post-processing distortion shader, and use them
with the associated demo application.
3. Animate the masking distortion shader to simulate heat haze above a fire. Hint: Use
GameTime::TotalGameTime() as input into the shader.
1、測試本章所有的effect和示例程序。在shader中使用不同的輸入,並觀察輸了結果。
2、創建你自己的distortion maps用於post-processing distortion shader,並在對應的示例程序中使用該shader。
3、實現一個動態變化的masking distortion shader,用於模擬一團火焰上的煙霧效果。提示:使用函數GameTime::TotalGameTime()的返回值作爲shader的輸入。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章