Depth test fails on MultiMesh rendering

Godot Version

4.2.1

Question

Hi! I’m kinda new to Godot and I’m still learning, so sorry for any mistakes. I want to render 2D particles as Voronoi cells, and to do this I use a trick where each particle is an instance of a cone mesh, and the intersections between these cones generate the Voronoi graph. The problem here is that this effect is produced with depth testing, which should be enabled by default in spatial materials. Here is the effect I want to achieve.
I am using a MultiMesh to draw cones efficiently, since I plan to have thousands of particles, but they are drawn as if depth testing was disabled:


This is the shader I use per-instance:

shader_type spatial;

varying flat int id;

float map(float value, float min1, float max1, float min2, float max2) {
  return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}

void vertex() {
	id = INSTANCE_ID;
}

void fragment() {
	float r = map(float(id), 0.0, 100.0, 0.0, 1.0);
	
	ALBEDO = vec3(r, 1.0 - r, 0.0);
	ALPHA = 1.0;
}

Then I have a node containing the MultiMeshInstance3D object, which has the following script:

extends Node3D

@export var cone_mesh: ArrayMesh

func _ready():
	generate_random_particles(100)


func generate_random_particles(number: int):
	for i in range(number):
		var x = randf_range(-5.0, 5.0)
		var y = randf_range(-5.0, 5.0)
		var pos = Vector3(x, 0.0, y)
		
		$ParticlesSpace.generate_particle(pos) # ParticlesSpace is the MultiMeshInstance3D
	

Finally here’s the MultiMeshInstance3D script:

extends MultiMeshInstance3D

const sh_particle = preload("res://shaders/particle.tres")

var array_mesh: ArrayMesh
var multi_mesh_inst: MultiMeshInstance3D
var multi_mesh: MultiMesh

const STARTING_INSTANCE_COUNT = 128
const MULTIMESH_GROWTH_STEP = 64

func _ready():
	array_mesh = ArrayMesh.new()
	var st = SurfaceTool.new()
	st.begin(Mesh.PRIMITIVE_TRIANGLES)
	create_cone(st, 0.5, 1.0, 16)
	st.commit(array_mesh)
	
	multi_mesh = MultiMesh.new()
	multi_mesh.mesh = array_mesh
	multi_mesh.transform_format = MultiMesh.TRANSFORM_3D
	multi_mesh.instance_count = STARTING_INSTANCE_COUNT # We start with STARTING_INSTANCE_COUNT instances.
	multi_mesh.visible_instance_count = 0 # The particles are initially invisible.
	
	multimesh = multi_mesh
	material_override = sh_particle


func generate_particle(pos: Vector3):
	var part_id = multi_mesh.visible_instance_count
	
	# Not enough particles, grow
	if part_id == STARTING_INSTANCE_COUNT:
		grow()
		print_debug("Not enough particles. Growing. New size: ", multi_mesh.instance_count)
	
	multi_mesh.set_instance_transform(part_id, Transform3D(Basis(), pos))
	multi_mesh.visible_instance_count += 1


func grow():
	multi_mesh.instance_count += MULTIMESH_GROWTH_STEP


func create_cone(st: SurfaceTool, radius: float, height: float, segments: int):
	var angle_step = 2.0 * PI / segments
	var top = Vector3(0, height / 2, 0) # cone tip
	var bottom_center = Vector3(0, -height / 2, 0) # base center
	
	for i in range(segments):
		var rad = i * angle_step
		var x = sin(rad) * radius
		var z = cos(rad) * radius
		var next_rad = (i + 1) * angle_step
		var next_x = sin(next_rad) * radius
		var next_z = cos(next_rad) * radius

		var current_point = Vector3(x, -height / 2, z)
		var next_point = Vector3(next_x, -height / 2, next_z)
		
		st.add_vertex(top)
		st.add_vertex(current_point)
		st.add_vertex(next_point)
		st.add_vertex(bottom_center)
		st.add_vertex(next_point)
		st.add_vertex(current_point)
	
	# Idk if this will be needed
	# but it could be useful later
	st.generate_normals()
	

I’m using an orthogonal camera placed towards the cone tips.
Thank you in advance.

remove the ALPHA = 1.0 line

Ok, now the cones are joined together as expected, but the part which should not be visible is still drawn. One question: why does ALPHA = 1.0 force drawing over the other objects?


Having an ALPHA assignment in your shader code automatically sends the object in the transparent queue, with no depth write. Which means that then they draw just based on the order they arrive to the GPU, and they don’t intersect with other multimesh and other objects because they are drawin in a single draw call. In opaque theyre also drawn in a single draw call but they do influence the depth buffer so they influence other objects.

1 Like

Ok, thank you. I got it. But what is causing the artifacts in the images I posted? It seems the culled area are somehow flipped…

Nevermind, the artifacts are caused from the fact that I’m looking at the cones from below :dotted_line_face:
The main problem was the ALPHA line.