POV-Ray / povray

The Persistence of Vision Raytracer (POV-Ray)
https://www.povray.org/
GNU Affero General Public License v3.0
1.37k stars 282 forks source link

Media method 3 jitter not bounded within sampling sub-interval end points. #348

Open wfpokorny opened 6 years ago

wfpokorny commented 6 years ago

Summary

Our current media method 3 jitter code doesn't bound even the smallest jitter within the end points of the sub-intervals. This is most obvious when the media density has a relatively high value as it exits the containing object and we are in fact sampling half the time outside the container.

See the images: NoJitter.png vs VeryTinyJitter.png. The two results should be nearly identical and they're not. The bounding issue happens any time abs (jitter value) >0 but depending on sample counts, density and so on it will be more or less visible in practice.

(Note: There is too a sub issue with jitter worth fixing with this one. On adaptive descent we are I think correctly passing the non-jitter 'distance' value to the recursive call, but we are incorrectly not re-calculating the color and optical distance before that call. The result is we are corrupting downstream tests against aa_threshold and downstream optical distance calculations. Perhaps none of this matters if the only intent is to give the user a noisy result. This just for noise aim is however different than how jitter is used with method 1 & 2 and the photon media depositing code where jitter tends to resolve the media's density with application.)

Environment

Ubuntu 16.04 linux. v3.7.0 onward at least. Attached code written and tested against release/v3.8.0 as of commit 5b99fdc Sun Jan 14 13:15:29 2018 +0100.

Steps to Reproduce

1) povray +w800 +h600 MediaMethod3JitterSubIntervalBounding.pov 2) Note noise in image where lit media exits container. See: VeryTinyJitter.png 3) Edit source file and change VarJitter to 0. 4) povray +w800 +h600 MediaMethod3JitterSubIntervalBounding.pov 5) Result clean and it's what we should produce in (1). See: NoJitter.png

Expected Behavior

Media method 3 jitter should be bound within the active interval so long as the absolute value is >=0 and <=1.0. With larger value it will of course jump intervals. Other media methods look to keep the jitter inside the 'interval's' end points.

Actual Behavior

The jitter isn't sub-sample interval bound though on recursive descent the issue is I think more the lack of recalculation of color and optical distance prior to decent.

Scene

//
// See VarJitter setting below. At 0 OK. At abs(jitter)>0 && <=1 not.
//
// Our current media method 3 jitter code doesn't bound even the smallest
// jitter within the end points of the sub-intervals. This is most obvious
// and most noisy when the media density has a relatively high value and
// exits the containing object.
//
// See the images: NoJitter.png vs VeryTinyJitter.png. The two results
// should be nearly identical and they're not.
//

#version 3.8;
global_settings { assumed_gamma 1 }
#default { finish {ambient 0.0 diffuse 1.0} }
#declare Grey40 = srgb <0.4,0.4,0.4>;
background { color Grey40 }
#declare VarOrthoMult = 1;
#declare Camera01z = camera {
    orthographic
    location <0,0,-2>
    direction z
    right VarOrthoMult*x*max(1,image_width/image_height)
    up VarOrthoMult*y*max(1,image_height/image_width)
    translate <0.5,0.5,-2>
}
#declare VarMediaR = 0.48;
#declare VarDistRes = 0.04;
#declare VarDist = VarMediaR-(VarDistRes/2);
#declare VarDistPos = VarDist+(VarDistRes/2);
#declare VarDistNeg = VarDist-(VarDistRes/2);
#declare White = srgb <1,1,1>;
#declare Light00 = light_source {
    <0.5,0.5,-0.5>, White
    fade_distance VarDistPos
    fade_power 1000
    media_attenuation on
}
#declare Red = srgb <1,0,0>;
#declare CylinderX = cylinder { -2*x, 2*x, 0.01 pigment { Red } }
#declare Green = srgb <0,1,0>;
#declare CylinderY = cylinder { -2*y, 2*y, 0.01 pigment { Green } }
#declare Blue = srgb <0,0,1>;
#declare CylinderZ = cylinder { -2*z, 2*z, 0.01 pigment { Blue } }
#declare Box00 = box {
    <-1.001,-1.001,-1.001>,<1.022,0.499,1.022>
    pigment { color White }
}
#declare Box01 = box { <-1,-1,-1>,<1,0.5,1> hollow }
#include "functions.inc"
#declare FnctSph = function (x,y,z) { sqrt(x*x+y*y+z*z)>0.46 }
#declare DensityOne = density { function { FnctSph(x-0.5,y-0.5,z+0.5) } }
#declare Sphere01 = sphere {
    <0,0,0>, 0.23
    scale <2.2,1,2.2>
    translate <0.5,0.75,-0.5>
    hollow
}
#declare Difference01 = difference { object { Sphere01 } object { Box01 } }
#declare VarSamples = 1;
   #declare VarJitter = 0.0;    // OK.
   #declare VarJitter = -0.001; // Even smallest bleeds outside sub-interval.
// #declare VarJitter = +0.001;
#declare VarAA_level = 1;
#declare MediaSC = media {
    method       3
    intervals    1
    samples      VarSamples
    jitter       VarJitter
    aa_level     VarAA_level
    aa_threshold 0.1
    absorption   rgb <0,0,0>
    emission     rgb <0,0,0>
    scattering { 1, rgb <1,1,1> extinction 1 }
    density { DensityOne } // Avoids method 3, constant density, method 1, fast path. 
}
#declare InteriorSC = interior { ior 1 media { MediaSC } }
#declare Clear90 = srgbt <1,1,1,0.9>;
#declare PigmClear = pigment { color Clear90 }
#declare TxtrClear = texture { pigment { PigmClear } }
#declare MatrSC = material { texture { TxtrClear } interior { InteriorSC } }
#declare Obj02SC = object { Difference01 material { MatrSC } hollow }

//--- scene ---
   camera { Camera01z }
   light_source { Light00 }
   object { Box00 }
   object { CylinderX }
   object { CylinderY }
   object { CylinderZ }
   object { Obj02SC }

Output

See attached NoJitter.png and VeryTinyJitter.png

nojitter verytinyjitter

Workaround

None. Recommend media method 3 jitter be avoided unless a noisy result is the actual aim.

Suggested Solution

The up front solutions would be to bound jitter to sub-sample / interval end point range as done elsewhere in media methods. Recalculate color (C2) and distance (od2) before recursive call in ComputeOneMediaSampleRecursive. However, these are likely to be expensive to the normal user as in my own use and review of media scenes - most do not use jitter. Splitting the code into jitter and no jitter trees is an option offering a substantial performance gain to jitter=0 users and limiting the jitter fix impacts to those using jitter. Further, I think it will likely make performance optimization in the jitter=0 tree much easier to accomplish. I'm playing with such split code as I write this but nothing is anywhere near ready.

We probably have to keep the existing distance based jitter given it's been around so long, but maybe we could think too about a new media { method 3 aa_jitter ... } option that would be applied at the aa_threshold test for recursion. That sort of jitter would tend to resolve media density in a way more friendly to AA, area lights and such.