Clouds Perlin & Scattering

Started by
19 comments, last by coelurus 18 years, 7 months ago
Forgot to login (I can't modify the post of anonymous). The image is there: clic here
jma
Advertisement
Visible blocking in shading can depend on too low resolution, but the artifacts in your image look pretty bad. Would you care to give some pseudo code for your raycaster?
I'm not sure but I would guess you have a height problem in your rays. The sun seems to be at the same height as the clouds. Those white pixels could be caused by errors in ray-tracing, the colors for that pixel isn't computed or is computed erroneously.
Quote:Original post by coelurus
Visible blocking in shading can depend on too low resolution, but the artifacts in your image look pretty bad. Would you care to give some pseudo code for your raycaster?


My "scattering" is a very simple and very intuitive 2D calculation of thickness of cloud the ray traverses.

Here a drawing of what I do:


Pseudo code is:
foreach texel
{
voxel0 = current texel x,y , z = noise value
RayTrace(voxel0, sun)
}

RayTrace(voxel0, sun)
{
calculate the equation of the red line (mA and mB)
mScatteringValue = 0
BresenhamLine(from voxel0, to sun)
finaltexel0 = mScatteringValue
}

BresehhamLine(voxel0,sun)
{
foreach texel in the line from voxel0 to sun
CalculateScattering(x,y)
}

CalculateScattering(x,y)
{
calculate distance in X,Y from voxel0 to that texel
calculate the rayZ (using the equation of the red line)
if ( rayZ is inside the cloud)
mScatteringValue += thickness of that texel
}

And here is the C++ code:
class ScatteringCalculator{	public:		void SetSun(int aSunX,int aSunY,int aSunZ) ;		void Apply(CorNoiMap2D<float>& vNoise,CorNoiMap2D<float>& vNoiseDest) ;	protected:		virtual bool CalculateScattering(int x,int y) ;		void RayTrace(int aVoxelX,int aVoxelY) ;		void BresenhamLineRunning(int x1,int y1,int x2,int y2) ;		float Angle(int x0,int y0,float z0,int x1,int y1, float z1) ;		// Position of the sun in the sky coordinate (0,0,0 is the top/left corner of the sky, with no cloud)		int mSunX ;		int mSunY ;		int mSunZ ;		// Start of current ray		int mVoxel0X ;		int mVoxel0Y ;		// Equation of the current Ray: y = a x + b		double mA ;		double mB ;		double mScatteringValue ;		CorNoiMap2D<float>* mNoise ;		CorNoiMap2D<float>* mNoiseDest ;} ;void ScatteringCalculator::SetSun(int aSunX,int aSunY,int aSunZ){	mSunX = aSunX ;	mSunY = aSunY ;	mSunZ = aSunZ ;}void ScatteringCalculator::Apply(CorNoiMap2D<float>& aNoise,CorNoiMap2D<float>& aNoiseDest){	mNoise = &aNoise ;	mNoiseDest = &aNoiseDest ;	int i ;	int j ;	for (i=0;i<vTex0.SizeX();i++)	{				for (j=0;j<vTex0.SizeY();j++)		{			RayTrace(i,j) ;		}	}}void ScatteringCalculator::RayTrace(int aVoxelX,int aVoxelY){	// No cloud = no scattering needed	if ( mNoise->Elt(aVoxelX,aVoxelY) < 0.01 )	{		mNoiseDest->Elt(aVoxelX,aVoxelY) = 0. ;		return ;	}	mScatteringValue = 0. ;	mVoxel0X = aVoxelX ;	mVoxel0Y = aVoxelY ;	double vDistanceSunVoxel = sqrt( (aVoxelX-mSunX) * (aVoxelX-mSunX) + (aVoxelY-mSunY) *(aVoxelY-mSunY) ) ;	double vVz0 = - mNoise->Elt(aVoxelX,aVoxelY) ;	// Compute the line parameters	if (vDistanceSunVoxel == 0)	{		mA = 0 ;	}	else	{		mA = ((float)(mSunZ-vVz0)) / vDistanceSunVoxel ; // Slope	}	mB = vVz0 ; // Starting height	BresenhamLineRunning(aVoxelX,aVoxelY,mSunX,mSunY) ;	float vResult = 1 - (mScatteringValue*0.1) ;	if (vResult < 0)		vResult = 0 ;	if (vResult >1.)		vResult = 1. ;	mNoiseDest->Elt(aVoxelX,aVoxelY) = vResult  ;}bool ScatteringCalculator::CalculateScattering(int x,int y){	double vVz = mNoise->Elt(x,y) ;		double vDistVoxel0Voxel = sqrt (  (mVoxel0X-x) * (mVoxel0X-x) + (mVoxel0Y-y) *(mVoxel0Y-y) ) ;	double vRayZ = mA * vDistVoxel0Voxel  + mB ;	if ( -vVz <= vRayZ && vVz >= vRayZ)	{		mScatteringValue += vVz - vRayZ ;		return true ;	}	return false ;}// cf. http://www.gamedev.net/reference/articles/article1275.asp// Calls CalculateScattering for each pixel instead of drawing it.// If CalculateScattering returns true the continue, else exitvoid ScatteringCalculator::BresenhamLineRunning(int x1,int y1,int x2,int y2)...
jma
"ScatteringCalculator::CalculateScattering" looks a little funny:

mScatteringValue += vVz - vRayZ;


It looks like you're taking the height of the cloud column above each ray sample and appending that to the scattering "value". When you sample light rays in voxel volumes, every voxel hit by light will be alone with affecting the incoming light.

Just some info (maybe superfluous, but better too much than too little [smile]):

The idea about light scattering in clouds is that a percentage of the light hitting a cloud particle scatters away from the light path and the rest continues traveling through the cloud (more stuff happens, but that's irrelevant). For each voxel that's in the way of a light ray (imagine a light-ray thinking: "if there is a voxel right where I am now, I lose some of my light."), the amount light that passes through the clouds decreases:

cloud_hits = 0for each voxel in "path of light ray" do if ray cuts voxel then  cloud_hits++light = light0 * pow(a, cloud_hits)


where 'a' describes how much light passes through a cloud voxel and 'light0' is the initial light intensity entering the clouds. Experimentation is the only way to get decent values there, approximate roughly first and then play around.
Original post by coelurus
"ScatteringCalculator::CalculateScattering" looks a little funny:

mScatteringValue += vVz - vRayZ;


Well, yes. I did that because if I take

mScatteringValue += 1 ;

The final image looks like this:

(for the moment there is no lookup texture, the texture is only greyscale + alpha, don't worry).
And taking the "thickness" of the cloud smooth the result.

Thank you for your explanations, they are not superfluous.

But my problem probably also comes from the usage I do of that "scattering texture". How do you use it in your illumination calculations ? For the moment I'm just using it as a multiplicator of the noise value.

i.e.
for each pixel:
final color = noise * scattering

jma
How do you use the accumulated number of voxel-hits for a lightray? Just multiplying by a factor 0.1 like in the code you posted before won't give proper shading:

If you single out a cloud voxel, light that hits the voxel will separate into some light moving on and the rest "disappearing" by absorption, scattering etc. The light that passes through a cloud voxel (which is a large group of cloud particles, so we can think in average) is not dependent on some absolute reference value, but on the incoming light.

lo = l0 * a

'a' is a factor telling us how much light passes through a cloud voxel, 'l0' is the incoming light intensity to a voxel and 'lo' the output light intensity. Hitting two voxels means:

lo1 = l0 * a
lo2 = lo1 * a = l0 * a * a

=> The final formula: l = l0 * a^n, n = number of voxel hits

Multiplication by some constant would give something like this:

l = l0 * (a + ... + a)

which means that the light that hits a voxel is independent on the path taken before hitting the voxel, which is obviously not correct.


As for how to combine cloud coverage (alpha) and shading (RGB); I basically just add them into an RGBA quadruplet and blend that onto the framebuffer. Using accuracy tricks such as splitting up noise into 2 color channels or more can be a little intricate, but the basic idea is to simply let the noise values work as alpha in blending the shading map.


I'm starting to feel the urge to write myself a new, improved cloud demo...
Quote:Original post by coelurus
l = l0 * a^n


Is "l0" the same for each voxel ? (= a constant value over the sky). Or does it depend on the distance from sun ? the angle sun/voxel ? ... Something else ?

Quote:Original post by coelurus
I'm starting to feel the urge to write myself a new, improved cloud demo...


Yessss ! great !
jma
Quote:Original post by jmaupay
Is "l0" the same for each voxel ? (= a constant value over the sky). Or does it depend on the distance from sun ? the angle sun/voxel ? ... Something else ?


Generally, a constant value should suffice, but Harris mentions a dot-product between the voxel-sun and voxel-eye rays in his paper. This means that lightrays with rather straight paths shine through more than those that have to reflect from clouds from far away.
The distance to the sun should not matter at all because all rays hit the clouds from above. The pointlight-sun trick for shading doesn't change this, you could think of the trick backwards: we transform the clouds 'til the sun rays become parallell and do our shading in that space. Distance still doesn't matter.

Quote:Original post by jmaupay
Quote:Original post by coelurus[!i]
I'm starting to feel the urge to write myself a new, improved cloud demo...

Yessss ! great !


Thanks for inspiring me, but I got an engine to write and studies to keep in mind (hmm, flip those two), and I'm not in CS [looksaround]
Well. I allways have such strange shading and analyzing the code I find that the result is coherent, but not what I expect ...



I have a 128x128 grid. I populate it with some noise (8 octaves added). I perform the scattering as described previously:
for each texel:
- calculate nb_hits of the ray in the noise grid
- that texel value = L0 * pow(a,nb_hits)

In the good case (the sun is very close to the clouds) 1 ray hits a maximum of 60 texels (nb_hits max = 60). If the sun is not close to the clouds, the ray hits let's say 10 texels.

So if I use that value as the final texture it is a 128x128 grid with values from 0 (no hit) to 10 (10 hits). So if I use that value as a greyscale it has only 10 different values (the pow() doesn't change anything).

What did I miss ?

[Edited by - jmaupay on September 12, 2005 9:02:52 AM]
jma

This topic is closed to new replies.

Advertisement