Today, we will talk about the dedicated Maya fluid shader workflow. Usually people who has their own fluid shader wants to mimic the Maya one, so we review in details what one needs to do.
The general flow of the fluid shading computation is:
- Compute the desired grid values for a point within the volume
- for example density or temperature
- the value at the point is determined by the grid values interpolated using the desired interpolation method (described in the attachment )
- Multiply the resulting value by the scale for that value, for example densityScale.
- Modulate the value based on any texturing for that attribute (for example opacity texture)
- Clip to result 0-1
- Apply the bias to the resulting value... the bias shifts the range 0-1 so that the values tend to be pushed more towards 1 or zero depending on the bias value.
- Use the result to look up the ramp value.
Find here some pseudo code which describes the overall fluids chain for shading:
# For a given point in space get the interpolated grid or # gradient value (which grid is determined by attributes # such as opacityInput) # If renderInterpolator is smooth then quadratic interpolation # is used otherwise it linearly interpolates grid values. # For example the opacity grid times the opacity scale value) inputValue =interpolatedGridValue * gridValueScale # Apply the built in noise texturing (they would need to # determine how to best simulate our noise methods if they # want to match those) # Note that if one is getting an opacity value then the # texture gain would be opacityTexGain. inputValue *=1 - textureGain + textureGain * noise_factor # Apply the bias if ( inputBias != 0.0 ) if ( inputBias < -0.99 ) inputBias =-0.99 # avoid divide by zero float pval =(inputBias - 1) / (-inputBias - 1) if ( inputValue < 0 ) inputValue =0 # protect power function from negative inputValue =powf (inputValue, pval)
The bias is applied to the value after all the other calculations (not simply added in). It basically shifts where the center (0.5) value will be while leaving 0 and 1 the same. Generally for users it is important that their grid*scale value ends up with values between 0-1, as values outside this range simply get clipped. Then we apply the bias to the clipped 0-1 values to adjust the distribution (avoids needing ramps with all indices near 0 or 1). The setup is as follows:
Ramp Indice =bias (textureFunction (inputGrid * gridScale))
Here is the math to apply the bias. InputVal is the grid value after scaling and texturing.
float pval =(inputBias - 1.0) / (-inputBias - 1.0) if ( pval < 0) indice =0 else indice =powf (inputVal, pval)
The Density values remapping for rendering:
The default settings map a 0-2 density range to 0-1 opacity values on the fluid (the densityScale defaults to 0.5, a value of 1 would map 1-1 density and opacity), which is then scaled by the transparency (which is a 3 channel rgb transparency).
(Other renderers might instead map 0-inf grid densities to 0-1 opacity)
Implementation of texture type:
It contains perlin noise, billow, volume wave, wispy, spacetime. Except for perlin noise, how another noise types were computed?
Here is an overview:
SpaceTime: perlin noise, but with a added dimension that is mapped to the time parameter (this has the time animation with the least bias)
Wispy: perlin noise where the texture coordinates are offset by a second perlin noise. Time animation animates the offset of the different frequencies in the texture offset noise.
Volume wave: each frequency is a cluster of sine waves along random vectors. Time offsets the input coordinate to the sin function.
Billow: this is a grid where each grid contains a point with a radius. The point position within the grid cell is randomized and the radius randomized. The time animation involves orbiting each point about the center of the voxel.
Maya 2D noise texture uses the same underlying code.
The formula for texGain is:
val =inputgrid * gridScale if ( gain < 1 ) val *=(1 - gain + gain * textureValue) else val *=gain * textureValue
If texture coordinate is fixed, the texture input P is just point of each center position of voxel in object space? How does coordinate speed affect P.
Coordinates are scaled relative to the size attribute on the fluid. I believe zero is the center of the fluid. Coordinate speed is only when the coordinates are defined as a dynamic simulating grid, in which case the simulation advection scales the grid velocity by the coordinate speed. (In terms of shaders, you simply need to export the coordinate grid and use that if it is dynamic)
In noise parameter: It contains ratio, threshold, depth max, frequency ratio. I have known that the meaning of these parameters, but how the frequency ratio be computed to affect ratio?
The frequency ratio determines the change in space (coordinate) scaling for each iteration of the noise, while the ratio scales the magnitude of the noise each iteration. They do not affect each other directly and in general if one lowers the frequency ratio one needs to increase the ratio (as well as max iterations) to preserve the character of the noise.
COMPUTING SPEED input for fluid ramps:
Starting with the raw grid velocity the formula to compute the ramp lookup index:
inputVal =1.0 - 1.0 / (1.0 + magnitude (velocity * velocityScale) * 0.1)
The inputVal then get modified by the bias function.
The edge dropoff affects the transparency used for the final fluid render (as opposed to the opacity input ). The dropoff can optionally be a grid.
In the grid case:
falloff =interpolatedFalloffGridValue (point) if ( edgeDropoff > 0.9999 ) opacityMult =0 else opacityMult =powf (falloff, edgeDropoff / (1 - edgeDropoff))
in the linear gradient case(xyz axis):
# Determine a distance from the center to the boundary of the # dropoff shape (cube, sphere, cone) or the simple linear gradient # along a particular axis: dist =gradientDistance # this is the 0-1 distance along an axis if( edgeDropoff > 0.9999 ) opacityMult =0 else opacityMult =powf (dist, edgeDropoff / (1 - edgeDropoff))
in the shape case (cube, sphere, cone)
dist =distanceFromCenter (point, dropoffShape) # this is a 0-1 distance from center to boundary of shape dist =(1 - dist) / edgeDropoff opacityMult =float(sin ((dist - 0.5) * PI) * 0.5 + 0.5)
The cube case is a little more complex than what is described above… basically we do the dropoff defined above( with the sin function) for each axis then the resulting density mult is the factor for each of the axis values multiplied together.
How the ramps are indexed:
u is the interpolant between the indices, ranging between 0-1. L1 and L2 are the luminosity of the two indices being interpolated.
u =u * u
u =1 - (1 - u * u)
u =(cos ((u + 1) * PI) + 1) * 0.5
if ( l1 > l2 ) u =sin (u * PI / 2) else u =sin ((u - 1) * PI / 2) + 1
if ( l1 < l2 ) u =sin (u * PI / 2) else u =sin ((u - 1) * PI / 2 ) + 1
How Inflection is computed:
if ( inflection ) if ( nval > 0 ) nval =nval * 2 - 1 else nval =-nval * 2 - 1
Where nval is noise value.