CParticleSamplerTexture

From PopcornFX
Jump to: navigation, search

Particle sampler texture - Main page : Particle samplers Particle samplers


The texture sampler allows you to sample a 2D texture (luminance or RGBA), it is useful to encode particle properties (for example, for particles spawned on a mesh shape).

The sampler is stable/consistent, meaning that if you author a texture with some channels at full intensity or at full zero, even if the texture is in a compressed format, you'll get exactly 1.0 or 0.0 when sampling them back, and not unprecise values like 1.0001, or 0.0001.


Particle sampler texture Node properties

Field name Default value Description
Particle sampler texture General
SamplerName Auto-generated name
SamplerDescription Empty Sampler Description (optional)
UserData Empty Use this if you need to pass custom data to your game engine's integration (custom PopcornFX SDK). If you're Using UE4 or Unity, you can ignore this.
TextureResource <empty> Path of the texture to be used, relative to the root of the project
AtlasDefinition <empty> v1.6.0 Path of the .pkat file to be used as an atlas definition (see Atlas builder)
ScriptOutputType float4 Determines if the sampler will sample the texture as an rgba or grayscale texture.
  • float : will sample as grayscale, the 'sample' script function will return a 'float'
  • float4 : will sample as RGBA, the 'sample' script function will return a 'float4'
DefaultFilter Point Filter used when sampling the texture. Tells the sampler how to interpolate between texels.
  • Point : samples the closest texel. no interpolation. fastest
  • Linear : bilinear interpolation between the 4 closest texels. best quality
DefaultWrap Wrap v1.6.0 Addressing mode used when sampling the texture. Tells the sampler how to wrap UVs around the texture.
  • Clamp : clamps the UVs in the [0,1] range
  • Wrap : wraps the UVs, use this for tiling textures
SampleRawValues false v1.8.0 If enabled, tells the image sampler to not perform any runtime conversions and to sample the raw texel data from the source texture.

Enable this if you want minimal runtime memory and performance overhead when loading the effect.

Sampler Regular Allows to choose between sampling the raw texels or using the texture as a density map
  • Regular : returns the texel value
  • Density : remaps UV coordinates to density-space (see below for details)
  • Both : exposes both regular and density sampling functions to the scripts.
SampleGammaSpace Linear v1.6.0 Gamma-space used when sampling the texture. depends on how your texture was saved, and what you'll use the sample for.

If you plan on assigning the texels to color values, and your lighting-pipeline is gamma-correct, and your texture was saved as sRGB, use 'Linear' mode.

  • Linear : will return color values in linear-space. If source texture was sRGB, will perform sRGB -> linear conversion
  • sRGB : will return color values in sRGB-space. If source texture was linear, will perform linear -> sRGB conversion
  • Linear_To_sRGB : will return color values in sRGB-space. Interprets source texture as being linear. will perform linear -> sRGB conversion
  • sRGB_To_Linear : will return color values in linear-space. Interprets source texture as being sRGB. will perform sRGB -> linear conversion
DensityPower 1.0f Densities will be exponentiated with this value. It allows to quickly change the perceived "contrast" of the density map.
DensitySrc RGBA_Average v1.6.0 Controls how the density map will be built if the source image is multi-channel.
  • Red : will use the red channel to build the density map
  • Green : will use the green channel to build the density map
  • Blue : will use the blue channel to build the density map
  • Alpha : will use the alpha channel to build the density map
  • RGBA_Average : will use the value 'DensityRGBAWeights' to weight each channel before adding them together to compute the final densities.
DensityRGBAWeights { 0.212671, 0.715160, 0.072169, 0 } v1.6.0 if 'DensitySrc' is set to 'RGBA_Average', these are the weights used for averaging.\nThey will be renormalized to 1 automatically.

Defaults to luminosity-preserving weights, and discards alpha.

Texture sampler properties


Particle sampler texture Script bindings

for each texture, the following are published to scripts:

"Sampler" value published functions
"Regular" or "Both":
When 'ScriptOutputType' is set to 'float':
float	sample(float2 uv, int filterMode, int addressingMode);

When 'ScriptOutputType' is set to 'float4':

float4	sample(float2 uv, int filterMode, int addressingMode);
"Density" or "Both":
float2	sampleDensity(int filterMode, int addressingMode);
float2	remapDensity(float2 uv, int filterMode, int addressingMode);

The filterMode parameter can be any of those two values:

  • textureFilter.Point (default)
  • textureFilter.Linear

