HDR trouble

Started by
21 comments, last by VISQI 12 years, 10 months ago
Hey guys,
So, i read along many articles about HDR rendering and i just want to summarize what i understood about the pipeline:
- Render the scene to a texture
- apply a bright-pass filter and downsample the texture to half its size
- apply a Gaussian blur in two passes(through x-Axis and Y-Axis)
- additively blend the scene texture with the gaussian blur

I tried to do those steps but apparently i went wrong somewhere.
I did check out the DX SDK sample about HDRLighting but i really didn't understand much of it.

So, did i miss anything??

also, here is the fx file for the demo



//HDR rendering with bloom effect

struct Mtrl
{
float4 Diff;
float4 Amb;
float4 Spec;

float SpecPower;
};

struct DirLight
{
float4 Diff;
float4 Amb;
float4 Spec;

float3 DirW;
};

uniform extern float4x4 gMatWVP;
uniform extern float4x4 gMatITW;
uniform extern float4x4 gMatW;

uniform extern float3 gEyePosW;

uniform extern Mtrl gMtrl;
uniform extern DirLight gLight;

//Full Texture Technique
//======================================
struct vOut{
float4 PosH : POSITION0;
float2 TexC : TEXCOORD0;
float3 EyeVecW : TEXCOORD1;
float4 NormW: TEXCOORD2;
};

vOut FullVShader(float3 PosL : POSITION0,
float3 NormL : NORMAL0,
float2 TexC : TEXCOORD0)
{
vOut V = (vOut)0;

V.PosH = mul(float4(PosL, 1.0f), gMatWVP);
V.NormW = mul(float4(NormL, 0.0f), gMatITW);

V.TexC = TexC;

float3 PosW = mul(float4(PosL, 1.0f), gMatW);

V.EyeVecW = PosW - gEyePosW;

return V;

};

float4 FullPShader(float3 EyeVecW : TEXCOORD1,
float4 NormW: TEXCOORD2):COLOR
{
EyeVecW = normalize(EyeVecW);

float3 LightVecW = -gLight.DirW;
float3 RefLightVecW = reflect(-LightVecW, NormW);

float d = max(dot(LightVecW, NormW), 0.0f);
float s = pow(max(dot(RefLightVecW, EyeVecW), 0.0f), gMtrl.SpecPower);

float3 Diff = d * (gMtrl.Diff.rgb * gLight.Diff.rgb);
float3 Amb = (gMtrl.Amb * gLight.Amb);
float3 Spec = s * (gMtrl.Spec * gLight.Spec).rgb;

float3 FinalColor = Diff + Amb + Spec;

return float4(FinalColor, gMtrl.Diff.a);
}

Technique FullTech
{
Pass p0
{
VertexShader = compile vs_2_0 FullVShader();
PixelShader = compile ps_2_0 FullPShader();
}
}

//Bright-Pass Filter Tech
uniform extern texture FullTexture;

sampler Full_sTex = sampler_state
{
texture = <FullTexture>;
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = POINT;
AddressU = WRAP;
AddressV = WRAP;
};

void BP_VShader(float3 PosL : POSITION0,
float2 TexC : TEXCOORD0,
out float4 oPosH : POSITION0,
out float2 oTexC : TEXCOORD0)
{
oPosH = mul(float4(PosL, 1.0f),gMatWVP);
oTexC = TexC;

}

float4 BP_PShader(float2 TexC : TEXCOORD0):COLOR
{
float3 Color = tex2D(Full_sTex, TexC);

Color = Color - float4(0.3f,0.3f,0.3f, 0.0f);

return float4(Color,1.0f);
}

Technique BP_Tech
{
Pass p0
{
VertexShader = compile vs_2_0 BP_VShader();
PixelShader = compile ps_2_0 BP_PShader();
}
}

//Gaussian Blur Tech With 2 Passes. One for the X-Axis and the other for the Y-Axis

uniform extern texture BP_Texture;

sampler BP_sTex = sampler_state
{
texture = <BP_Texture>;
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = POINT;
AddressU = WRAP;
AddressV = WRAP;
};

static const float2 gBlurXOffset = float2(0.0025f, 0.0f);
static const int gBlurXKernalSize = 10;

static const float2 gBlurYOffset = float2(0.0f, 0.0033333f);
static const int gBlurYKernalSize = 10;

//Blur on The X-Axis
//========================================
void BlurX_VShader(float3 PosL: POSITION0,
float2 TexC: TEXCOORD0,
out float4 oPosH: POSITION0,
out float2 oTexC: TEXCOORD0)
{
oPosH = mul(float4(PosL, 1.0f), gMatWVP);
oTexC = TexC;
}

