HDRLighting Sample

DX9 Update中的HDRLighting Sample分析 收藏
这里着重分析整个渲染过程,至于HDR就不介绍了。(下面是流程图)

   

HDR的流程大概为:

1,使用高亮度渲染场景,并保存为浮点数纹理。

- 应用HDR环境贴图。

- 应用高范围的材质,光源和照明模型。(即不必被局限在0~1里)

2,将高亮区域取出,保存为浮点数纹理。

- 在pixel shader里判断该像素的亮度是否大于阀值。

vec3 color = texture2D(scene, gl_TexCoords[0].st);

float lum = dot(vec3(0.3, 0.59, 0.11), color);

gl_FragColor.rgb = (lum > brightThreshold) ? color : vec3(0.0);

3,对高亮区域进行应用bloom滤镜。

4,对高亮区域应用streak滤镜。

5,对高亮区域应用ghost滤镜。

6,将scene,bloom,streak,ghost等合成为一张最终效果纹理。

7,对最终纹理应用tone mapping,及曝光调整。

 

 

    我们先来看看HDRLighting Sample里面比较重要的一些Texture:

    PDIRECT3DTEXTURE9 m_pTexScene;            // HDR render target containing the scene
    //保持整个场景的FP Texture格式是D3DFMT_A16B16G16R16F这样保存的颜色值就可以突破0-1的限制
    PDIRECT3DTEXTURE9 m_pTexSceneScaled;      // Scaled copy of the HDR scene
    //一个上面m_pTexScene的缩小到1/4的FP Texture格式和m_pTexScene一样,这个是我们后面的一些post-process的来源
    PDIRECT3DTEXTURE9 m_pTexBrightPass;       // Bright-pass filtered copy of the scene
    //保存了m_pTexScene通过Bright-Pass Filter后的,只剩下了亮度高的部分。因为这个Texture不需要去做一些HDR相关的操作,
所以格式32bit贴图就行了。这个是后面我们做Bloom,Star的来源。
    PDIRECT3DTEXTURE9 m_pTexAdaptedLuminanceCur;  // The luminance that the user is currenly adapted to
    //当前适合的亮度值,我们为了模拟出眼睛对于光的适应过程,采用的两个亮度值之一。格式是D3DFMT_R16F,大小是1x1
    PDIRECT3DTEXTURE9 m_pTexAdaptedLuminanceLast; // The luminance that the user is currenly adapted to
    //和上面的那个一样,我们通过交换这两个贴图来作出对于光的适应过程
    PDIRECT3DTEXTURE9 m_pTexStarSource;       // Star effect source texture
    //做Star的来源,格式是D3DFMT_A8R8G8B8
    PDIRECT3DTEXTURE9 m_pTexBloomSource;      // Bloom effect source texture
    //做Bloom的来源,格式是D3DFMT_A8R8G8B8
  
    PDIRECT3DTEXTURE9 m_apTexBloom[NUM_BLOOM_TEXTURES];     // Blooming effect working textures
    //Bloom效果用的系列贴图,格式是D3DFMT_A8R8G8B8,这里一共3张
    PDIRECT3DTEXTURE9 m_apTexStar[NUM_STAR_TEXTURES];       // Star effect working textures
    //Star效果用的系列贴图,格式是D3DFMT_A8R8G8B8,这里一共12张
    PDIRECT3DTEXTURE9 m_apTexToneMap[NUM_TONEMAP_TEXTURES]; // Log average luminance samples
                                                            // from the HDR render target
    //计算场景的亮度值用的系列贴图,格式是D3DFMT_R16F,在这里大小为1X1,4X4,16X16,64X64,一共4张

   

    接下来就是整个的渲染过程 Render()

    PDIRECT3DSURFACE9 pSurfLDR; // Low dynamic range surface for final output
    PDIRECT3DSURFACE9 pSurfHDR; // High dynamic range surface to store
                                // intermediate floating point color values
   
    // Setup HDR render target
    m_pd3dDevice->GetRenderTarget(0, &pSurfLDR);
    m_pTexScene->GetSurfaceLevel(0, &pSurfHDR);

    // Clear the viewport
    m_pd3dDevice->SetRenderTarget(0, pSurfHDR);
    m_pd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 0, 0), 1.0f, 0L);

    //这段代码保存了原始的RT,设置m_pTexScene为RT.

    RenderScene();

    //将Scene渲染到我们新的RT上,由于RT是FP的,我们能够保存超过0-1的数据
   
    Scene_To_SceneScaled();

    //将m_pTexScene渲染到m_pTexSceneScaled上,也就是缩小到1/4,这样的作用是在下面的处理能提高不少速度,我们看看这
