3D游戏引擎系列(十一):后处理渲染

发表于2017-04-04
评论0 6.6k浏览

        后处理是游戏场景渲染的重要一环,利用这节给读者介绍运用于场景中的后处理渲染效果,后处理效果也是体现GPU强大的处理能力。最常用的后处理效果渲染:Bloom渲染、Blur渲染、HDR渲染等被称为后处理渲染。Bloom渲染又称为全屏泛光,后处理渲染在端游里应用的最广泛,通俗的讲就是场景首先经过CPU处理后,是每帧处理后再通过GPU在原有渲染的基础上再渲染一遍,最后将其在屏幕上显示出来的过程称为后处理渲染。实现方式新建一个文本文件把扩展名改成.fx。在编写Shader文件之前先讲一下Bloom实现原理主要分为4步:

1、提取原场景贴图中的亮色;

2、针对提取贴图进行横向Blur模糊;

3、在横向模糊基础上进行纵向Blur模糊;

4、所得贴图与原场景贴图叠加得最终效果图。

        根据这4个步骤,开始着手编写Shader代码,还是新建一个文本文件将扩展名命名为fx。对应的Shader完整代码如下所示:

  1. texture texSource;  
  2. texture texScene;  
  3.   
  4. struct VS_INPUT  
  5. {  
  6.     float4 pos : POSITION;  
  7.     float2 uv  : TEXCOORD0;  
  8. };  
  9.   
  10. struct VS_OUTPUT  
  11. {  
  12.     float4 pos : POSITION;  
  13.     float2 uv  : TEXCOORD0;  
  14. };  
  15.   
  16. VS_OUTPUT quad_vs(VS_INPUT vert)  
  17. {  
  18.     VS_OUTPUT vsout = (VS_OUTPUT)0;  
  19.       
  20.     vsout.pos = vert.pos;  
  21.     vsout.uv = vert.uv;  
  22.       
  23.     return vsout;  
  24. }  
  25.   
  26. sampler sourceSampler = sampler_state  
  27. {     
  28.     Texture = ;  
  29.     MinFilter = LINEAR;  
  30.     MagFilter = LINEAR;  
  31. };  
  32.   
  33. // 向下取样  
  34. //---------------------------------------------------  
  35. const float HighlightThreshold = 0.5;  
  36.   
  37. float luminance(float3 c)  
  38. {  
  39.     return dot( c, float3(0.3, 0.59, 0.11) );  
  40. }  
  41.   
  42. // this function should be baked into a texture lookup for performance  
  43. float highlights(float3 c)  
  44. {  
  45.     return smoothstep(HighlightThreshold, 1.0, luminance(c.rgb));  
  46. }  
  47.   
  48. float4 ps_downsample(float2 uv : TEXCOORD0) : COLOR  
  49. {  
  50.     float4 color = tex2D(sourceSampler, uv);  
  51.       
  52.     // store hilights in alpha  
  53.     color.a = highlights(color);  
  54.       
  55.     return color;  
  56. }  
  57.   
  58. // blur  
  59. //----------------------------------------------------------------  
  60. // blur filter weights  
  61. const float weights7[7] = {0.05, 0.1, 0.2, 0.3, 0.2, 0.1, 0.05};      
  62. //横向Blur  
  63. float4 ps_hblur(float2 uv : TEXCOORD0) : COLOR  
  64. {  
  65.     float texelSize = 1.0f/512.0f;  //1除以贴图大小  
  66.   
  67.     float4 color = 0;  
  68.     for(int i=0; i<7; i++)  
  69.     {  
  70.         // 把uv的生成放到vs会节省很多运算  
  71.         float2 uvi = uv + float2(texelSize*(i-3),0);  
  72.         color += tex2D(sourceSampler, uvi) * weights7[i];  
  73.     }  
  74.       
  75.     return color;  
  76. }  
  77.   
  78. //纵向Blur  
  79. float4 ps_vblur(float2 uv : TEXCOORD0) : COLOR  
  80. {  
  81.     float texelSize = 1.0f/384.0f;  //1除以贴图大小  
  82.   
  83.     float4 color = 0;  
  84.     for(int i=0; i<7; i++)  
  85.     {  
  86.         // 把uv的生成放到vs会节省很多运算  
  87.         float2 uvi = uv + float2(0, texelSize*(i-3));  
  88.         color += tex2D(sourceSampler, uvi) * weights7[i];  
  89.     }  
  90.       
  91.     return color;  
  92. }  
  93.   
  94. // 组合  
  95. //------------------------------------------------------------------  
  96. sampler sceneSampler = sampler_state  
  97. {     
  98.     Texture = ;  
  99.     MinFilter = None;  
  100.     MagFilter = None;  
  101.     AddressU = CLAMP;  
  102.     AddressV = CLAMP;  
  103. };  
  104.   
  105. float4 ps_compose(float2 uv : TEXCOORD0) : COLOR  
  106. {  
  107.     float4 blurColor = tex2D(sourceSampler, uv);  
  108.     float4 sceneColor = tex2D(sceneSampler, uv);  
  109.       
  110.     return sceneColor*0.8 + blurColor*0.2 + blurColor*blurColor.a;  
  111. }  
  112.   
  113. // technique  
  114. //------------------------------------------------------------------  
  115. technique PP_Bloom  
  116. {  
  117.     pass DownSample  
  118.     {  
  119.         CullMode = none;  
  120.         ZEnable = false;  
  121.         ZWriteEnable = false;  
  122.           
  123.         VertexShader = compile vs_1_1 quad_vs();  
  124.         PixelShader = compile ps_2_0 ps_downsample();  
  125.     }  
  126.       
  127.     pass HBlur  
  128.     {  
  129.         VertexShader = compile vs_1_1 quad_vs();  
  130.         PixelShader = compile ps_2_0 ps_hblur();  
  131.     }  
  132.       
  133.     pass VBlur  
  134.     {  
  135.         VertexShader = compile vs_1_1 quad_vs();  
  136.         PixelShader = compile ps_2_0 ps_vblur();  
  137.     }  
  138.       
  139.     pass Compose  
  140.     {  
  141.         VertexShader = compile vs_1_1 quad_vs();  
  142.         PixelShader = compile ps_2_0 ps_compose();  
  143.     }  
  144. }  

        以上是整个Shader代码的编写,接下来对于一些核心知识点重点介绍一下:第一步是实现提取原场景贴图中的亮色,因为Bloom又称为全屏泛光,所以要提取贴图中的亮色。对应的代码函数如下所示:

  1. const float HighlightThreshold = 0.5;  
  2.   
  3. float luminance(float3 c)  
  4. {  
  5.     return dot( c, float3(0.3, 0.59, 0.11) );  
  6. }  
  7.   
  8. // this function should be baked into a texture lookup for performance  
  9. float highlights(float3 c)  
  10. {  
  11.     return smoothstep(HighlightThreshold, 1.0, luminance(c.rgb));  
  12. }  

        第二步在第一步的基础上,针对需要渲染的贴图进行横向Blur模糊。函数代码如下:

[cpp] view plain copy
 
  1. const float weights7[7] = {0.05, 0.1, 0.2, 0.3, 0.2, 0.1, 0.05};      
  2.   
  3. float4 ps_hblur(float2 uv : TEXCOORD0) : COLOR  
  4. {  
  5.     float texelSize = 1.0f/512.0f;  //1除以贴图大小  
  6.   
  7.     float4 color = 0;  
  8.     for(int i=0; i<7; i++)  
  9.     {  
  10.         // 把uv的生成放到vs会节省很多运算  
  11.         float2 uvi = uv + float2(texelSize*(i-3),0);  
  12.         color += tex2D(sourceSampler, uvi) * weights7[i];  
  13.     }  
  14.       
  15.     return color;  
  16. }  

        第三步在第二步的基础上,针对提取贴图进行纵向Blur模糊。函数代码如下:

[cpp] view plain copy
 
  1. float4 ps_vblur(float2 uv : TEXCOORD0) : COLOR  
  2. {  
  3.     float texelSize = 1.0f/384.0f;  //1除以贴图大小  
  4.   
  5.     float4 color = 0;  
  6.     for(int i=0; i<7; i++)  
  7.     {  
  8.         // 把uv的生成放到vs会节省很多运算  
  9.         float2 uvi = uv + float2(0, texelSize*(i-3));  
  10.         color += tex2D(sourceSampler, uvi) * weights7[i];  
  11.     }  
  12.       
  13.     return color;  
  14. }  

        第四步将模糊处理的图片和原场景贴图分别进行纹理取样,最后将二者按照线性公式混合处理得到最终的结果。函数代码如下:

