【问题标题】:XNA and HLSL: Sample entire texture for luminosity averageXNA 和 HLSL:对整个纹理进行采样以获得平均亮度
【发布时间】:2013-12-22 05:30:09
【问题描述】:

我正在尝试在我的 3D 应用程序中进行 HDR 渲染。

我知道,为了获得场景中的平均光线,您需要将渲染输出下采样到 1x1 纹理。这就是我目前正在努力的地方。

我已经设置了一个 1x1 分辨率的渲染目标,我将在该目标上绘制上一个渲染输出。我尝试使用简单的 SpriteBatch Draw 调用并使用目标矩形将输出绘制到该渲染目标。然而,希望我能发现别人没有想到的东西实在是太希望了,结果实际上并不是整个场景被下采样到 1x1 纹理,看起来好像只绘制了左上角的像素,不管我有多少使用目标矩形或比例重载播放。

现在,我正在尝试进行另一个四屏渲染通道,使用着色器技术对场景进行采样,并将单个像素渲染到渲染目标中。所以公平地说,标题有点误导,我想做的是对均匀分布在表面上的像素网格进行采样,然后将它们平均出来。但这就是我难过的地方。

我遇到了这个教程: http://www.xnainfo.com/content.php?content=28

在可以下载的文件中,有几个下采样示例,我最喜欢的一个是使用循环遍历 16 像素,将它们平均并返回。

到目前为止,我所做的任何事情都无法产生可行的输出。出于调试目的,下采样的纹理被渲染到我的屏幕一角。

我已将 HLSL 代码修改为如下所示:

pixelShaderStruct vertShader(vertexShaderStruct input)
{
    pixelShaderStruct output;

    output.position = float4(input.pos, 1);
    output.texCoord = input.texCoord + 0.5f;

    return output;
};

float4 PixelShaderFunction(pixelShaderStruct input) : COLOR0
{
   float4 color = 0;
   float2 position = input.texCoord;

   for(int x = 0; x < 4; x++)
   {
        for (int y = 0; y < 4; y++)
        {
            color += tex2D(getscene, position + float2(offsets[x], offsets[y]));
        }
   }

   color /= 16;

   return color;
}

这里的这一行是我认为我犯了错误的地方:

color += tex2D(getscene, position + float2(offsets[x], offsets[y]));

我从未正确理解 tex2D 采样中使用的 texCoord 值是如何工作的。 在制作运动模糊效果时,为了产生正常的外观效果,我不得不转发这样的无穷大值,我担心它会被四舍五入到零,而其他时候,需要转发像 30 和 50 这样的大值来产生可能占据屏幕三分之一的效果。

不管怎样,我的问题是:

给定一个屏幕四边形(因此,平面),我如何增加或修改 texCoord 值以生成一个像素网格,均匀分布并在其上采样?

我尝试过使用:

color += tex2D(getscene, position + float2(offsets[x] * (1/maxX), offsets[y] * (1/maxY)));

其中 maxX 和 maxY 是屏幕分辨率,

color += tex2D(getscene, position + float2(offsets[x] * x, offsets[y] * y));

...和其他“黑暗中的镜头”,所有结果最终都相同:最终生成的像素似乎与我屏幕正中间的像素相同,好像这是唯一的像素正在采样。

如何解决?

另外,这些纹理坐标是如何工作的? (0,0) 在哪里?最大值是多少?

提前谢谢大家。

