# Setting particle color with generated m x n image texture

I have a large particle system in which the particles (stars) are colored according to a generated image texture.

The image texture was originally 119,617 wide and 1 px high. No matter what I did, the particle colors were sampled 9-13 pixels away from the correct pixel coordinates, and the random nature of the offset made me suspect a numerical error somewhere.

I've gone to lengths to verify that 1) the pixel colors are correct for the pixel coordinates, and 2) that the image texture has enough precision to correctly store this information (by saving the image in 32-bit EXR format and loading it to the appropriate Image Texture node).

This to say that I trust the image. This places the most likely source of the error somewhere between the Particle Info node, from which I obtain the particle indices, and the Image Texture node, where I assign the particle indices their colors.

The original mapping of particle index to pixel coordinate was straightforward. The image texture was a single row of 119,617 pixels, one for each particle, so the normalized x-coordinate (in the 0-1 range expected by the Image Texture node) was simply the particle index divided by the total number of particles (minus 1, because the count starts at 0).

I have concerns about dividing over such a large number, and to remove that division as a possible source of error, I've decided to make my image texture 347 pixels wide and 345 pixels high (approximately square). This would reduce any numerical error due to division by ~1e5. The last row of pixels includes some blank pixels, but I don't expect these to give me trouble, since they will correspond to particle indices that don't exist and will simply not be assigned.

Roughly: The x-coordinate of the pixel to sample is calculated as the modulo of the particle index and the width of a pixel row (347 pixels). The modulo gives me the remainder of 119,617 / 347, which corresponds to the x-coordinate of the pixel. To normalize that coordinate to the range of 0-1, its value is divided by the width of a row (minus 1 because the count starts at 0).

The figure shows the node logic for the pixel row (y-coordinate) calculation (isolated for clarity).

The y-coordinate of the pixel to sample is calculated as the difference between the particle index and the modulo calculated above, divided by the width of a pixel row to get the number of the pixel row, divided by the total number of rows (minus 1, because count starts at 0) to normalize between 0 and 1.

(I added 1 to the particle index to ensure that the 347 pixel (and every nth multiple of it) falls on a new row; at worst, this adds a small systematic error that should be easy to catch.)

The figure shows the node logic for the pixel column (x-coordinate) calculation (also isolated for clarity).

I've verified that the pixel row makes sense (with a small error that I have yet to trace). Changing the pixel row (by adding one row or subtracting one from the calculation) gives me the expected color. I can't verify this for the pixel column (x-coordinate), as the color doesn't seem to change...

The figure shows the image texture I used for testing. The different color ranges are there just to give me a sense of which pixel in the image texture a chosen particle landed on (that particle being #32263, the Hipparcos catalog number for the star Sirius A). The magenta pixel between the yellow and blue pixels is the correct color for that star (row 92, column 339 of the image texture).

Below is the disappointing result. Sirius shows up yellow. I spent an inordinate amount of time (on the order of days) just adding and subtracting from the calculated pixel coordinates, trying to get the magenta color. Shifting the pixel row up by 1/344 turns Sirius A blue. Shifting the pixel row down by the same amount turns it red.

Sadly, doing this to the pixel column seems to have no effect. When the image texture was a single horizontal row, I was consistently off (for this particular star) by -13 pixels.

Has anyone struggled with controlling particle properties with image textures larger than 1 pixel in height? Can you suggest a better way to calculate the x- and y-coordinates of the pixels to sample? Can you see anything that could cause this error (because sadly, I can't)? I would love to move on from star colors (so much more to do) but it's critical that the colors be precise...

Many thanks in advance!

EDIT: Manually entering pixel coordinates in a Combine XYZ node and feeding them to the Image Texture node gives me the correct color (which, of course, being just a single number with no reference to particle index, is applied equally to every particle). The fractions in the Combine node are 339/346 for x, 92/344 for y, and 0.5 for z.

...Yet, calculating those exact same numbers from the particle indices gives an error. I've been grappling with this issue for a week and can't see what could be wrong. Could it be that the Index output of the Particle Info node is off somehow?

EDIT: I've verified that the mapping from particle index to pixel coordinates is correct. I did this by replacing the Particle Info node with a Value input node and setting the value to 32,263 (the particle I've been using for testing). This gives me the correct color. Adding an offset to the mapped pixel coordinates changes the color as expected, and going outside of the width and height of the image texture makes the particle go black (because no color).

However, adding back the Particle Info node (and making no other changes) brings back the incorrect color mapping.

This means that the issue is in the index output of this node. I can think of two possible issues: the node itself outputs the wrong particle index (although I consider this sort of bug unlikely); or the particle index does not correspond to the vertex index, which is what I've been using for testing.

This leaves me wondering: could the particle indices be different from the vertex indices from which they are generated? Is there anyway to ensure that they are the same? I realize that this post has grown out of proportion, so at this point I am mostly documenting my struggle with this issue and my search for an answer or workaround...

EDIT: Having run more experiments, it is now clear that the issue is not a numerical error but a mismatch between the indices of the particles and those of the vertices from which they were generated.

In my testing, I have been going on the assumption that each particle would be placed at the coordinates of the vertex of the same index. I saw no reason why this shouldn't be. I didn't use any randomness in my model, and there were no child particles. So it seemed that vertex 32263, for example, would spawn the particle of index 32263.

But this is not so. The indices weren't off by much: only -13 places for particle 32263 (out of 119617 total). But they were off, so no matter what I did to the image texture or to the calculations used to map particle indices to image texture pixels, the colors were never correct.

I checked that the issue was in fact the index mismatch by accessing the location of a test particle (32263) and comparing it to the location of the vertex of the same index:

import bpy

obj = bpy.data.objects["starPoints"]
ps = obj.particle_systems["ParticleSystem"]
p = ps.particles
v = obj.data.vertices

print("p32263 Original:", p[32263].location, "p118442 Original", p[118442].location, "p27919 Original:", p[27919].location)

print("v32263 Original:", v[32263].co, "v118442 Original", v[118442].co, "v27919 Original:", v[27919].co)

n = len(p)
for i in range(n):
p[i].location = v[i].co

print("p32263 New:", p[32263].location, "p118442 New", p[118442].location, "p27919 New:", p[27919].location)


So now my task is to make the particles each coincident with the vertex of the same index. My issue with particle color should then go away entirely. Will respond with an answer if it works and accept it to close this thread since it hasn't attracted any answers. Hopefully this long post will come in handy to someone down the line (probably in 5-10 years time, if Cycles is still around :D)