The addressingMode parameter can be any of those two values:

  • textureAddr.Clamp
  • textureAddr.Wrap (default)

The uv parameter is in standard normalized texture coordinates space:

  • float2(0,0) is the top-left texture corner
  • float2(1,0) is the top-right texture corner
  • float2(1,1) is the bottom-right texture corner
  • float2(0,1) is the bottom-left texture corner
  • float2(0.5,0.5) is the center of the texture.


for example, let's consider a texture sampler named "MyCoolTexture", bound to an RGBA texture.

In order to sample it with bilinear filtering and tiling enabled at UV coordinates {0.3,0.78}, one would write in hh-script:

	float2	texcoords = float2(0.3, 0.78);
	float4	colorRGBA = MyCoolTexture.sample(texcoords, textureFilter.Linear, textureAddr.Wrap);

as textureFilter.Point and textureAddr.Wrap are the default values for the filter and addressing parameters of the 'sample' function, if one were to write:

	float4	colorRGBA = MyCoolTexture.sample(texcoords);

it would sample the texture with point filtering (that is, no filtering at all, returning the nearest texel), and wrap address mode (which means texture tiling is enabled). note that:

	float4	colorRGBA = MyCoolTexture.sample(texcoords, textureFilter.Linear);

is therefore equivalent to:

	float4	colorRGBA = MyCoolTexture.sample(texcoords, textureFilter.Linear, textureAddr.Wrap);

as textureAddr.Wrap is the default value.


Particle sampler texture Examples

To better show the effects of the filter and wrap parameters, here is a visual example with a flat square particle spawner, that spawns 300K Particles. We'll sample a texture in the spawn script, using the particle x/y position as UV texture coordinates, and set the particle coord to the color sampled in the texture at that location.

Base particle canvas Base texture
Base particle canvas Base texture used


The following images were generated by the following spawn script applied to the base particle canvas, and modifying the filter mode, addressing mode, and UV multipliers and offsets:

function void	Eval()
{
	float	uvMultiplier = 1.0;
	float2	uvOffset = float2(0.5, 0.5);
	float2	uv = Position.xy * uvMultiplier * 0.125 + uvOffset;
	Size = 0.025;
	Color = Texture01.sample(uv, textureFilter.Linear, textureAddr.Clamp);
}


Addressing modes

textureAddr.Wrap textureAddr.Clamp
textureAddr.Wrap
UV multiplier = 2.0
textureAddr.Clamp
UV multiplier = 2.0
textureAddr.Wrap textureAddr.Clamp
textureAddr.Wrap
UV multiplier = 16.0
textureAddr.Clamp
UV multiplier = 16.0


Filter modes

textureFilter.Point textureFilter.Linear
textureFilter.Point
UV multiplier = 0.04
textureFilter.Linear
UV multiplier = 0.04


Particle sampler texture Probability Density map

When setting the "Sampler" field to either "Density" or "Both", you have access to the remapDensity() and sampleDensity() (v1.9.0) functions.

When given uniform random float2 texcoords, in the [0,1] range, the remapDensity() function will treat the image as a density function, and will remap the input texcoords to the image density space.
This will produce samples that are more likely to fall where the image is bright. dark areas of the image won't get sampled as much as lighter ones.

For example, the following script :

function void	Eval()
{
	Life = 0.5;
	Size = 0.0065;
	
	float2	uv = Image.remapDensity(rand(float2(0), float2(1)), textureFilter.Linear);
	Position = (uv * float2(1,-1) + float2(-0.5,0.5)).xy0;
}


Given a texture sampler named "Image" with "DensityPower" set to 2.0, will produce this:

Source image Resulting particle distribution
Source image Resulting particle distribution

sampleDensity() doesn't expect an UV, and returns a random UV on the texture, properly distributed based on the density function.
It is equivalent to calling remapDensity(rand(float2(0), float2(1))), but more efficient (around 3 times faster on most hardware). The previous example script using sampleDensity would look like this.


function void	Eval()
{
	Life = 0.5;
	Size = 0.0065;
	
	float2	uv = Image.sampleDensity(textureFilter.Linear);
	Position = (uv * float2(1,-1) + float2(-0.5,0.5)).xy0;
}


Filter modes

PDF : Point filter PDF : linear filter Source image
Point filtering
Samples are taken on the pixel centers
Linear filtering
Samples are distributed uniformly on the pixel's surface
Image used as the probability density map


Performance & Optimizations