个函数里面一些主要的操作:

    HRESULT CMyD3DApplication::Scene_To_SceneScaled()
    {
        ....
        m_pEffect->SetTechnique("DownScale4x4");

        //这里用到了DownScale4x4这段shader,我们仔细得看看这个shader

        float4 DownScale4x4(in float2 vScreenPosition : TEXCOORD0) : COLOR
        {

        float4 sample = 0.0f;

    for( int i=0; i < 16; i++ )
    {
sample += tex2D( s0, vScreenPosition + g_avSampleOffsets[i] );
    }
   
    return sample / 16;
        }

        //这里就是按照g_avSampleOffsets的偏移值采样16个texel,然后计算均值。这个偏移数组的数据怎么样计算得出来的呢?
        //我们注意到函数接下来的几行有这样两句

        GetSampleOffsets_DownScale4x4( m_d3dsdBackBuffer.Width, m_d3dsdBackBuffer.Height, avSampleOffsets );
        m_pEffect->SetValue("g_avSampleOffsets", avSampleOffsets, sizeof(avSampleOffsets));

        //这两句就是计算出这个偏移数组并且设定到相应的位置,这里我们先不具体的看GetSampleOffsets_DownScale4x4函数,
通过这个函数得到的偏移数组是以原始采样点为中心的4X4的一个矩阵。
  
        m_pd3dDevice->SetRenderTarget( 0, pSurfScaledScene );
        m_pd3dDevice->SetTexture( 0, m_pTexScene );
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT );
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP );
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );

        //这样我们就得到了1/4的版本pSurfScaledScene

    }

    //接下来就是求平均亮度的过程

    if( m_bToneMap )
        MeasureLuminance();

    //我们仔细得看看这个函数里面执行的操作

    HRESULT CMyD3DApplication::MeasureLuminance()
    {
        ....

        //首先这里是先计算了9个采样数据的偏移值

        float tU, tV;
        tU = 1.0f / (3.0f * desc.Width);
        tV = 1.0f / (3.0f * desc.Height);
   
        index=0;
        for( x = -1; x <= 1; x++ )
        {
            for( y = -1; y <= 1; y++ )
            {
                avSampleOffsets[index].x = x * tU;
                avSampleOffsets[index].y = y * tV;

                index++;
            }
        }

        //这9个形成3X3的矩阵
       
        //接下来是设置相应的操作和数据

        m_pEffect->SetTechnique("SampleAvgLum");
        m_pEffect->SetValue("g_avSampleOffsets", avSampleOffsets, sizeof(avSampleOffsets));
   
        m_pd3dDevice->SetRenderTarget(0, apSurfToneMap[dwCurTexture]);
        m_pd3dDevice->SetTexture(0, m_pTexSceneScaled);
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
        m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
        m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );

        //在这里RT是m_apTexToneMap[3],而shader操作则是一个取值,计算Log,计算均值得过程,下面是shader的代码

        float4 SampleLumInitial(in float2 vScreenPosition : TEXCOORD0) : COLOR
{
     float3 vSample = 0.0f;
     float  fLogLumSum = 0.0f;

     for(int iSample = 0; iSample < 9; iSample++)
     {
         // Compute the sum of log(luminance) throughout the sample points
         vSample = tex2D(s0, vScreenPosition+g_avSampleOffsets[iSample]);
         fLogLumSum += log(dot(vSample, LUMINANCE_VECTOR)+0.0001f);
     }
   
     // Divide the sum to complete the average
     fLogLumSum /= 9;

     return float4(fLogLumSum, fLogLumSum, fLogLumSum, 1.0f);
}

        //于是m_apTexToneMap[3]保存的就是按照3X3的方式采样得到的平均的Log值,我们的目标是计算出所有的像素的Log的
平均值然后exp,我们下一部就是利用每次的缩小,取得更大范围的平均值,最后到4X4得贴图,我们在采样这最后的16个像素,然
exp(value/16);

        //下面的循环就是完成直到4X4得求平均值的过程

