Sign in to follow this  

Jolly's HDR Sample (Need Help) [Solved]

This topic is 4109 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

After much searching, I just don't think I'm getting something, I feel stupid, because soo many people posted and seem to understand whats going on, but I don't get it. I'm basically making my engine so its capable of doing this, so I really need to learn the steps, not just copy code :). So, lets start with step 1 (Bright Pass & Downsample). I want to basically apply that post process and then once thats correct, move on. But looking at the example program and a few other programs, I'm not quite sure I understand, I know the bright pass is to select the pixels I want to sorda "bloom" I have seen this two ways, please tell me if I'm correct and which is the better: So, it seams I can either a) Get everything in the scene at 0.8+ (rgb) value or b) apply *5.0 factor to rgb and get 1.0+ (rgb) value and black the other pixels, this will end up with my bloom texture. Is this correct? I also don't understand the whole scale down 2x2, here is the offsets:
// Find the dimensions for the source data
D3DSURFACE_DESC srcDesc;
pHDRSource->GetLevelDesc( 0, &srcDesc );

// Find the dimensions for the destination data
D3DSURFACE_DESC destDesc;
pBrightPassSurf->GetDesc( &destDesc );

// Compute the scalar
float sU = (static_cast< float >( srcDesc.Width ) 
			/ static_cast< float >( destDesc.Width ) 
			/ 2.0f)
			* ( 1.0f / static_cast< float >( srcDesc.Width ) );

float sV = (static_cast< float >( srcDesc.Height ) 
			/ static_cast< float >( destDesc.Height ) 
			/ 2.0f)
			* ( 1.0f / static_cast< float >( srcDesc.Height ) );

offsets[0] = D3DXVECTOR4(	
							-0.5f * sU,		-0.5f * sV,
							 0.5f * sU,		-0.5f * sV 
						);
offsets[1] = D3DXVECTOR4(	
							-0.5f * sU,		0.5f * sV,
							 0.5f * sU,		0.5f * sV 
						);

PostProcess::g_pBrightPassConstants->SetVectorArray( pDevice, "tcDownSampleOffsets", offsets, 2 );




But how did you end up with srcwidth/destwidth/2?? Is dest width the 2x2 downsample? So if I had a 800x600 screen, it'd be 400x300?, what are the offsets used for? I took a look at the shader, but im really confused. Jeff. PS I understand the concepts and steps for this process, I'm just not getting the nuts and bolts. [Edited by - webjeff on September 18, 2006 1:28:28 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by webjeff
But looking at the example program and a few other programs, I'm not quite sure I understand, I know the bright pass is to select the pixels I want to sorda "bloom" I have seen this two ways, please tell me if I'm correct and which is the better:

So, it seams I can either
a) Get everything in the scene at 0.8+ (rgb) value
or
b) apply *5.0 factor to rgb and get 1.0+ (rgb) value
and black the other pixels, this will end up with my bloom texture.

Is this correct?

No. The bright-pass selects bright pixels based on a threshold. You can hard-code the threshold (e.g. 0.8) or make it a shader variable to ease tweaking. I recommend making it tweakable, since you'll need a lot of tweaking to get things looking good.

As for the multiplication by 5, I seem to recall Jack (and other samples as well) were doing that just to produce values outside the range [0, 1], because that's the point of HDR.

These are the functions responsible for the bright-pass from the MDX port I wrote:

/// <summary>
/// Performs the first pass, the bright-pass. This pass down-samples the HDR scene
/// into a texture that is half the size in each dimension, blacking out pixels whose luminance is
/// less than the bright pass threshold.
/// </summary>
/// <param name="hdrTexture">The source HDR texture.</param>
void DoBrightPass(Texture hdrTexture)
{
device.SetTexture(0, hdrTexture);
device.PixelShader = brightPassPs;
brightPassPsConstants.SetValue(device, "brightPassThreshold", brightPassThreshold);

using(Surface destSurface = brightPassTexture.GetSurfaceLevel(0))
{
device.SetRenderTarget(0, destSurface);

SurfaceDescription destDesc = destSurface.Description;
Vector4[] offsets = SamplingHelper.Get2x2Offsets(destDesc.Width, destDesc.Height);
brightPassPsConstants.SetValue(device, "brightPassOffsets", offsets);

RenderToTexture(destDesc.Width, destDesc.Height);
}
}


