Normal of generated mesh weirdly rendered

Godot Version

v4.4 beta 3

Question

I am working with a 3D Mesh Instance for which I am generating a mesh.
All this works just fine but the addition of normal’s appears to not work properly. What am I doing wrong?

I have the vertices and indices correctly generated but the normal i calculate as the cross-product of two of the three edges, when applied to the mesh information, is displayed weirdly at first, and not at all after extending the mesh once more.
The normal’s themselves look alright, see below.


The look of the surface after shading does not. Why?

Thanks for your expertise in advance. The code might help a bit:

extends MeshInstance3D

@export var radius: float = 1.0
@export var material: StandardMaterial3D

@onready var camera: Camera3D = get_node("../Camera3D")

var is_first_point = true

var last_point: Vector3
const CIRCLE_RESOLUTION: int = 6

var vertices: PackedVector3Array = []
var indices: PackedInt32Array = []
var normals: PackedVector3Array = []

func _process(delta: float) -> void:
	debug_draw()

func extend_mesh(point: Vector3):
	# Set (initial) plant direction
	var direction: Vector3
	if is_first_point:
		direction = Vector3.UP
	else:
		direction = (point - last_point).normalized()
	
	# Generate disc (points in circle)
	var current_point: Vector3 = direction.cross(Vector3.FORWARD) # Start point on disc
	for i in range(CIRCLE_RESOLUTION):
		current_point = current_point.rotated(direction, TAU / CIRCLE_RESOLUTION)
		vertices.append(point + radius * current_point.normalized())
	
	# Generate face and normal information
	if not is_first_point:
		for left_column in range(CIRCLE_RESOLUTION):
			var right_column = (left_column + 1) % CIRCLE_RESOLUTION
			var top_row = -CIRCLE_RESOLUTION
			var bottom_row = -2 * CIRCLE_RESOLUTION
			
			var p1: int = len(vertices) + top_row + left_column # Top point left
			var p2: int = len(vertices) + bottom_row + left_column # Bottom point left
			var p3: int = len(vertices) + top_row + right_column # Top point right
			var p4: int = len(vertices) + bottom_row + right_column # Bottom point right
			
			indices.append_array([p3, p2, p1])
			normals.append(((vertices[p1]-vertices[p2]).cross(vertices[p1]-vertices[p3])).normalized())
			indices.append_array([p2, p3, p4])
			normals.append(-((vertices[p2]-vertices[p3]).cross(vertices[p2]-vertices[p4])).normalized())
		
	# Create mesh, set information
	self.mesh = ArrayMesh.new()
	var array = []
	array.resize(Mesh.ARRAY_MAX)
	array[Mesh.ARRAY_VERTEX] = vertices
	if not indices.is_empty():
		array[Mesh.ARRAY_INDEX] = indices
		array[Mesh.ARRAY_NORMAL] = normals
	self.mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, array)
	
	last_point = point
	is_first_point = false
	
	
func debug_draw() -> void:
	return
	print("\rVeticies: %s \tFaces: %s \tNormals: %s" % [len(vertices), len(indices) / 3, len(normals)])
	# Draw Vertices
	DebugDraw3D.draw_points(vertices, DebugDraw3D.POINT_TYPE_SPHERE, 0.02, Color.MEDIUM_VIOLET_RED)
	
	if indices.is_empty(): return
	for i in range(0, len(indices), 3):
		# Draw Triangles
		DebugDraw3D.draw_line(vertices[indices[i]], vertices[indices[i+1]], Color.BLUE_VIOLET)
		DebugDraw3D.draw_line(vertices[indices[i+1]], vertices[indices[i+2]], Color.BLUE_VIOLET)
		DebugDraw3D.draw_line(vertices[indices[i]], vertices[indices[i+2]], Color.BLUE_VIOLET)
		
		# Draw Normals
		var avg: Vector3 = (vertices[indices[i]] + vertices[indices[i+1]] + vertices[indices[i+2]]) / 3.0
		var dir: Vector3 = normals[i / 3]
		if dir.dot(camera.position - avg) < 0: continue
		DebugDraw3D.draw_arrow(avg, avg + dir * 0.5, Color.YELLOW, 0.1, true)

I figured it out. Godot expects not face but vertex normals.
Meaning instead of supplying a normal for every face, I instead should have supplied one for each vertex (here very simple, because I can just draw a vector from point to current_point and append it to normals for each generated disc vertex.)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.