while( dwCurTexture > 0 )
     {
         m_apTexToneMap[dwCurTexture+1]->GetLevelDesc( 0, &desc );
         GetSampleOffsets_DownScale4x4( desc.Width, desc.Height, avSampleOffsets );
   
         m_pEffect->SetTechnique("ResampleAvgLum");
         m_pEffect->SetValue("g_avSampleOffsets", avSampleOffsets, sizeof(avSampleOffsets));

         m_pd3dDevice->SetRenderTarget(0, apSurfToneMap[dwCurTexture]);
         m_pd3dDevice->SetTexture(0, m_apTexToneMap[dwCurTexture+1]);
         m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT );
         m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT );
   
         ....

         dwCurTexture--;
    }

//这个部分的shader操作如下

float4 SampleLumIterative(in float2 vScreenPosition : TEXCOORD0) : COLOR
{
     float fResampleSum = 0.0f;
   
     for(int iSample = 0; iSample < 16; iSample++)
     {
         // Compute the sum of luminance throughout the sample points
         fResampleSum += tex2D(s0, vScreenPosition+g_avSampleOffsets[iSample]);
     }
   
     // Divide the sum to complete the average
     fResampleSum /= 16;

     return float4(fResampleSum, fResampleSum, fResampleSum, 1.0f);
}

        //每次的操作都使用上次的结果作为Texture

        //最后一步就是计算我们的exp(value/16);

        m_pEffect->SetTechnique("ResampleAvgLumExp");
        m_pEffect->SetValue("g_avSampleOffsets", avSampleOffsets, sizeof(avSampleOffsets));
   
        m_pd3dDevice->SetRenderTarget(0, apSurfToneMap[0]);
        m_pd3dDevice->SetTexture(0, m_apTexToneMap[1]);
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT );
     m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT );

        //相应的shader操作是:

        float4 SampleLumFinal(in float2 vScreenPosition : TEXCOORD0) : COLOR
{
     float fResampleSum = 0.0f;
   
     for(int iSample = 0; iSample < 16; iSample++)
     {
         // Compute the sum of luminance throughout the sample points
         fResampleSum += tex2D(s0, vScreenPosition+g_avSampleOffsets[iSample]);
     }
   
     // Divide the sum to complete the average, and perform an exp() to complete
     // the average luminance calculation
     fResampleSum = exp(fResampleSum/16);
   
     return float4(fResampleSum, fResampleSum, fResampleSum, 1.0f);
}

        //这样我们就得到场景的平均亮度m_apTexToneMap[0]这个1X1的贴图里面。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sevecol/archive/2003/09/26/2489.aspx

