我正在尝试在我的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)在哪里?什么是最大值?
提前全部谢谢。
答案 0 :(得分:1)
我已经解决了这个问题。
我相信使用十几个renderTargets,每一步的分辨率只有一半,但是我错了。
在2013年的中端GPU上,nVidia GTX 560,重新渲染10个渲染目标的成本并不明显,具体数字:从230 FPS下降至220 FPS。
解决方案如下。暗示你已经将整个场景处理并渲染到renderTarget,在我的例子中是“renderOutput”。
首先,我声明一个renderTarget数组:
public RenderTarget2D[] HDRsampling;
接下来,我计算在Load()方法中需要多少个目标,在Menu更新和游戏更新循环之间调用(用于加载菜单中不需要的游戏资源的过渡状态),并正确初始化它们: / p>
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();
}
}
你如何实现你的场景平均光度取决于你,我还在尝试所有这些,但我希望这有助于那里的人!
答案 1 :(得分:0)
您链接的教程很好地演示了如何进行Tonemapping,但听起来您还没有执行重复下采样来获取1x1图像。
您不能简单地拍摄高分辨率图像(例如1920x1080),取16个任意像素,将它们相加并除以16并称之为亮度。
您需要反复将源图像下采样为越来越小的纹理,通常每个维度的每个维度减半。所得到的下采样的每个像素是先前纹理上的2×2像素网格的平均值(这由双线性采样处理)。最终,您将获得1x1图像,该图像是整个原始1920x1080图像的平均颜色值,您可以从中计算源图像的平均亮度。
如果没有重复的下采样,您的亮度计算将是一个非常嘈杂的值,因为它的输入仅仅是原始图像中~2M像素中的16个。为了获得正确和平滑的亮度,原始图像中的每个像素都需要贡献。