Particle emission color texture doesn't retrieve correct color

Godot Version



The colors sampled by a GPUParticles3D don’t seem to work as documented. Can someone help me see if I am doing something wrong or if I am misunderstanding the feature?

Goal: Given an array of Vector3, I want to create a particle at each Vector3 AND set the particle’s color based on its distance to the origin.

I thought I could accomplish this using GPUParticles3D POINTS emission_shape and emission_color_texture. The documentation says the color of the particle is sampled at the same point as the emission shape.

I setup a minimal example of my attempt. I calculate the distance to the origin for each position and use that to create a emission_color_texture. Particles closer to the center should be Blue and farther points should be Red.
However, what I actually see is a very random distribution of reds and blues.

Can someone explain to me what is going on? Below is a simple script demonstrating this.

extends GPUParticles3D

const maxOffset = 3.0
const particleCount = 800
var maxDist = Vector3(maxOffset, maxOffset, 0).length()
var pointCache: Array[Vector3]

# Called when the node enters the scene tree for the first time.
func _ready():
	# Generate array of 3D points
	var rng: RandomNumberGenerator =
	for pi in particleCount:
		pointCache[pi] = Vector3(rng.randf_range(-maxOffset, maxOffset),rng.randf_range(-maxOffset, maxOffset),0)
	var particleProcessMat := self.process_material as ParticleProcessMaterial
	var emissionImage: Image = Image.create(particleCount, 1, false, Image.FORMAT_RGBF)
	var emissionColorImage: Image = Image.create(particleCount, 1, false, Image.FORMAT_RGBF)

	# Set each particle's position and color
	for pi in particleCount:
		# Use the particle's distance to the origin to determine it's color
		var curDist = pointCache[pi].length()
		var distRatio = clampf(curDist / maxDist, 0, 1)
		emissionColorImage.set_pixel(pi, 0, Color(distRatio, 0, 1.0 - distRatio))
		emissionImage.set_pixel(pi, 0, Color(pointCache[pi].x, pointCache[pi].y, pointCache[pi].z))
	var emissionTex: ImageTexture = ImageTexture.create_from_image(emissionImage)
	var emissionColorTex: ImageTexture = ImageTexture.create_from_image(emissionColorImage)

	particleProcessMat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINTS
	particleProcessMat.emission_point_texture = emissionTex
	particleProcessMat.emission_point_count = particleCount
	particleProcessMat.emission_color_texture = emissionColorTex
	self.amount = particleCount
	self.emitting = true

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):