float4 BlurX_PShader(float2 TexC : TEXCOORD0):COLOR
{
float4 TexColor = tex2D(BP_sTex, TexC);

for(int i = 1; i < gBlurXKernalSize; i++)
{
TexColor += tex2D(BP_sTex, TexC + (i * gBlurXOffset));
TexColor += tex2D(BP_sTex, TexC - (i * gBlurXOffset));
}

return TexColor;
}

//Blur on The Y-Axis
//========================================
void BlurY_VShader(float3 PosL: POSITION0,
float2 TexC: TEXCOORD0,
out float4 oPosH: POSITION0,
out float2 oTexC: TEXCOORD0)
{
oPosH = mul(float4(PosL, 1.0f), gMatWVP);
oTexC = TexC;
}

float4 BlurY_PShader(float2 TexC : TEXCOORD0):COLOR
{
float4 TexColor = tex2D(BP_sTex, TexC);

for(int i = 1; i < gBlurYKernalSize; i++)
{
TexColor += tex2D(BP_sTex, TexC + (i * gBlurYOffset));
TexColor += tex2D(BP_sTex, TexC - (i * gBlurYOffset));
}

return TexColor;
}

Technique BlurTech
{
Pass p0
{
VertexShader = compile vs_2_0 BlurX_VShader();
PixelShader = compile ps_2_0 BlurX_PShader();
}

pass p1
{
VertexShader = compile vs_2_0 BlurY_VShader();
PixelShader = compile ps_2_0 BlurY_PShader();
}
}

//Final Tech
uniform extern texture BlurredTexture;

sampler Blur_sTex = sampler_state
{
texture = <BlurredTexture>;
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = POINT;
AddressU = CLAMP;
AddressV = CLAMP;
};

void FinalVShader(float3 PosL : POSITION0,
float2 TexC : TEXCOORD0,
out float4 oPosH : POSITION0,
out float2 oTexC : TEXCOORD0)
{
oPosH = mul(float4(PosL, 1.0f), gMatWVP);
oTexC = TexC;

}

float4 FinalPShader(float2 TexC : TEXCOORD0):COLOR
{
float4 Original = tex2D(Full_sTex, TexC);
float4 Blurred = tex2D(Blur_sTex, TexC);

float4 FinalColor = Original+Blurred;

return FinalColor;
}

technique FinalTech
{
pass p0
{
VertexShader = compile vs_2_0 FinalVShader();
PixelShader = compile ps_2_0 FinalPShader();
}
}


I checked PIX, and apparently only the first texture is getting drawn. From the Bright-pass till the end is blank.

I would really appreciate the help.
Advertisement
Yes you missed the most important part of HDR rendering.
You forgot to calculate luminance and to tonemap the final render target.Check this article

Imo, HDRLighting example from DXSDK is really easy to follow and produces good results. Try reading it carefully.

So the pipeline should be:

- Render the scene to a texture
-Transform the texture into a luminance texture with a smaller size than the original texture (example 256x256).
-Downsample the luminance texture multiple times until size is 1x1.
- apply a bright-pass filter and downsample the texture to half its size (also uses the luminance value in some calculations)
- apply a Gaussian blur in two passes(through x-Axis and Y-Axis)
- additively blend the scene texture with the gaussian blur
-Tonemap the final scene texture with the 1x1 luminance value.

Yes you missed the most important part of HDR rendering.
You forgot to calculate luminance and to tonemap the final render target.Check this article

Imo, HDRLighting example from DXSDK is really easy to follow and produces good results. Try reading it carefully.

So the pipeline should be:

- Render the scene to a texture
-Transform the texture into a luminance texture with a smaller size than the original texture (example 256x256).
-Downsample the luminance texture multiple times until size is 1x1.
- apply a bright-pass filter and downsample the texture to half its size (also uses the luminance value in some calculations)
- apply a Gaussian blur in two passes(through x-Axis and Y-Axis)
- additively blend the scene texture with the gaussian blur
-Tonemap the final scene texture with the 1x1 luminance value.


In the past I have performed the tonemap before the bright-pass. This has a couple of advantages that I can think of:
  • the bright pass doesn't need a changing cut off, just set it to 1.
  • more stages of the pipe-line can be done in rgb8 buffers.
Am I missing something?

Yes you missed the most important part of HDR rendering.
You forgot to calculate luminance and to tonemap the final render target.Check this article

Imo, HDRLighting example from DXSDK is really easy to follow and produces good results. Try reading it carefully.

So the pipeline should be:

- Render the scene to a texture
-Transform the texture into a luminance texture with a smaller size than the original texture (example 256x256).
-Downsample the luminance texture multiple times until size is 1x1.
- apply a bright-pass filter and downsample the texture to half its size (also uses the luminance value in some calculations)
- apply a Gaussian blur in two passes(through x-Axis and Y-Axis)
- additively blend the scene texture with the gaussian blur
-Tonemap the final scene texture with the 1x1 luminance value.