[cpp] view plain copy
 
  1. sampler sceneSampler = sampler_state  
  2. {     
  3.     Texture = ;  
  4.     MinFilter = None;  
  5.     MagFilter = None;  
  6.     AddressU = CLAMP;  
  7.     AddressV = CLAMP;  
  8. };  
  9.   
  10. float4 ps_compose(float2 uv : TEXCOORD0) : COLOR  
  11. {  
  12.     float4 blurColor = tex2D(sourceSampler, uv);  
  13.     float4 sceneColor = tex2D(sceneSampler, uv);  
  14.       
  15.     return sceneColor*0.8 + blurColor*0.2 + blurColor*blurColor.a;  
  16. }  

        下面开始介绍在引擎中的实现了,其C++实现也是根据Shader的思路编写的。首先通过函数创建三张纹理,这三张纹理都是在内存中的,第一张用于存放游戏中提取原场景贴图中的亮色,第二张用于存放横向模糊的图片,第三张用于存放纵向模糊的图片。另外编写的Shader文件必须要程序加载才能生效,C++方面也有对应着Shader编程的代码,主要分了四步:取样,横向Blur,纵向Blur,组合。现将C++核心代码展示如下:

[cpp] view plain copy
 
  1. bool PostProcessBloom::init(IDirect3DDevice9 *pD3DDevice)  
  2. {  
  3.     m_pD3DDevice = pD3DDevice;  
  4.   
  5.     if(!m_scene.init(pD3DDevice))  
  6.         return false;  
  7.   
  8.     // 创建所需的render targets  
  9.     HRESULT hr = pD3DDevice->CreateTexture(clientWidth, clientHeight,  
  10.         1,//mip-map  
  11.         D3DUSAGE_RENDERTARGET,  
  12.         D3DFMT_A8R8G8B8,  
  13.         D3DPOOL_DEFAULT,  
  14.         &m_pRTFull,  
  15.         NULL);  
  16.   
  17.     if(FAILED(hr))  
  18.         return false;  
  19.     //创建纹理存放横向Blur  
  20.     hr = pD3DDevice->CreateTexture(clientWidth/2, clientHeight/2,  
  21.         1,//mip-map  
  22.         D3DUSAGE_RENDERTARGET,  
  23.         D3DFMT_A8R8G8B8,  
  24.         D3DPOOL_DEFAULT,  
  25.         &m_pRTQuarterA,  
  26.         NULL);  
  27.     if(FAILED(hr))  
  28.         return false;  
  29.       
  30.     hr = pD3DDevice->CreateTexture(clientWidth/2, clientHeight/2,  
  31.         1,//mip-map  
  32.         D3DUSAGE_RENDERTARGET,  
  33.         D3DFMT_A8R8G8B8,  
  34.         D3DPOOL_DEFAULT,  
  35.         &m_pRTQuarterB,  
  36.         NULL);  
  37.     if(FAILED(hr))  
  38.         return false;  
  39.   
  40.     // create effect  
  41.     m_pEffect = DrawingUtil::getInst()->loadEffect(pD3DDevice,"Shader\Bloom.fx");  
  42.   
  43.     // pictue  
  44.     hr = D3DXCreateTextureFromFileEx(pD3DDevice, "Media\gf.jpg",  
  45.         D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2,  
  46.         1, //mip lv  
  47.         0,//usage  
  48.         D3DFMT_A8R8G8B8,  
  49.         D3DPOOL_MANAGED,  
  50.         D3DX_DEFAULT,  
  51.         D3DX_DEFAULT,  
  52.         0,  
  53.         NULL,  
  54.         NULL,  
  55.         &m_pPic);  
  56.   
  57.     if(FAILED(hr))  
  58.         return false;  
  59.       
  60.     D3DSURFACE_DESC sd;  
  61.     hr = m_pPic->GetLevelDesc(0, &sd);  
  62.   
  63.     return m_screenQuad.init(pD3DDevice);  
  64. }  
  65.   
  66. void PostProcessBloom::render()  
  67. {  
  68.     HRESULT hr;  
  69.   
  70.     IDirect3DTexture9 *pScene = NULL;  
  71.       
  72.     if(m_bUsePic)  
  73.         pScene = m_pPic;  
  74.     else  
  75.     {  
  76.         // 把场景渲染到RT full  
  77.         SetRenderTarget SRT(m_pD3DDevice, m_pRTFull);  
  78.         m_scene.render();  
  79.   
  80.         pScene = m_pRTFull;  
  81.     }  
  82.     UINT numPass = 0;  
  83.     hr = m_pEffect->Begin(&numPass,0);  
  84.     assert(numPass == 4);  
  85.     // down sample  
  86.     {  
  87.         hr = m_pEffect->SetTexture("texSource", pScene);  
  88.         hr = m_pEffect->BeginPass(0);  
  89.           
  90.         SetRenderTarget SRT(m_pD3DDevice, m_pRTQuarterA);  
  91.         m_screenQuad.draw();  
  92.   
  93.         hr = m_pEffect->EndPass();  
  94.     }  
  95.   
  96.     // H blur  
  97.     {  
  98.         hr = m_pEffect->SetTexture("texSource", m_pRTQuarterA);  
  99.         hr = m_pEffect->BeginPass(1);  
  100.   
  101.         SetRenderTarget SRT(m_pD3DDevice, m_pRTQuarterB);  
  102.         m_screenQuad.draw();  
  103.   
  104.         hr = m_pEffect->EndPass();  
  105.     }  
  106.   
  107.     // V blur  
  108.     {  
  109.         hr = m_pEffect->SetTexture("texSource", m_pRTQuarterB);  
  110.         hr = m_pEffect->BeginPass(2);  
  111.   
  112.         SetRenderTarget SRT(m_pD3DDevice, m_pRTQuarterA);  
  113.         m_screenQuad.draw();  
  114.   
  115.         hr = m_pEffect->EndPass();  
  116.     }  
  117.     // compose  
  118.     hr = m_pEffect->SetTexture("texSource", m_pRTQuarterA);  
  119.     hr = m_pEffect->SetTexture("texScene", pScene);  
  120.     hr = m_pEffect->BeginPass(3);  
  121.     m_screenQuad.draw();  
  122.     hr = m_pEffect->EndPass();  
  123.   
  124.     hr = m_pEffect->End();  
  125. }  

        上述代码是根据我们说的Bloom四步法实现的,C++代码中设置了四个Pass通道与Shader文件对应着处理。Bloom渲染技术也是面试官经常询问的问题,比如问面试者关于Bloom实现的原理,希望通过本节课的讲解对大家有所帮助。Bloom后处理渲染在游戏中的效果如下图:


        Bloom在端游特别是次时代游戏中使用是最多的,可以说是必不可少的后处理渲染,在评价一款游戏品质方面也占很大的比重,所以必须要重点掌握。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论