How to calculate vertex normals of custom cylinder

A CylinderGeometry with 24 radial segments has 148 vertices and 96 indices, but that could be optimized to 49 vertices (24 for base + 1 NaN + 24 for top) and 92 indices (22 for base + 22 for top + 2 times 24 for body).

My problem is that I don’t know how to calculate the vertex normals for my “cylinder algorithm”.

geometry.calculateVertexNormals() doesn’t works. The result of this is:
image

My code:

const cylinderGeometry = new BufferGeometry()
	.setAttribute(
		'position',
		new BufferAttribute(new Float32Array([
			// cylinder base
			0, -1, 0,
			0.2588, -0.9659, 0,
			0.5, -0.866, 0,
			0.7071, -0.7071, 0,
			0.866, -0.5, 0,
			0.9659, -0.2588, 0,
			1, 0, 0,
			0.9659, 0.2588, 0,
			0.866, 0.5, 0,
			0.7071, 0.7071, 0,
			0.5, 0.866, 0,
			0.2588, 0.9659, 0,
			0, 1, 0,
			-0.2588, 0.9659, 0,
			-0.5, 0.866, 0,
			-0.7071, 0.7071, 0,
			-0.866, 0.5, 0,
			-0.9659, 0.2588, 0,
			-1, 0, 0,
			-0.9659, -0.2588, 0,
			-0.866, -0.5, 0,
			-0.7071, -0.7071, 0,
			-0.5, -0.866, 0,
			-0.2588, -0.9659, 0,
			0, -1, 0,

			// This position attribute is also used in the edges.
			// The edges are drawn with Line instead of LineSegments for better
			// performance, but lines, differently from line segements are continuous,
			// except if we add NaNs between the lines.
			// HACK:
			NaN, NaN, NaN,

			// cylinder top
			0, -1, -1,
			0.2588, -0.9659, -1,
			0.5, -0.866, -1,
			0.7071, -0.7071, -1,
			0.866, -0.5, -1,
			0.9659, -0.2588, -1,
			1, 0, -1,
			0.9659, 0.2588, -1,
			0.866, 0.5, -1,
			0.7071, 0.7071, -1,
			0.5, 0.866, -1,
			0.2588, 0.9659, -1,
			0, 1, -1,
			-0.2588, 0.9659, -1,
			-0.5, 0.866, -1,
			-0.7071, 0.7071, -1,
			-0.866, 0.5, -1,
			-0.9659, 0.2588, -1,
			-1, 0, -1,
			-0.9659, -0.2588, -1,
			-0.866, -0.5, -1,
			-0.7071, -0.7071, -1,
			-0.5, -0.866, -1,
			-0.2588, -0.9659, -1,
			0, -1, -1
		]), 3)
	)
	.setIndex([
		// cylinder base
		1, 2, 3,
		3, 4, 5,
		3, 5, 7,
		5, 6, 7,
		7, 8, 9,
		7, 9, 11,
		7, 11, 15,
		9, 10, 11,
		11, 12, 13,
		11, 13, 15,
		13, 14, 15,
		15, 16, 17,
		15, 17, 19,
		15, 19, 23,
		17, 18, 19,
		19, 20, 21,
		19, 21, 23,
		21, 22, 23,
		23, 0, 1,
		23, 1, 3,
		23, 3, 7,
		23, 7, 15,

		// cylinder top
		28, 27, 26,
		30, 29, 28,
		32, 30, 28,
		32, 31, 30,
		34, 33, 32,
		36, 34, 32,
		40, 36, 32,
		36, 35, 34,
		38, 37, 36,
		40, 38, 36,
		40, 39, 38,
		42, 41, 40,
		44, 42, 40,
		48, 44, 40,
		44, 43, 42,
		46, 45, 44,
		48, 46, 44,
		48, 47, 46,
		26, 25, 48,
		28, 26, 48,
		32, 28, 48,
		40, 32, 48,

		// cylinder body

		26, 1, 0,
		26, 27, 1,

		27, 2, 1,
		27, 28, 2,

		28, 3, 2,
		28, 29, 3,

		29, 4, 3,
		29, 30, 4,

		30, 5, 4,
		30, 31, 5,

		31, 6, 5,
		31, 32, 6,

		32, 7, 6,
		32, 33, 7,

		33, 8, 7,
		33, 34, 8,

		34, 9, 8,
		34, 35, 9,

		35, 10, 9,
		35, 36, 10,

		36, 11, 10,
		36, 37, 11,

		37, 12, 11,
		37, 38, 12,

		38, 13, 12,
		38, 39, 13,

		39, 14, 13,
		39, 40, 14,

		40, 15, 14,
		40, 41, 15,

		41, 16, 15,
		41, 42, 16,

		42, 17, 16,
		42, 43, 17,

		43, 18, 17,
		43, 44, 18,

		44, 19, 18,
		44, 45, 19,

		45, 20, 19,
		45, 46, 20,

		46, 21, 20,
		46, 47, 21,

		47, 22, 21,
		47, 48, 22,

		48, 23, 22,
		48, 49, 23,

		49, 24, 23,
		49, 50, 24,
	]);

It can’t work with NaNs. You may notice a message in the console, that the framework fails with .computeBoundingSphere for your geometry because of NaNs.

Reminded me of this topic: Using NaN in attributes

@prisoner849 that doesn’t work even without NaNs.
image

CylinderGeometry version:
image

Yeah, my mistake. Computation of normals seems work even with NaNs.
And those weird “stripes”-like shades on your custom cylinder are caused by shared vertices for side faces and for top or bottom faces.
In CylinderGeometry, top and bottom caps have their own set of vertices.

If I would want a cylinder with indexed geometry, build of 24 radial segments, I’d chose a geometry with 96 vertices (48 for side faces, 48 for top and bottopm lids), and for lines (LineSegments), I’d use an indexed geometry, that shares the same buffer attribute for positions of the cylinder geometry, and its index would refer to the first 48 vertices only.
And the result looks good for me (smooth shading, no striped artifacts): Edit fiddle - JSFiddle - Code Playground

PS Optimization for the sake of optimization (the attempt to use 48 vertices for everyting) is not a very good way, IMHO.

1 Like