【问题讨论】:

    标签: c# xna hlsl


    【解决方案1】:

    我已经解决了这个问题。

    我认为使用十几个每一步分辨率一半的渲染目标会很昂贵,但我错了。

    在 2013 年的中端 GPU 上,nVidia GTX 560,重新渲染到 10 个渲染目标的成本并不明显,具体数字:从 230 FPS 性能下降到大约 220 FPS。

    解决方案如下。这意味着您已经将整个场景处理并渲染到了 renderTarget,在我的例子中是“renderOutput”。

    首先,我声明一个 renderTarget 数组:

    public RenderTarget2D[] HDRsampling;
    

    接下来,我计算在我的 Load() 方法中需要多少个目标,该方法在菜单更新和游戏更新循环之间调用(用于加载菜单中不需要的游戏资产的转换状态),并正确初始化它们:

        int counter = 0;
        int downX = Game1.maxX;
        int downY = Game1.maxY;
    
        do
        {
            downX /= 2;
            downY /= 2;
            counter++;
    
        } while (downX > 1 && downY > 1);
    
        HDRsampling = new RenderTarget2D[counter];
        downX = Game1.maxX / 2;
        downY = Game1.maxY / 2;
    
        for (int i = 0; i < counter; i++)
        {
            HDRsampling[i] = new RenderTarget2D(Game1.graphics.GraphicsDevice, downX, downY);
            downX /= 2;
            downY /= 2;
        }
    

    最后,C#渲染代码如下:

            if (settings.HDRpass)
            {   //HDR Rendering passes
                //Uses Hardware bilinear downsampling method to obtain 1x1 texture as scene average
    
                Game1.graphics.GraphicsDevice.SetRenderTarget(HDRsampling[0]);
                Game1.graphics.GraphicsDevice.Clear(ClearOptions.Target, Color.Black, 0, 0);
                downsampler.Parameters["maxX"].SetValue(HDRsampling[0].Width);
                downsampler.Parameters["maxY"].SetValue(HDRsampling[0].Height);
                downsampler.Parameters["scene"].SetValue(renderOutput);
                downsampler.CurrentTechnique.Passes[0].Apply();
                quad.Render();
    
                for (int i = 1; i < HDRsampling.Length; i++)
                {   //Downsample the scene texture repeadetly until last HDRSampling target, which should be 1x1 pixel
                    Game1.graphics.GraphicsDevice.SetRenderTarget(HDRsampling[i]);
                    Game1.graphics.GraphicsDevice.Clear(ClearOptions.Target, Color.Black, 0, 0);
                    downsampler.Parameters["maxX"].SetValue(HDRsampling[i].Width);
                    downsampler.Parameters["maxY"].SetValue(HDRsampling[i].Height);
                    downsampler.Parameters["scene"].SetValue(HDRsampling[i-1]);
                    downsampler.CurrentTechnique.Passes[0].Apply();
                    quad.Render();
    
                }
                //assign the 1x1 pixel
                downsample1x1 = HDRsampling[HDRsampling.Length - 1];
    
                Game1.graphics.GraphicsDevice.SetRenderTarget(extract);
                //switch out rendertarget so we can send the 1x1 sample to the shader.
                bloom.Parameters["downSample1x1"].SetValue(downsample1x1);
    
            }
    

    这获得了downSample1x1纹理,稍后在最终着色器的最终通道中使用。

    实际下采样的着色器代码非常简单:

    texture2D scene;
    
    sampler getscene = sampler_state
    {
        texture = <scene>;
        MinFilter = linear;
        MagFilter = linear;
        MipFilter = point;
        MaxAnisotropy = 1;
        AddressU = CLAMP;
        AddressV = CLAMP;
    };
    
    float maxX, maxY;
    
    struct vertexShaderStruct
    {
        float3 pos      : POSITION0;
        float2 texCoord : TEXCOORD0;
    };
    
    struct pixelShaderStruct
    {
        float4 position : POSITION0;
        float2 texCoord : TEXCOORD0;
    };
    
    pixelShaderStruct vertShader(vertexShaderStruct input)
    {
        pixelShaderStruct output;
    
        float2 offset = float2 (0.5 / maxX, 0.5/maxY);
        output.position = float4(input.pos, 1);
        output.texCoord = input.texCoord + offset;
    
        return output;
    };
    
    float4 PixelShaderFunction(pixelShaderStruct input) : COLOR0
    {
       return tex2D(getscene, input.texCoord);
    }
    
    technique Sample
    {
        pass P1
        {
            VertexShader = compile vs_2_0 vertShader();
            PixelShader = compile ps_2_0 PixelShaderFunction();
        }
    }
    

    如何实现场景平均亮度取决于您,我仍在尝试所有这些,但我希望这对那里的人有所帮助!

    【讨论】:

      【解决方案2】:

      您链接的教程很好地演示了如何进行色调映射,但在我看来,您没有执行获得 1x1 图像所需的重复下采样。

      您不能简单地将高分辨率图像(例如 1920x1080)拍摄 16 个任意像素,将它们相加并除以 16 并称之为亮度。

      您需要反复将源图像下采样为越来越小的纹理,通常在每个阶段的每个维度上减半。生成的下采样的每个像素是前一个纹理上 2x2 像素网格的平均值(这由双线性采样处理)。最终,您将得到一个 1x1 图像,它是整个 1920x1080 原始图像的平均颜色值,您可以从中计算源图像的平均亮度。

      如果不重复下采样,您的亮度计算将是一个非常嘈杂的值,因为它的输入只是原始图像中约 200 万像素中的 16 个。为了获得正确和平滑的亮度,原始图像中的每个像素都需要做出贡献。

      【讨论】:

      • 这是有道理的,事实上我正在考虑这样的理想解决方案,但是一旦我解决了实际的采样问题。你认为我应该如何解决这个问题?
      猜你喜欢
      • 1970-01-01
      • 2011-06-19
      • 1970-01-01
      • 2019-07-12
      • 1970-01-01
      • 1970-01-01
      • 2012-06-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多