ebruneton / precomputed_atmospheric_scattering

This project provides a new implementation of our EGSR 2008 paper "Precomputed Atmospheric Scattering".
BSD 3-Clause "New" or "Revised" License
908 stars 120 forks source link

Transmittance texture looks wrong... #24

Closed LocalStarlight closed 6 years ago

LocalStarlight commented 6 years ago

(Sorry, me again.)

I'm trying to build a version of this code which I can use in the material editor of Unreal Engine. For that, I need to actually output the textures so that I can use them there. So I'm going through the code trying to strip all the GL stuff away so that I can essentially just have pure code for generating the textures. This is proving more complicated than I hoped.

Before I even get to the issue of the 3D textures (since UE4 can use them internally, but not in the material editor), I am just trying to output the 2D transmittance texture to see if everything is working how I would like.

Essentially what I'm doing is using the code in the demo scene to create an instance of the Model, which then uses the functions in the Model (the lambdas for wavelength functions and creation of density profiles) along with the code in the glsl_headerfactory function to create an AtmosphereParameters struct, which I am then passing to the functions for computing transmittance.

The only alteration that I've made to the transmittance functions is that I have had to change ComputeTransmittanceToTopAtmosphereBoundaryTexture since I am not using gl. I have modified it so that it loops through all the pixels (from {0,0} to {255, 63}), gets the relevant UV value, and then uses GetRMuFromTransmittanceTextureUv followed by ComputeTransmittanceToTopAtmosphereBoundary to get the transmittance value. I am then writing this value to a 1-dimensional array of vector3 (actually FLinearColor in UE4) values.

Inside UE4, I am using a render canvas object to write these pixels and save them out to an HDR.

The pixels are written left to right, top to bottom, from the top left corner.

Here is the result: https://i.imgur.com/ZuTmedI.jpg

And here is a download link: TransmittanceTexture4.zip

This looks nothing like the examples I have seen elsewhere, for example: https://www.gamedev.net/forums/topic/647768-pre-computed-atmospheric-scattering-transmittance-table/ And the example on page 54 of this masters' thesis: http://www.sperlhofer.com/images/stories/atmospheric/thesis-sperlhofer.pdf

As well as looking totally different, there are two pixels in the bottom-left corner which are totally black, which I'm sure is an error?

But I've been going over and over this for days now, and I can't figure out what I'm doing wrong. I realise that this might not be possible to diagnose without seeing the code (which I'm happy to share), but I wondered whether it's possible to say what might have happened here just from seeing this image?

In case it helps to figure out where the problem is, here is a dump of the values in the AtmosphereParameters struct which is being passed for the transmittance calculations:

    solar_irradiance    {X=1.47399998 Y=1.85039997 Z=1.91198003 }
    sun_angular_radius  0.00467499997
    bottom_radius       63600.0000
    top_radius      64200.0000
    rayleigh_density
        layers
            [0]
            width       0.00000000000000000
            exp_term    1.0000000000000000
            exp_scale   -0.012500000000000001
            linear_term 0.00000000000000000
            constant_term   0.00000000000000000

            [1]
            width       0.00000000000000000
            exp_term    1.0000000000000000
            exp_scale   -0.012500000000000001
            linear_term 0.00000000000000000
            constant_term   0.00000000000000000
    rayleigh_scattering {X=0.000580233929 Y=0.00135577621 Z=0.00331000052 }
    mie_density
        layers
            [0]
            width       0.00000000000000000
            exp_term    1.0000000000000000
            exp_scale   -0.083333333333333343
            linear_term 0.00000000000000000
            constant_term   0.00000000000000000

            [1]
            width       0.00000000000000000
            exp_term    1.0000000000000000
            exp_scale   -0.083333333333333343
            linear_term 0.00000000000000000
            constant_term   0.00000000000000000

    mie_scattering  {X=0.000399599987 Y=0.000399599987 Z=0.000399599987 }
    mie_extinction  {X=0.000444000005 Y=0.000444000005 Z=0.000444000005 }
    mie_phase_function_g    0.800000012
    absorption_density
        layers
            [0]
            width       250.00000000000000
            exp_term    0.00000000000000000
            exp_scale   0.00000000000000000
            linear_term 0.0066666666666666671
            constant_term   -0.66666666666666663

            [1]
            width       0.00000000000000000
            exp_term    0.00000000000000000
            exp_scale   0.00000000000000000
            linear_term -0.0066666666666666671
            constant_term   2.6666666666666665
    absorption_extinction   {X=6.49716603e-05 Y=0.000188089994 Z=8.50166816e-06 }
    ground_albedo   {X=0.100000001 Y=0.100000001 Z=0.100000001 }
    mu_s_min    1.78023577