And the shader function:

/*
Function: BrightPass
Bright-pass and 2x2 down-sampling shader.

Description:
Performs the 2x2 down sample, and then accepts any pixels that are greater or equal to the
configured threshold.
*/

float4 BrightPass( in float2 t : TEXCOORD0 ) : COLOR
{
float3 luminanceVector = { 0.2125f, 0.7154f, 0.0721f };
float4 average = { 0.0f, 0.0f, 0.0f, 0.0f };

// load in and combine the 4 samples from the source HDR texture
for( int i = 0; i < 4; i++ )
average += tex2D( originalSceneSampler, t + brightPassOffsets[i] );

average *= 0.25f;

// Determine the luminance of this particular pixel
float luminance = dot(average.rgb, luminanceVector);

// If this pixel satisfies the bright pass threshold it will
// be > 0.0 after this operation
luminance = max( 0.0f, luminance - brightPassThreshold );

// Muliply by the above predicate, use the sign() intrinsic
// to make sure that the result is either 0.0 or 1.0, thus not
// scaling any bright pixels that are needed.
average.rgb *= sign( luminance );

// Write the colour to the bright-pass render target
average.a = 1.0f;
return average;
}


Quote:

I also don't understand the whole scale down 2x2, here is the offsets:
*** Source Snippet Removed ***

But how did you end up with srcwidth/destwidth/2?? Is dest width the 2x2 downsample? So if I had a 800x600 screen, it'd be 400x300?, what are the offsets used for? I took a look at the shader, but im really confused.

Jack, IIRC, used the offsets from one of the two Microsoft samples--HDR-Formats or HDRLighting. Each one of these uses different offsets. I seem to recall discussing this with Jack back then, but I don't remember what we concluded. What I did in the end, for the MDX port, was come up with factors that made sense to me. I ended up using this:

/// <summary>
/// Generates 4 offsets for a 2x2 sampling pattern.
/// </summary>
/// <param name="destTextureWidth">Width of the destination texture.</param>
/// <param name="destTextureHeight">Height of the destination texture.</param>
/// <returns>An array of Vector4's containing the sampling offsets in their X and Y fields.</returns>
public static Vector4[] Get2x2Offsets(int destTextureWidth, int destTextureHeight)
{
float tu = 1.0f/destTextureWidth;
float tv = 1.0f/destTextureHeight;

Vector4[] offsets = new Vector4[4];
int index = 0;
for(int x = 0; x < 2; x++)
{
for(int y = 0; y < 2; y++)
{
offsets[index].X = (x - 0.5f) * tu;
offsets[index].Y = (y - 0.5f) * tv;

index++;
}
}
/*
* The above is equivalent to:
* offsets[0].X = -0.5f * tu;
* offsets[0].Y = -0.5f * tv;
*
* offsets[1].X = -0.5f * tu;
* offsets[1].Y = 0.5f * tv;
*
* offsets[2].X = 0.5f * tu;
* offsets[2].Y = -0.5f * tv;
*
* offsets[3].X = 0.5f * tu;
* offsets[3].Y = 0.5f * tv;
*/


return offsets;
}

Share this post


Link to post
Share on other sites
Just a quick word of warning, the latest version of my code can be found in the DirectX SDK - The Dec'05 build wasn't the latest, but it was updated from Feb'06 onwards (iirc). HDRPipeline Sample.

I made lots of little changes here-n-there to the code between the time I posted it on these forums and the time it got into the SDK. I'd highly recommend using the latest version.

Quote:
So if I had a 800x600 screen, it'd be 400x300?, what are the offsets used for? I took a look at the shader, but im really confused.
It is actually quite simple if you draw it out on paper. Here comes the ASCII art [cool]:


+---+---+ +-------+
| | | | |
+---+---+ ----> | |
| | | | |
+---+---+ +-------+


