Fragment Shaded Explosion

Started by
3 comments, last by Lithic 19 years, 12 months ago
I've been working to create an explosion based entirely on the GPU as kind of a side project. Heres how it works: We do lookups in a 4D perlin noise. Lookup locations are found by taking a position where the noise should be based on the texture X and why and translating it based on the camera's view vector and up vectors. These vectors may need to be negated so that the explosion offsets rotate properly with view changes. Perlin noise is generated inside the Cg code. The explosion will be subdivided into virtual arrays of gradients. Basically we take the floor of the inputted location times the radius divided by size of a space to find the number of the space our location is contained in. Then we find the static gradient of that space using a pseudorandom function (this will later need to be altered so that it varies based on a uniform input variable). This way we always calculate the same gradient for that point no matter what the time. We then linearly interpolate between all the gradients to get our final output value. Doing abs() of fractal lookups based on location should provide a fiery-looking design. Mapped to the surface of our virtual sphere (with somewhat randomized positions) and given and interpolated betwen color gradients should provide for an extremely realistic explosion. A simple depthmap parameter can be used to modify the result so that alpha will be lowered based on depth. Another noise lookup in a different fashion can be used to generate smoke surrounding the blast. Great in theory, but implementation is very difficult. Here is the involved code i have so far (it is all Cg code, execution begins in main):
// A pseudorandom function -- read notes to avoid overflow

float rand(float seed, half4 pseudoSeeds) {
	// NOTE: pseudoSeeds should be 2 digit positive integers to work effectively

	// NOTE: seed should be between -1 and 1

	return sin(seed * pseudoSeeds.x * (seed * pseudoSeeds.y - pseudoSeeds.z) + pseudoSeeds.w);
}

// Calculate a gradient's falloff

float fade(float t) {
	return t * t * 3 - 2 * t * t * t;
}

float grad(float seed, float x, float y, float z, float w) {
	half4 pseudoSeeds = {56, 84, 34, 92};
	float randVar = abs(rand(seed, pseudoSeeds));
	return ((randVar < (1.0/6.0)) ? x + y : (randVar < (1.0/3.0)) ?  x + z : (randVar < 0.5) ?  x + w :
		(randVar < (2.0/3.0)) ? y + z : (randVar < (5.0/6.0)) ? y + w : z + w);
}

// Linear Interpolation

float lerp(float t, float a, float b) {
	return a + t * (b - a); 
}

float randomize(float4 unitCube) {
	half4 pseudoSeeds = {23, 56, 94, 87};

	return rand(unitCube.x + unitCube.y + unitCube.z + unitCube.w, pseudoSeeds) / 2.0 + 0.5;
}

// Calculate 4D perlin noise

float noise(float4 location, float radius) {
	// World space between gradients

	float gradientSpacing = 0.5;
	
	// Find the X, Y, Zs of the unit cube 

	float4 tempLoc = location;
	tempLoc.xyz *= radius / gradientSpacing;
	half4 unitCube;
	
	// Is it possible to condense this to unitCube = floor(tempLoc); ?

	unitCube = floor(tempLoc);	
	
	// Is it possible to condense this to tempLoc -= unitCube; ?

	tempLoc -= unitCube;
	
	unitCube += 1;
	
	// Calculate fades

	float4 fades;
	fades.x = fade(tempLoc.x);
	fades.y	= fade(tempLoc.y);
	fades.z = fade(tempLoc.z);
	fades.w = fade(tempLoc.w);
	
	tempLoc.x = unitCube.x / ((radius / gradientSpacing) * 4.0);
	tempLoc.y = unitCube.y / ((radius / gradientSpacing) * 4.0);
	tempLoc.z = unitCube.z / ((radius / gradientSpacing) * 4.0);
	tempLoc.w = unitCube.w / ((radius / gradientSpacing) * 4.0);
	
	return lerp(fades.w, lerp(fades.z, lerp(fades.y, lerp(fades.x, randomize(tempLoc),
								       randomize(tempLoc)),
							 lerp(fades.x, randomize(tempLoc),
								       randomize(tempLoc))),
					   lerp(fades.y, lerp(fades.x, randomize(tempLoc),
								       randomize(tempLoc)),
							 lerp(fades.x, randomize(tempLoc),
								       randomize(tempLoc)))),
			     lerp(fades.z, lerp(fades.y, lerp(fades.x, randomize(tempLoc),
								       randomize(tempLoc)),
							 lerp(fades.x, randomize(tempLoc),
								       randomize(tempLoc))),
					   lerp(fades.y, lerp(fades.x, randomize(tempLoc),
								       randomize(tempLoc)),
							 lerp(fades.x, randomize(tempLoc),
								       randomize(tempLoc)))));
}
// Get the color gradient

float4 gradient(float intensity, float4 brightColor, float4 darkColor) {
	float intensityFade = fade(intensity);
	return darkColor + intensityFade * (brightColor - darkColor);
}

