I’m trying to make a 3d renderer but I can only get at most 20fps on idle.
I tried using @functools.lru_cache(maxsize=None)
on project_and_rotate()
and got it up to 40fps on idle.
Is there any way I could make this any faster?
I’m using a long math formula I found a few month ago but it seems to be to slow for the map in projected_des
.
from math import *
import pygame
import numpy
from functools import lru_cache
@lru_cache(maxsize=None)
def project_and_rotate(x, y, z,rotx,roty,rotz,posx,posy,posz,cx,cy,cz,scale,render_distance):
x,y,z=x-posx,y-posy,z-posz
if abs(x)>render_distance or abs(z)>render_distance:return None
px = (((x * cos(rotz) - sin(rotz) * y) * cos(roty) - z * sin(roty)) * (315 / ((((z * cos(roty) + (x * cos(rotz) - sin(rotz) * y) * sin(roty)) * cos(rotx) + (y * cos(rotz) + x * sin(rotz)) * sin(rotx)) + 5) + cz))) * scale + cx
py = (((y * cos(rotz) + x * sin(rotz)) * cos(rotx) - (z * cos(roty) + (x * cos(rotz) - sin(rotz) * y) * sin(roty)) * sin(rotx)) * (315 / ((((z * cos(roty) + (x * cos(rotz) - sin(rotz) * y) * sin(roty)) * cos(rotx) + (y * cos(rotz) + x * sin(rotz)) * sin(rotx)) + 5) + cz))) * scale + cy
return [round(px),round(py)]
class coordinate:
def __init__(self,x,y,z):
self.x=x
self.y=y
self.z=z
class verticies_structure:
def __init__(self):
self._verts=[]
def add_vert(self,x,y,z):
self._verts.append(coordinate(x,y,z))
def get_coords(self,indexes):
return self._verts[indexes[0]:indexes[1]]
class camera:
def __init__(self,w,h,render_distance,fov=45):
self.fov=360-fov
self.w=w
self.h=h
self.x=0
self.rx=0
self.cx=0
self.y=0
self.ry=0
self.cy=0
self.z=0
self.rz=0
self.cz=0
self.render_distance=render_distance
def false(f,value):
if value==f:
value=f+0.01
return value
def inf360(value):
if value>359:value=0
if value<0:value=359
return value
class mesh(object):
def __init__(self,file_obj,cam):
self.cam=cam
self.verts=verticies_structure()
self.source=file_obj
self.read_object_file()
self.verts=verticies_structure()
size=100
for x in range(size):
for z in range(size):
self.verts.add_vert(x-size//2,0,z-size//2)
self.w2s_vect=numpy.vectorize(self.w2s)
self.array_verts=numpy.array(self.verts._verts)
def w2s(self,coord):
cam=self.cam
return project_and_rotate(coord.x,coord.y,coord.z,cam.ry,cam.rx,cam.rz,cam.x,cam.y,cam.z,cam.cx,cam.cy,cam.cz,10,cam.render_distance)
def projected_des(self,cam):
#return self.w2s_vect(self.array_verts).tolist()
return map( lambda coord:project_and_rotate(coord.x,coord.y,coord.z,cam.ry,cam.rx,cam.rz,cam.x,cam.y,cam.z,cam.cx,cam.cy,cam.cz,10,cam.render_distance),self.verts.get_coords([0,-1]))
def read_object_file(self):
self.verts=verticies_structure()
import re
reComp = re.compile("(?<=^)(v |vn |vt |f )(.*)(?=$)", re.MULTILINE)
with open(self.source) as f:
data = [txt.group() for txt in reComp.finditer(f.read())]
v_arr, vn_arr, vt_arr, f_arr = [], [], [], []
for line in data:
tokens = line.split(' ')
if tokens[0] == 'v':
v_arr.append([float(c) for c in tokens[1:]])
elif tokens[0] == 'vn':
vn_arr.append([float(c) for c in tokens[1:]])
elif tokens[0] == 'vt':
vn_arr.append([float(c) for c in tokens[1:]])
elif tokens[0] == 'f':
f_arr.append([[int(i) if len(i) else 0 for i in c.split('/')] for c in tokens[1:]])
vertices, normals = [], []
for face in f_arr:
for tp in face:
self.verts.add_vert(*v_arr[tp[0]-1])
self.array_verts=numpy.array(self.verts._verts)
class draw:
class frame:
class pygame_uitl:
def grid(rowx,rowy,px,color=(255,255,255)):
display=pygame.display.get_surface()
for r in range(rowx):
r+=1
pygame.draw.line(display,color,(0,(display.get_height()/(rowx+1))*r),(display.get_width(),(display.get_height()/(rowx+1))*r),px)
for r in range(rowy):
r+=1
pygame.draw.line(display,color,((display.get_width()/(rowy+1))*r,0),((display.get_width()/(rowy+1))*r,display.get_height()),px)
class system:
class pygame_util:
def get_orientation():
inf=pygame.display.Info()
w,h=inf.current_w,inf.current_h
if w>h:
return 1
else:
return 0
class Drivers:
class Pygame:
DEFAULT="PG-default"
SDL2="PG-sdl2"
class master:
class scene:
def __init__(self,wh:list,display_driver:str,render_distance:int,fps:int):
self._model={
"class 1":[],
"class 2":[],
"class 3":[],
"class 4":[]}
self._fps=fps
self._window_wh=wh
self._driver=display_driver
self._camera=camera(*wh,render_distance)
self._mode="mesh"
self._super_ls=0
if display_driver==Drivers.Pygame.DEFAULT:
self._render_pygame_def_setup()
def add_model(self,file):
model=mesh(file,self._camera)
vertexes=len(model.verts._verts)
if vertexes>100:
self._model["class 4"].append(model)
elif vertexes>50:
self._model["class 3"].append(model)
elif vertexes>25:
self._model["class 2"].append(model)
else:
self._model["class 1"].append(model)
def regulate_camera(self):
self._camera.rx,self._camera.ry,self._camera.rz=false(0,self._camera.rx),false(0,self._camera.ry),false(0,self._camera.rz)
self._camera.cx,self._camera.cy,self._camera.cz=false(0,self._camera.cx),false(0,self._camera.cy),false(0,self._camera.cz)
def correct_camera(self,orient=1):
self._orient=orient
if orient:
self._camera.cx=self._window_wh[1]//2
self._camera.cy=self._window_wh[0]
self._camera.ry=0.4
else:
self._camera.cx=self._window_wh[0]//2
self._camera.cy=self._window_wh[1]
self._camera.ry=0.2
def auto_render_distance(self):
if self._driver==Drivers.Pygame.DEFAULT:
if self._pygame_clock.get_fps()+5>self._fps:
self._camera.render_distance+=1
else:
self._camera.render_distance-=1
def landscape_super(self):
self._super_ls=1
self._lss_hdri_file_jpg_surf=pygame.Surface([self._window_wh[0],self._window_wh[1]//2.01])
self._lss_hdri_file_jpg_surf.fill((200,220,255))
def _render_pygame_def_setup(self):
self._pygame_clock=pygame.time.Clock()
self._pygame_screen=pygame.display.set_mode((self._camera.w,self._camera.h),pygame.DOUBLEBUF|pygame.HWACCEL|pygame.HWSURFACE)
def _render_pygame_def_update(self):
self._pygame_screen.fill((0,70,0))
self.regulate_camera()
for idx,vclass in self._model.items():
for model in vclass:
for point in model.projected_des(self._camera):
if point!=None:
try:self._pygame_screen.set_at(point,(255,255,255))
except:pass
if self._super_ls:
self._pygame_screen.blit(self._lss_hdri_file_jpg_surf,(0,0))
def _render_pygame_def_finish(self):
pygame.display.flip()
self._pygame_clock.tick(self._fps)
scene=master.scene([2176,1080],Drivers.Pygame.DEFAULT,render_distance=25,fps=60)
scene.add_model("plane.obj")
scene.correct_camera(0)
scene.landscape_super()
#make the sky mapped to edge of render
pygame.font.init()
while 1:
rx,ry=pygame.mouse.get_rel()
scene._camera.rx+=rx/200
scene._render_pygame_def_update()
#scene.auto_render_distance()
scene._pygame_screen.blit(pygame.font.SysFont(None,60).render(str(scene._pygame_clock.get_fps()),None,(255,0,0)),(0,0))
scene._render_pygame_def_finish()
plane.obj
# Exported OBJ from Prisma3D Exporter v2018
g Plane
v 10 0 -10
v 10 0 10
v -10 0 10
v -10 0 -10
vn 0 0.07053456 0
vn 0 0.07053456 0
vn 0 0.07053456 0
vn 0 0.07053456 0
vt 0 0
vt 0 1
vt 1 1
vt 1 0
usemtl Material
f 3/3/3 2/2/2 1/1/1
f 3/3/3 1/1/1 4/4/4
-
1\$\begingroup\$ Is the mesh binary? If not, if it's text, please post it \$\endgroup\$Reinderien– Reinderien2023年01月05日 03:45:21 +00:00Commented Jan 5, 2023 at 3:45
-
\$\begingroup\$ i added the obj file \$\endgroup\$Roberto E. Torres– Roberto E. Torres2023年01月05日 04:12:35 +00:00Commented Jan 5, 2023 at 4:12
-
\$\begingroup\$ That's plane.obj and not cube.obj. Is that a naming problem or the wrong mesh? \$\endgroup\$Reinderien– Reinderien2023年01月05日 13:20:38 +00:00Commented Jan 5, 2023 at 13:20
-
\$\begingroup\$ i fixed the names \$\endgroup\$Roberto E. Torres– Roberto E. Torres2023年01月05日 16:14:16 +00:00Commented Jan 5, 2023 at 16:14
-
\$\begingroup\$ You should look into numpy and matrix math. \$\endgroup\$Tamoghna Chowdhury– Tamoghna Chowdhury2023年02月02日 12:49:41 +00:00Commented Feb 2, 2023 at 12:49
1 Answer 1
One problem is that you apply project_and_rotate to every point and recompute lots of sin and cos every time, even though the rot-values do not change.
Try rewriting project_and_rotate to pass sin(rotx), cos(rotx), etc to the function, instead of the angles rotx, rot,y, rotz.
This will be reflected in the call from project_des. Old code:
def projected_des(self, cam):
#return self.w2s_vect(self.array_verts).tolist()
prj_n_rot = lambda coord: project_and_rotate(
coord.x, coord.y, coord.z,
cam.ry, cam.rx, cam.rz,
cam.x, cam.y, cam.z,
cam.cx, cam.cy, cam.cz,
10,
cam.render_distance)
return map(prj_n_rot, self.verts.get_coords([0,-1]))
new code
def projected_des(self, cam):
#return self.w2s_vect(self.array_verts).tolist()
sin_cam_ry = sin(cam.ry)
cos_cam_ry = cos(cam.ry)
sin_cam_rx = sin(cam.rx)
cos_cam_rx = cos(cam.rx)
sin_cam_rz = sin(cam.rz)
cos_cam_rz = cos(cam.rz)
prj_n_rot = lambda coord: project_and_rotate(
coord.x, coord.y, coord.z,
sin_cam_ry, cos_cam_ry,
sin_cam_rx, cos_cam_rx,
sin_cam_rz, cos_cam_rz,
cam.x, cam.y, cam.z,
cam.cx, cam.cy, cam.cz,
10,
cam.render_distance)
return map(prj_n_rot, self.verts.get_coords([0,-1]))