Images can typically eat-up a _lot_ of memory very quickly when they become large.
Popcorn's image samplers can gracefully handle and sample large images, as long as you've got enough memory.

You should be careful not to use images that are too large, especially on prev-gen consoles or mobile. 64*64 or 128*128 is already a size that should be good enough for most uses.
Smaller images will be good for both memory usage and performance.


There are a few things to consider to keep memory and performance overhead to a minimum when using the image sampler:


Regular sampling

For faster CPU sampling, popcorn only knows how to sample a few image formats.

When the original image is already in one of these known formats, there is no extra memory or performance overhead.
When it is not, popcorn has to convert it to the correct format, allocate a new buffer, and do the conversion (can mean decompressing the image at loading time)

You can remove this step by providing an image that's already in the correct format.
The natively supported formats that require no conversion are:


v1.8.0 and above

Changing the 'SampleGammaSpace' property might also produce extra computations at load time, depending on the gamma-space of the input image.
To ensure there is no extra computations done, you can check the 'SampleRawValues' property, it'll make the sampler return the raw values stored in the texture, without caring about gamma-spaces.


Natively supported formats
Format Channels Texel Size Point sampling Linear sampling Description
BGRA8 RGBA 32-bits YES YES 4 8-bits channels note: NOT RGBA8 !
BGRA4 RGBA 16-bits YES YES 4 4-bits channels note: NOT RGBA4 !
DXT1 RGBA 4-bits YES NO Compressed, 5.6.5 RGB + 1 bit alpha
R32F single-channel 32-bits YES NO 1 32-bits float channel

Note that 'BGRA' is also named 'ARGB' in some packages, like NVidia's photoshop DDS exporter
Unlike in previous versions, there is no additional precomputations and overhead for linear sampling.
So load times are faster, and image samplers take far less memory.
Formats not supporting linear sampling fallback to point sampling if you try to sample them with a linear filter.


Total memory used
Resolution BGRA8 BGRA4 DXT1 R32F
64*64 16 Kb 8 Kb 2 Kb 16 Kb
128*128 65 Kb 32 Kb 8 Kb 65 Kb
256*256 256 Kb 128 Kb 32 Kb 256 Kb
1024*1024 4 Mb 2 Mb 512 Kb 4 Mb
4096*4096 64 Mb (!) 32 Mb (!) 8 Mb (!) 64 Mb (!)


Regarding performance, BGRA4 is fastest for 4-channels, BGRA8 is only very slightly slower, and DXT1 is roughly twice slower than BGRA8 and BGRA4.
R32F is the fastest overall, from 1.5x to 2x faster than the fastest 4-channel sampler, but... you only have a single channel.

Eliminating load-time overhead

To remove ALL loading-time overhead related to format conversion or precomputations, make sure you do the following:

  • Enable 'SampleRawValues'
  • Set 'ScriptOutputType' so that it matches your texture's channel-count. Set it to 'float4' for RGBA textures, and to 'float' for R32F
  • Make sure the texture you reference is a .dds that has been saved in one of the formats listed in the 'Natively supported formats' table above.


v1.7.0 and below

Natively supported formats
Format Channels Texel Size Point sampling Linear sampling Description
BGRA8 BGRA 32-bits YES YES 4 8-bits channels note: NOT RGBA8 !
R32F single-channel 32-bits YES NO 1 32-bits float channel

Note that 'BGRA' is also named 'ARGB' in some packages, like NVidia's photoshop DDS exporter
Formats not supporting linear sampling fallback to point sampling if you try to sample them with a linear filter.


Then, once the texels are in a known format, if the format is BGRA8, it will pre-swizzle them, and duplicate some of them to remove extra computations.
The extra memory used added by this step will be equal to ImageWidth * ImageHeight * 16 bytes
These pre-computations are used only for linear sampling, but they will be computed at load time even if you don't use linear sampling afterwards.

Taking all this into account, here is the total amount of memory popcorn will end up needing for various image sizes:
(that includes the memory used by the original image, as well as the memory used by the sampler's precomputed tables)


Total memory used
Resolution BGRA8
64*64 80 Kb
128*128 320 Kb
256*256 1.25 Mb
1024*1024 20 Mb (!)
4096*4096 320 Mb (!)

Changing the 'SampleGammaSpace' property might also produce extra computations at load time, depending on the gamma-space of the input image.

Density sampling

The density map will take at most roughly ImageWidth * ImageHeight * 4 bytes, regardless of the original image format.
That is, for a 512*512 image, 1 Mb of memory will be used.