thanks for the input. I just get too frustrated with DX SDK samples framework sometimes.
i did modify the FX file a bit to include what SimonJacoby method.
He used a const value for luminance as 2.0f.
But in your method, i have a few questions:
1- In step 1, What do you mean by a luminance texture(do you mean just a floating-point texture)?
2- In step 2, how do you do that??
3- In step 3, by a bright pass filter, do you mean something as simple as Color = Color - float3(0.3f,0.3f,0.3); and that is it or is there something more to it??
4- In the final step, by Tonemapping, do you mean just add the Vignette and the luminance code together or is there something a bit more fancy??

Thanks

1- In step 1, What do you mean by a luminance texture(do you mean just a floating-point texture)?
2- In step 2, how do you do that??
3- In step 3, by a bright pass filter, do you mean something as simple as Color = Color - float3(0.3f,0.3f,0.3); and that is it or is there something more to it??
4- In the final step, by Tonemapping, do you mean just add the Vignette and the luminance code together or is there something a bit more fancy??


1. Luminance. So basically a single value per pixel that represents its brightness.

2. One method would be to first scale down the full texture to a fixed size (e.g 64x64), then draw the texture using a shader that averages 4 adjacent pixels to a single value until the result is a single pixel.
3. Basically yes. This is also the point at which I would recommend applying the exposure function. I would put the exposure and bright pass filter into the same function, writing the <=1 and >1 values to two separate buffers.
4. Not sure what vignette has to do with it. This is the stage where you accumulate the <1 buffer and the blurred >1 buffer for the final result.
I think it's important to keep in mind that HDR, bloom, tone mapping, and exposure are all very separate things (even if they are related). A lot of people just lump them into one process, and incorrectly assume that the way the DirectX or Nvidia samples are the only way to do it.

1. HDR just means you're using a range for lighting and rendering values that's wider than your displayable range.

2. Bloom is an effect meant to simulate glares that occur from bright objects. It doesn't require HDR, but you can do it better with HDR because you can determine which pixels are really really bright..

3. Exposure is just a scalar applied to your HDR values. Since you can't typically view really bright and really dark things simultaneously, adjusting exposure lets you pick whether you want to be able to see the bright stuff or the dark stuff. So if you're in a house you'd use a higher exposure, and then when you step outside to a bright sunny day you'd switch to a lower exposure.You can also implement "auto-exposure" if you want, where you try to choose a good exposure based on what's currently on the screen. Most of the samples use the Reinhard algorithm for this, which uses the geometric mean (log average) of pixel luminance combined with a key value.

4. Tone mapping is applying a curve to HDR pixel values, which is typically nonlinear. Tone mapping is basically like choosing film for a camera: certain tone mapping operators will let you display a wider range of brightnesses, and will do a better job of preserving contrast and detail. However it also affects the final "look", and consequently certain operators might be chosen due to artistic preference. Most of the samples use one of the Reinhard operators for this, and so do a lot of games. Reinhard tends to be good at preserving details, but can produce results that more washed out. "Filmic" operators (popularized by Naughty Dog) tend to preserve less detail, but give look that's more saturated with crushed blacks that's closer to the look movies usually go for.

In the past I have performed the tonemap before the bright-pass. This has a couple of advantages that I can think of:
  • the bright pass doesn't need a changing cut off, just set it to 1.
  • more stages of the pipe-line can be done in rgb8 buffers.
Am I missing something?


Usually you don't want to do that (if you can spare the performance) for the same reason you want to do any other post-process effect in HDR: if you don't do it in HDR, the bright spots won't be preserved and will become "washed out". For bloom this usually means all of your bloom blurs out to white, rather than to the actual color of the pixel.


3- In step 3, by a bright pass filter, do you mean something as simple as Color = Color - float3(0.3f,0.3f,0.3); and that is it or is there something more to it??



There's no real "right" way to do a threshold for bloom, since bloom is fundamentally a hack and not anything close to a real simulation of glare. So yeah, just subtracting some value (and clamping to 0 so that you don't get negative values) can work just fine.
all of this is really easy to understand, but from what i searched, the only good implementation of HDR with bloom effects and all the good stuff is DX SDK which i really have a hard time getting through with it. Is there any other source code that doesn't make me want to bang my head on the wall just for the sack of it??


And i have a few question on the HDR article in the D3DBOOK in the luminance transform section:
- In The implementation, why did he fetch it multiple times, shouldn't he calculate the luminance for EACH pixel one pixel at a time??
- Shouldn't he convert the pixels to CIE Yxy then calculate the luminance?? why did he do the opposite??
- And where is equation x??
- Is there a full implementation on how to get the average luminance based on the author's method??

Thanks
- Render the scene to a texture
- apply a bright-pass filter and downsample the texture to half its size
- apply a Gaussian blur in two passes(through x-Axis and Y-Axis)
- additively blend the scene texture with the gaussian blur
As mentioned above, this is bloom, not HDR.

This topic is closed to new replies.

Advertisement