Sure, here is the full script.

```
import numpy as np
from panda3d.core import *
from direct.filter.CommonFilters import CommonFilters
from direct.showbase.ShowBase import ShowBase
N_SPHERES = 15000
load_prc_file_data("", """
win-size 1600 900
window-title High performance spheres
framebuffer-multisample 1
multisamples 4
""")
base = ShowBase()
render = base.render
render.setAntialias(AntialiasAttrib.MMultisample)
# Original code from http://www.songho.ca/opengl/gl_sphere.html.
def make_sphere(stack_counts, sector_counts, radius=1):
sector_step = 2 * np.pi / sector_counts
stack_step = np.pi / stack_counts
vertices = []
normals = []
tex_coords = []
for i in range(stack_counts + 1):
stack_angle = np.pi / 2 - i * stack_step
xy = radius * np.cos(stack_angle)
z = radius * np.sin(stack_angle)
for j in range(sector_counts + 1):
sector_angle = j * sector_step
x = xy * np.cos(sector_angle)
y = xy * np.sin(sector_angle)
vertices += [x, y, z]
normals += [x/radius, y/radius, z/radius]
tex_coords += [j / sector_counts, i / stack_counts]
# Make indices.
indices = []
for i in range(stack_counts):
k1 = i * (sector_counts + 1)
k2 = k1 + sector_counts + 1
for j in range(sector_counts):
if i != 0:
indices += [k1, k2, k1 + 1]
if i != (stack_counts - 1):
indices += [k1 + 1, k2, k2 + 1]
k1 += 1
k2 += 1
vertices_data = np.array(vertices, dtype=np.float32).reshape(-1, 3)
normals_data = np.array(normals, dtype=np.float32).reshape(-1, 3)
uvs_data = np.array(tex_coords, dtype=np.float32).reshape(-1, 2)
indices = np.array(indices, dtype=np.uint32)
data = {
"vertices": vertices_data,
"normals": normals_data,
"texcoords": uvs_data,
"indices": indices,
}
return data
# Generate custom sphere.
data = make_sphere(stack_counts=20, sector_counts=20, radius=1)
array_format = GeomVertexArrayFormat()
array_format.addColumn("vertex", 3, Geom.NTFloat32, Geom.CPoint)
array_format.addColumn("normal", 3, Geom.NTFloat32, Geom.CNormal)
array_format.addColumn("texcoord", 2, Geom.NTFloat32, Geom.CTexcoord)
array_format.addColumn("color", 4, Geom.NTUint8, Geom.CColor)
vertex_format = GeomVertexFormat(array_format)
vertex_format = GeomVertexFormat.registerFormat(vertex_format)
vertex_data = GeomVertexData("sphere", vertex_format, Geom.UHStatic)
n_verts = len(data["vertices"])
n_rows = N_SPHERES * n_verts
vertex_data.uncleanSetNumRows(n_rows)
data_array = vertex_data.modify_array(0)
memview = memoryview(data_array).cast("B").cast("f")
# Set indices for primitive.
n_inds = len(data["indices"])
primitive = GeomTriangles(Geom.UHStatic)
primitive.setIndexType(Geom.NTUint32)
tris_array = primitive.modify_vertices()
tris_array.uncleanSetNumRows(N_SPHERES * n_inds)
prim_memview = memoryview(tris_array).cast("B").cast("I")
# 3 + 3 + 2 + 1.
strides = 9
for i in range(N_SPHERES):
color = [
np.random.randint(0, 255),
np.random.randint(0, 255),
np.random.randint(0, 255),
255
]
# Convert 4 np.uint8 values to 1 np.float32 value.
color_data = np.tile(
np.array(color, dtype=np.uint8),
reps=(n_verts, 1),
).view(np.float32)
scale = np.random.uniform(0.1, 1)
pos = np.random.uniform(-10, 10, size=(1, 3)).astype(np.float32)
all_data = np.concatenate([
scale*data["vertices"]+pos,
data["normals"],
data["texcoords"],
color_data
],
axis=1,
).copy()
data_memview = memoryview(all_data).cast("B").cast("f")
memview[i*n_verts*strides : (i+1)*n_verts*strides] = data_memview
# Apply some transformation.
#scale_mat = Mat4.scaleMat(1, 0.7, 2.0)
#vertex_data.transformVertices(scale_mat)
# Set indices for primitive.
idx = (data["indices"] + i*n_verts).astype(np.uint32)
data_memview = memoryview(idx).cast("B").cast("I")
prim_memview[i*n_inds : (i+1)*n_inds] = data_memview
geom = Geom(vertex_data)
geom.addPrimitive(primitive)
node = GeomNode("gnode")
node.addGeom(geom)
node_path = render.attachNewNode(node)
node_path.setPos(0, -100, 100)
node_path.setScale(100)
myMaterial = Material()
myMaterial.setShininess(125.0)
myMaterial.setSpecular((0.8, 0.8, 0.8, 1))
node_path.setMaterial(myMaterial)
# Position the camera.
base.trackball.node().setPos(0, 4000, -100)
base.camLens.setFov(70)
# Create Ambient Light
ambientLight = AmbientLight('ambientLight')
ambientLight.setColor((0.7, 0.7, 0.7, 1))
ambientLightNP = render.attachNewNode(ambientLight)
render.setLight(ambientLightNP)
# Directional light 02
directionalLight = DirectionalLight('directionalLight')
directionalLight.setColor((1, 1, 1, 1))
directionalLightNP = render.attachNewNode(directionalLight)
# This light is facing forwards, away from the camera.
directionalLightNP.setHpr(50, -30, 50)
render.setLight(directionalLightNP)
# Add shadow caster.
directionalLight.setShadowCaster(True, 1024, 1024)
directionalLight.getLens().setFilmSize(1000, 1000)
directionalLight.getLens().setNearFar(-500, 500)
render.setShaderAuto()
# Apply SSAO.
base.camLens.setNearFar(100, -500)
filters = CommonFilters(base.win, base.cam)
filters.setAmbientOcclusion(numsamples=16, strength=0.01, radius=0.015, amount=1)
base.run()
```