Does any of that look wrong? If so, then I'm messing things up in the early stages, if not them I'm messing things up in the transmittance computation.

I really appreciate any help with this, I'm pulling my hair out over here!

tobspr commented 6 years ago

The transmittance textures looks right to me. I recommend you compile the demo yourself and write out the textures, so you have a reference (This is actually this first thing I did).

The black pixels come from high floating point values I believe. I had the same issue when writing the texture to disk if I remember correctly,

In case it helps, here are the textures generated by the demo (The alpha channel is 0, so your image viewer probably won't display it correctly):

ScatteringResults.zip

ebruneton commented 6 years ago

FYI, a port of this code was made for Unity: https://github.com/Scrawk/Brunetons-Improved-Atmospheric-Scattering. I don't know if this can help or not.

Also, the default atmosphere in Unreal is based on precomputed textures (based on my previous implementation at http://evasion.inrialpes.fr/~Eric.Bruneton/PrecomputedAtmosphericScattering2.zip, not on this one). See https://docs.unrealengine.com/en-us/Engine/Actors/FogEffects/AtmosphericFog and the Unreal source code on github.

LocalStarlight commented 6 years ago

Thanks for the link for the Unity version. Unfortunately UE4 and Unity require slightly different implementations it seems, so I"m continuing to work on the UE4 version.

I've been looking into why those two pixels at the bottoms left of my transmittance texture are returning strange values. I looked into whether it's just high floating point values, but I tried just clamping them and it made no difference. It actually seems that GetRMuFromTransmittanceTextureUv is returning odd values for mu on those pixels.

Here's a dump of the values during GetRMuFromTransmittanceTextureUv at pixel [0,61], which is the first pixel of the row above the two problematic pixels, where the output seems correct, and pixels [0,62] and [0,63], the incorrect ones:

XY: 0, 61 UV: 0.000000, 0.953125 x_mu: -0.001961 | x_r: 0.960317 H: 8756.708008 rho: 8409.219727 r: 64153.527344 d_min: 46.472656 d_max: 17165.927734 d: 12.905094 r: 64153.527344 | mu: 1.000000

XY: 0, 62 UV: 0.000000, 0.968750 x_mu: -0.001961 | x_r: 0.976190 H: 8756.708008 rho: 8548.214844 r: 64171.894531 d_min: 28.105469 d_max: 17304.921875 d: -5.770645 r: 64171.894531 | mu: -1.000000

XY: 0, 63 UV: 0.000000, 0.984375 x_mu: -0.001961 | x_r: 0.992063 H: 8756.708008 rho: 8687.209961 r: 64190.554688 d_min: 9.445313 d_max: 17443.917969 d: -24.739929 r: 64190.554688 | mu: -0.381503

It seems odd that d should return a negative value, which seems related to the negative value of x_mu at these positions. I'm not sure I totally understand x_r and x_mu, are they supposed to be getting values off the edge of the UV range so that the edge values are correct?

But since it seems that since we want to always start with a mu of 1.0f, there's only a simple change needed to fix this. I changed this line:

mu = d == 0.0f * m ? 1.0f : (H * H - rho * rho - d * d) / (2.0f * r * d);

to:

mu = d <= 0.0f * m ? 1.0f : (H * H - rho * rho - d * d) / (2.0f * r * d);

So that when d is a negative number, mu returns 1.0f.

I'm not sure why this would be an issue in my code, and not the original though! But anyway, it fixed those black pixels in my transmittance texture, and everything else looks the same.

ebruneton commented 6 years ago

Your issue might be due to the units used. From your value bottom_radius = 63600.0 it seems you are using a length unit of 100m, whereas the demo uses a length unit of 1km (and therefore bottom_radius = 6360.0 km). See https://github.com/ebruneton/precomputed_atmospheric_scattering/issues/21.

A dump of the transmittance texture computed for the demo, done with

  float* pixels =
      new float[TRANSMITTANCE_TEXTURE_WIDTH * TRANSMITTANCE_TEXTURE_HEIGHT * 4];
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, transmittance_texture_);
  glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, pixels);
  std::ofstream output_stream("transmittance.txt");
  for (int j = 0, k = 0; j < TRANSMITTANCE_TEXTURE_HEIGHT; ++j) {
    for (int i = 0; i < TRANSMITTANCE_TEXTURE_WIDTH; ++i) {
      output_stream
          << i << " " << j << ": "
          << pixels[k++] << " "
          << pixels[k++] << " "
          << pixels[k++] << " "
          << pixels[k++] << "\n";
    }
  }
  output_stream.close();

