• 3D
  • Make a Procedural Sphere (Using tool script and ArrayMesh)

This script should be everything you need to make a simple UV sphere, possibly exactly the same as the UV sphere in blender.

<details> <summary>Click this line to see code.</summary>

Put this on a regular node then add a MeshInstance child.

tool
extends Spatial

export (NodePath) var mesh_node = null setget set_mesh_node
func set_mesh_node(val):
	_clear()
	mesh_node = val
	_initialize()

export (int) var segments = 16 setget set_segments
func set_segments(val):
	_clear()
	segments = val
	segments = clamp(segments, 1, 256)
	_initialize()

export (int) var rings = 16 setget set_rings
func set_rings(val):
	_clear()
	rings = val
	rings = clamp(rings, 1, 256)
	_initialize()

export (float) var radius = 1.0 setget set_radius
func set_radius(val):
	_clear()
	radius = val
	if radius < 0.0:
		radius = 0.0
	_initialize()

export (Vector3) var Scale = Vector3(1,1,1) setget set_scale
func set_scale(val):
	_clear()
	Scale = val
	_initialize()


var mesh_nd : MeshInstance = null

var vertices : PoolVector3Array
var UVs : PoolVector2Array
var normals : PoolVector3Array
var indices : PoolIntArray

var is_initialized = false


func _generate_sphere():

	vertices = PoolVector3Array()
	UVs = PoolVector2Array()
	normals = PoolVector3Array()
	indices = PoolIntArray()

	# Vertex indices.
	var thisrow = 0
	var prevrow = 0
	var point = 0

	# Loop over rings.
	for i in range(rings + 1):
		var v = float(i) / rings
		var w = sin(PI * v)
		var y = cos(PI * v)

		# Loop over segments in ring.
		for j in range(segments):
			var u = float(j) / segments
			var x = sin(u * PI * 2.0)
			var z = cos(u * PI * 2.0)
			var vert = Vector3(x * radius * w * Scale.x, y * radius * Scale.y, z * radius * w * Scale.z)
			vertices.append(vert)
			normals.append(vert.normalized())
			UVs.append(Vector2(u, v))
			point += 1

			# Create triangles in ring using indices.
			if i > 0 and j > 0:
				indices.append(prevrow + j - 1)
				indices.append(prevrow + j)
				indices.append(thisrow + j - 1)

				indices.append(prevrow + j)
				indices.append(thisrow + j)
				indices.append(thisrow + j - 1)

		if i > 0:
			indices.append(prevrow + segments - 1)
			indices.append(prevrow)
			indices.append(thisrow + segments - 1)

			indices.append(prevrow)
			indices.append(prevrow + segments)
			indices.append(thisrow + segments - 1)

		prevrow = thisrow
		thisrow = point


func _generate_mesh():
	_generate_sphere()
	var mesh = ArrayMesh.new()
	var data = []
	data.resize(ArrayMesh.ARRAY_MAX)
	data[ArrayMesh.ARRAY_VERTEX] = vertices
	data[ArrayMesh.ARRAY_TEX_UV] = UVs
	data[ArrayMesh.ARRAY_INDEX] = indices
	data[ArrayMesh.ARRAY_NORMAL] = normals
	mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, data)
	mesh.regen_normalmaps()
	return mesh


func _initialize():
	if is_initialized:
		return
	
	if mesh_node == null:
		return
	
	mesh_nd = get_node_or_null(mesh_node)
	if mesh_nd == null:
		return
		
	mesh_nd.mesh = _generate_mesh()
	is_initialized = true

func _clear():
	if mesh_nd != null and is_initialized:
		is_initialized = false
		mesh_nd.mesh = null
	
func _ready():
	_initialize()

</details>

Note, I am only 20% sure this code is flawless. Would appreciate someone with related experience play around with this.

Oh. Now that can't be right. (camera on the inside)