In the above diagram we have the source (left) which is 2x2 and the destination which is 1x1. Given that we know Direct3D defines the sampling point as the centre of the texel (this changes with D3D10 though [wink]) when the pixel shader is invoked on the destination pixel the coordinate used will map to the middle "+" of the source image. Bang in the middle of all 4 texels. Most hardware doesn't support linear filtering of HDRI data so we'd end up with a slightly confused point sampling of just 1 of those 4.

So, what the application does is generate four offsets, each half of a pixel away (we can use the height/width to determine the size of a pixel - remember they aren't always square [wink]). The pixel shader then gets the input coordinate (interpolated through from the VS) and says "I want to take a sample from the four surrounding pixels, so if I offset them based on these coordinates I'll be able to get them".

By using the correct offsets we can correctly retrieve the four pixel values from the source image. We can then do the averaging/min/max filter and write out the single result.

In mathematical terms its just an example of mapping from many-to-one [smile]

Quote:
I recommend making it tweakable, since you'll need a lot of tweaking to get things looking good.
Just to add to this, you may want to make everything tweakable. One of the more difficult parts of HDRI rendering is getting it balanced - I have no idea how many hours I spent changing nothing but the constants/variables and trying however many bazillion combinations!

Once you've got the basic process implemented the rest is largely up to you - there isn't really a single right way of doing things. Which is part of why it can be hard to learn from samples - they're all mostly the same but each one has a slightly different approach.

Quote:
As for the multiplication by 5, I seem to recall Jack (and other samples as well) were doing that just to produce values outside the range [0, 1], because that's the point of HDR.
This is correct - just a cheap and easy way of generating some HDRI data to play with [smile]

Quote:
Each one of these uses different offsets. I seem to recall discussing this with Jack back then, but I don't remember what we concluded.
I think the conclusion was that I was wrong and you were right [lol] I think (but not 100% sure) the offsets were one of the things I changed between the forum post and the final SDK build. There were a few slightly incorrect equations in earlier builds [embarrass]

Share this post


Link to post
Share on other sites
Hi,

I'll just join this thread instead of creating a new one :)

I've got a question about generating gaussian weights in jollyjeffers' demo. You've got the following code in your sample:

// 'x' is just a simple alias to map the [0,8] range down to a [-1,+1]
float x = (static_cast< float >( i ) - 4.0f) / 4.0f;


"x" is then passed to a function that calculates gaussian distribution. Why do you map x to [-1, +1] and not [-(N/2-1), +(N/2-1)], where N is the size of the kernel? It gives very good results (when used for blur), but changing kernel size won't change anything - i.e. values at the edges of 3x3 kernel would be the same as in 11x11 kernel as x is always mapped to [-1, +1] and doesn't depend on kernel size.

Is there any reason to do it, or it's just that way because it looks good?

Share this post


Link to post
Share on other sites
I can't remember off the top of my head, but there is some mathematical reasoning behind the [-1,+1] mapping.

But you're right - I wrote it the way it is simply because it looked good [smile]

Having said that, I was swayed by the fact that everyone else uses gaussian distributions (especially in the more general computer graphics research papers), but I did experiment with other distributions. I didn't particularly like the way that gaussian tends towards zero weights at the edge - it almost makes the extreme samples a bit pointless as they'll have a minimal impact on the final image.

For a bit of fun try replacing the gaussian with a sine wave instead - not very realistic but it struck me as being quite a cool effect [grin]

Jack

Share this post


Link to post
Share on other sites
Thanks guys, great explanations from both of you guys. I think I can finally get past that part, knowing how it works exactly. I also wanted to note incase anyone else reads this post, I thought the 2x2 ascii art was of the full image and the 1x1 was a representation of the downsampled image... but after reviewing the post with a friend, we realized it was of 2x2 pixels and a resultant 1x1 pixel from that.

Once I figured that out, it was a piece of cake just like you said :).

Thanks again, ill let you guys know if I run into any other problems working further into the demo.

J.

Share this post


Link to post
Share on other sites

This topic is 4109 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this