面如果我们不打算做Star和Bloom效果的话,我们可以直接使用这个亮度值来进行我们最后的调整,获得最后的画面。但是如果HDR缺少了Star和Bloom或类似的效果的话,就如同太阳失去了光芒一样。不过在进行我们的Star和Bloom效果处理之前还有一个问题需要解决,这个问题和这些效果没什么关系。我们知道我们的眼睛当遇到强光的时候,并不是一下子就能适应,而是有个渐进的过程。如果我们的程序没有这个处理的话,将会失色很多。也就是说两个亮度值之间需要有个过渡的变化。我们如何做到这一点呢?

    fNewAdaptation = fAdaptedLum + (fCurrentLum - fAdaptedLum) * ( 1 - pow( 0.98f, 30 * g_fElapsedTime ) );

    上面的就是我们计算过渡亮度值的公式,fCurrentLum是我们的目标的亮度值,fAdaptedLum是上一桢的亮度值,我们在每次计算的时候,一般是保持fCurrentLum,上一次计算的fNewAdaptation作为这次计算的fAdaptedLum,这样我们就能做到逐步的接近我们的最终结果fCurrentLum。而我们把每桢计算得出的fNewAdaptation作为我们调整颜色用的亮度值。

    让我们来具体的看看代码

    if( m_bAdaptationInvalid )
    {
        // Clear the update flag
        m_bAdaptationInvalid = false;

        // Calculate the current luminance adaptation level
        CalculateAdaptation();
    }

    这里的if( m_bAdaptationInvalid )并不起作用,因为在FrameMove()函数内都m_bAdaptationInvalid设置成了true.也就是说每桢都调用CalculateAdaptation();我们来看看着个函数是如何实现上面说得那样的计算:

    HRESULT CMyD3DApplication::CalculateAdaptation()
    {
        HRESULT hr = S_OK;
        UINT uiPass, uiPassCount;

        // Swap current & last luminance
        PDIRECT3DTEXTURE9 pTexSwap = m_pTexAdaptedLuminanceLast;
        m_pTexAdaptedLuminanceLast = m_pTexAdaptedLuminanceCur;
        m_pTexAdaptedLuminanceCur = pTexSwap;

        这里可以看到每桢都交换了Cur和Last,将上桢的结果作为这一桢的输入参数

        m_pEffect->SetTechnique("CalculateAdaptedLum");
        m_pEffect->SetFloat("g_fElapsedTime", m_fElapsedTime);
   
        m_pd3dDevice->SetRenderTarget(0, pSurfAdaptedLum);
        m_pd3dDevice->SetTexture(0, m_pTexAdaptedLuminanceLast);
        m_pd3dDevice->SetTexture(1, m_apTexToneMap[0]);
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT );
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT );
        m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MAGFILTER, D3DTEXF_POINT );
        m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MINFILTER, D3DTEXF_POINT );

        这里m_apTexToneMap(0)就是我们的最终的目标亮度值,这里用到的shader的代码如下

        float4 CalculateAdaptedLum(in float2 vScreenPosition : TEXCOORD0) : COLOR
        {
            float fAdaptedLum = tex2D(s0, float2(0.5f, 0.5f));
            float fCurrentLum = tex2D(s1, float2(0.5f, 0.5f));
   
           // The user's adapted luminance level is simulated by closing the gap between
           // adapted luminance and current luminance by 2% every frame, based on a
           // 30 fps rate. This is not an accurate model of human adaptation, which can
           // take longer than half an hour.
           float fNewAdaptation = fAdaptedLum + (fCurrentLum - fAdaptedLum) *
                                  ( 1 - pow( 0.98f, 30 * g_fElapsedTime ) );
           return float4(fNewAdaptation, fNewAdaptation, fNewAdaptation, 1.0f);
        }

        这里就是使用上面说到的公式来计算我们当前的亮度值

    }

    通过上面的计算,我们在最后使用的亮度值保存在了m_pTexAdaptedLuminanceCur里面。

    接下来我们就准备开始获得Star和Bloom效果了.首先我们需要将场景中特别亮的像素给提取出来。

    SceneScaled_To_BrightPass();

    这个函数就是获得我们Star和Bloom所需要的来源贴图.我们来看看着个函数的内部实现

    HRESULT CMyD3DApplication::SceneScaled_To_BrightPass()
    {
        ....

        m_pEffect->SetTechnique("BrightPassFilter");

        m_pd3dDevice->SetRenderTarget( 0, pSurfBrightPass );
        m_pd3dDevice->SetTexture( 0, m_pTexSceneScaled );
        m_pd3dDevice->SetTexture( 1, m_pTexAdaptedLuminanceCur );
        m_pd3dDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, TRUE );
        m_pd3dDevice->SetScissorRect( &rectDest );
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT );
        m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT );
        m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MINFILTER, D3DTEXF_POINT );
        m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MAGFILTER, D3DTEXF_POINT );

        我们使用刚刚计算得出的m_pTexAdaptedLuminanceCur(如果跳过模拟过渡的阶段,这里应该使用m_apTexToneMap(0))和m_pTexSceneScaled作为我们的参数,计算后的只剩下我们需要的亮度信息保存pSurfBrightPass,我们来看看这里用到的shader的操作

        float4 BrightPassFilter(in float2 vScreenPosition : TEXCOORD0) : COLOR
        {
    float4 vSample = tex2D( s0, vScreenPosition );
    float  fAdaptedLum = tex2D( s1, float2(0.5f, 0.5f) );

    // Determine what the pixel's value will be after tone-mapping occurs
    vSample.rgb *= g_fMiddleGray/(fAdaptedLum + 0.001f);

    // Subtract out dark pixels
    vSample.rgb -= BRIGHT_PASS_THRESHOLD;

    // Clamp to 0
    vSample = max(vSample, 0.0f);

    // Map the resulting value into the 0 to 1 range. Higher values for
    // BRIGHT_PASS_OFFSET will isolate lights from illuminated scene
    // objects.
    vSample.rgb /= (BRIGHT_PASS_OFFSET+vSample);
   
    return vSample;
        }

        这里通过-BRIGHT_PASS_THRESHOLD和Clamp to 0我们就得到了只剩下我们需要的像素亮度值得Texture了

    }

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/pizi0475/archive/2010/04/07/5457475.aspx

你可能感兴趣的:(HDRLighting Sample)