added in model.cc (at the end of Precompute()) gives no black pixel: transmittance.txt.zip

LocalStarlight commented 6 years ago

You're right that I had a mistake in there, I had a value of 100.0 not 1000.0 for kLengthUnitInMeters.

However, that doesn't seem to make a difference. I still get those two black pixels.

Here is a dump of the values during GetRMuFromTransmittanceTextureUv for the first pixel of the last three lines of the image:

Pixel: 0, 61
x_mu: -0.001961    x_r: 0.960317
H: 875.671143    rho: 840.922241
d_min: 4.646973    d_max: 1716.593384
d: 1.290215
U: 0.000000    V: 0.953125
R: 6415.353027    MU: 1.000000

Pixel: 0, 62
x_mu: -0.001961    x_r: 0.976190
H: 875.671143    rho: 854.821777
d_min: 2.810547    d_max: 1730.492920
d: -0.577066
U: 0.000000    V: 0.968750
R: 6417.189453    MU: -1.000000

Pixel: 0, 63
x_mu: -0.001961    x_r: 0.992063
H: 875.671143    rho: 868.721375
d_min: 0.944336    d_max: 1744.392578
d: -2.474190
U: 0.000000    V: 0.984375    R: 6419.055664    MU: -0.381471

My (very limited) understanding of what's meant to happen is that at each altitude (r) moving through the UV values should give a spread of mu values starting from pointing straight up (mu == 1.0f) and gradually rotating downwards to the horizon intersection point. For every starting pixel for each altitude except the final two rows, this is the case, they start from mu == 1.0f. But somehow these last two are getting messed up in this line:

float d = d_min + x_mu * (d_max - d_min);

For example, for the final line, this gives the following value:

d = 0.944336 + -0.001961 * (1744.392578 - 0.944336) d = 0.944336 + -3.4189 d = -2.474190

Which I find very confusing, because surely there should never be a negative value for d? And I'm even more confused because I've copy-pasted your code. I've had to change certain things since I'm outputting values to an array and then writing pixels in an image buffer in UE4, but I haven't changed the core calculations in any of these functions. Nor could I, since I confess as hard as I'm trying, I don't totally understand everything going on here!

And I've got further issues coming up now that I've moved on to the single scattering calculations. Will post more about that later. But I feel like there's a key problem here if I'm ending up with different values than the original code.

I'm sure if I could build the demo, it would help, but having problems getting that to work too. Will post that as a separate issue.

ebruneton commented 6 years ago

I see that you have negative x_mu values, this should not happen. If you are using GetRMuFromTransmittanceTextureUv on CPU (which is not a good idea, because it is way slower than on GPU), then you need to replicate the OpenGL semantics for fragment coordinates, namely that they are at pixel centers (https://www.opengl.org/sdk/docs/manglsl/docbook4/xhtml/gl_FragCoord.xml). So for pixel (0,0) of a texture, the fragment coordinate is (0.5,0.5). See https://github.com/ebruneton/precomputed_atmospheric_scattering/blob/master/atmosphere/reference/model.cc#L144

LocalStarlight commented 6 years ago

I've been having so many problems trying to get this to work! But, thanks to the code you posted above (for outputting the pixel values to a text file) I've realised what I was trying to do was totally unnecessary. I've adapted that code to output the irradiance and scattering pixel values as well. I've then used UE4 to output these as textures. Since UE4 can't use 3D textures in the material editor, I've had to create the scattering texture as a 2D texture (256 x 4096) and adapt the way the UV values are extracted. After modifying the glsl functions to hlsl, I've got it working! I've still no idea why I was ending up with some really strange values, and will never know now. You're right about using the CPU: calculating just one layer of the scattering texture took 9 minutes! Anyway, thank you so much for being patient and trying to help. Your work is amazing and I'm very excited to be integrating it into my project. At some point in the future, I would like to clean it up and share a basic UE4 project with the community, if that's ok?

ebruneton commented 6 years ago

Glad you solved your issue. It is ok to share your project, you can put it in a new github project, similarly to https://github.com/Scrawk/Brunetons-Improved-Atmospheric-Scattering (keep the LICENSE as in this project).