float3 getLocation(float2 texCoord, float3 view, float3 up, float cDist) {
	float3 newLocation;
	
	// Find location of the pixel (throw in a little randomness =p)

	newLocation.xy = texCoord;
	float2 temps = (newLocation.xy - 0.5);
	temps = temps * temps;
	float dist = sqrt(temps.x + temps.y);
	newLocation.z = clamp(sin(clamp(dist * 2.0 * 3.1415926, 0.0, 3.1415926 / 2.0)), 0.0, cDist);
	half4 pseudoSeeds = {40, 72, 93, 23};
	
	newLocation.x += rand(newLocation.x, pseudoSeeds) / 20.0;
	newLocation.y += rand(newLocation.y, pseudoSeeds) / 20.0;
	newLocation.z += rand(newLocation.z, pseudoSeeds) / 20.0;
	
	clamp(newLocation, 0.0, 1.0);	
	newLocation = newLocation * 2.0 - 1.0;
	
	// Calculate "Side" vector

	float3 side = cross(view, up);
	
	// Translate location in terms of the camera view

	newLocation.x = newLocation.x * side.z + newLocation.x * view.x + newLocation.x * up.y;
	newLocation.y = newLocation.y * side.x + newLocation.y * view.y + newLocation.y * up.z;
	newLocation.z = newLocation.z * side.y + newLocation.z * view.z + newLocation.z * up.x;
	
	// location of the lookup is between 0 and 1

	newLocation = newLocation / 2.0 + 0.5;
	
	return newLocation;
} 

float4 main(float2 texCoord : TEXCOORD0, float3 stateVars : TEXCOORD1, 
			float4 brightColor : COLOR0, float4 darkColor : TEXCOORD2,
			float3 vView : TEXCOORD3, float3 vUp : TEXCOORD4, 
			uniform sampler2D depthMap : TEXUNIT0) : COLOR {
	// NOTE: cameraVector MUST be normalized

	
	// stateVars.x = explosion radius

	// stateVars.y = camera distance (1 to 0, 0 being at the back of the explosion while 1 is the front)

	// stateVars.z = time from explosion start (1/10 of a second)

	
	// Get the location of the lookup

	float4 location;
	location.xyz = getLocation(texCoord, vView, vUp, stateVars.y);
	location.w = stateVars.z;
	
	// Get intensity from position (fractal noise lookups)

	float intensity;

	// This line will be replaced by a fractal lookup to get fiery/smokey noise

	intensity = noise(location, stateVars.x);
	
	// Return gradient value

	return gradient(intensity, brightColor, darkColor);
}
This fragment shader will not compile, although (IMHO) it appears syntaxically correct. Also, note that the shader will draw things to the screen if i return other parts of noise function. For example, if i return tempLoc.z right after i subtract unitCube i get a sample like the one shown below. I can generate similar pictures from the x and y values which also look correct. A picture of the local z positions within the points' bounding cube. This is how it should look. However, it all breaks down a few lines later and I start getting strange results. The code does not appear to have any noticable bugs in it, so I am currently under the assumption that it is something hardware-related. Please ICQ at 304-988-304 if you have questions about the implementation. EDIT: Please note that this is merely a test, and I would like the final version of this fragment program run another perlin noise to add in smoke, and also i would have it so as dist from the center grows time slows down (this would make it seem as though center particles were more or less flying outwards) and I would also have it so that below zero time particles are not present (alpha = 0), and that the alpha visibility fades off after a certain time (these would simulate the creation and fading of the explosion). --===LITHIC===-- --===WWW.Decimation.TK===-- [edited by - Lithic on April 6, 2004 6:10:45 PM]
Advertisement
After some testing I have narrowed the error down to a single line, but I do not know how to solve it, or why it is happening.

After this:
	tempLoc.x = unitCube.x / ((radius / gradientSpacing) * 4.0);	tempLoc.y = unitCube.y / ((radius / gradientSpacing) * 4.0);	tempLoc.z = unitCube.z / ((radius / gradientSpacing) * 4.0);	tempLoc.w = unitCube.w / ((radius / gradientSpacing) * 4.0);


I added a line for debugging:
return tempLoc.x; //
I tried the same with tempLoc.y,z,w and they all worked properly. However, the following line does NOT work and instead returns a black screen:
return tempLoc.x + tempLoc.z;
In fact, the only addition that i tried that DID work was tempLoc.x + tempLoc.y; Since all four parts are correct between 0.0 and 0.25 it doesn't make sense that it would return a black screen here (I'm not even sure how a black screen is possible since i'm using my gradient function). Any help?

--===LITHIC===--
--===WWW.Decimation.TK===--

[edited by - Lithic on April 7, 2004 2:26:19 PM]
i dont know the first thing about shader languages, but could it be a rounding problem? if both values are in the range 0-0.25 then the outcome will always be zero in case of an int conversion. your function type is float so that shouldnt be a problem, but maybe its in the postprocessing of this returned value?
Nope, postprocessing just goes straight to the gradient function right now, and like i said in the earlier post, if i return other values it will display a proper gradient.

--===LITHIC===--
--===WWW.Decimation.TK===--
Sorry superpig for bringing up a thread that''s close to your two week rule, but there was a new development. I upgraded shaders to version 2.1.2 and this helped me with this problem, but i still need you guys'' help. In the new fragment program version when a program runs over the 512 instruction limit it now gives a compiler error. My program runs over the 512 instruction (not lines of code) limit somewhere during this line:

unitCube = floor(tempLoc);

I am rather certain this is the problem, because under the earlier compiler (version 2.0?), it would always compile past these lines, but if i returned anything calculated after these lines it would usually give me a black screen.

This poses a big problem, since when over the limit, the program cannot execute properly. I was wondering if there was a way that i could perhaps split the program into two smaller fragment programs that run chained. Is this possible? If not, perhaps somebody could give suggestions on how to make the program simpler. One idea i thought of was simply to map a 2D noise texture and use the POSITION return as well as the COLOR return with the pixel to have a simulated 3D. This would require nearly a complete rewrite of the implementation, however. Also, i think that implementation would take the cheap-hack-of-the-year award.

This topic is closed to new replies.

Advertisement