Need help on how to apply grey scale Applying greyscale shading to objects in a 3dPython 3D renderer i made in python, the code is pasted below
import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import * import math import numpy as np
pygame.init() screen_size = (800, 600) HEIGHT = 600 color = 0 WIDTH = 800 screen = pygame.display.set_mode(screen_size) angle = 0 clock = pygame.time.Clock()
z = 600
f = 400 centre_x = 400 centre_y = 300 line_width = 600 v4 = 0 z = 100 FOV = math.tan(60) x_left,x_right,y_left,y_rigth=0,0,0,0
vertices1 = np.array([
[-200, -200, -200],
[-100, -200, -200],
[-100, -100, -200],
[-200, -100, -200],
[-200, -200, -100],
[-100, -200, -100],
[-100, -100, -100],
[-200, -100, -100]
])
dt = 0
vertices = [] triangles = [] texture = pygame.image.load("D:/3dI need help with how to apply greyscale shading to objects/pexels-pixabay-207142_texture.jpg").convert_alpha() #not used
with open("C:/Users/Sohan/Desktop/game final/Low_Poly_AK_47.obj", "r") as g:
for line in g:
if line.startswith("v "): # vertex
parts = line.strip().split()[1:]
vertices.append([float(p) for p in parts])
elif line.startswith("f "): # face
parts = line.strip().split()[1:]
face = []
for p in parts:
# OBJ is 1-indexed, we convert to 0-indexed
idx = int(p.split("/")[0]) - 1
face.append(idx)
# triangulate if face has more than 3 vertices
if len(face) == 3:
triangles.append(tuple(face))
elif len(face) == 4:
# quad → 2 triangles
triangles.append((face[0], face[1], face[2]))
triangles.append((face[0], face[2], face[3]))
else:
# For polygons >4, simple fan triangulation
for i in range(1, len(face)-1):
triangles.append((face[0], face[i], face[i+1]))
vertices = np.array(vertices)
vertices = np.array([ [-1, -1, -1], [ 1, -1, -1], [ 1, 1, -1], [-1, 1, -1], [-1, -1, 1], [ 1, -1, 1], [ 1, 1, 1], [-1, 1, 1] ], dtype=float)
triangles = np.array([ [0, 1, 2], [0, 2, 3], # back [4, 5, 6], [4, 6, 7], # front [0, 1, 5], [0, 5, 4], # bottom [2, 3, 7], [2, 7, 6], # top [0, 3, 7], [0, 7, 4], # left [1, 2, 6], [1, 6, 5] # right ])
uvs = np.array([ [0, 0], [1, 0], [1, 1], [0, 1], [0, 0], [1, 0], [1, 1], [0, 1] ], dtype=float) near = 1
print("Vertices:", len(vertices)) print("Triangles:", len(triangles)) scale = 50 vertices = vertices * (scale)/10
def get_normal(v1,v2,v3):
normal = np.cross((v2 - v1),(v3 - v1))
return normal*-1
def get_shadow(v1,v2,v3):
#camera = 0,0,0
normal = get_normal(v1,v2,v3)
l1_cam = (v1 + v2 + v3)/3
unit_l1_cam = l1_cam/np.linalg.norm(l1_cam)
unit_normal = normal/np.linalg.norm(normal)
theta = np.dot(unit_normal,unit_l1_cam)
# camera/light at origin
light_unit = unit_l1_cam
intensity = max(0, theta)
color = int(255 * intensity)
theta_rad = np.arccos(np.clip(theta, -1.0, 1.0))
theta_deg = np.degrees(theta_rad)
print(theta_deg)
return (color,color,color)
def get_shadow(v1, v2, v3):
# Compute normal
normal = get_normal(v1, v2, v3)
normal = normal / np.linalg.norm(normal)
# Light direction from origin (camera)
tri_center = (v1 + v2 + v3) / 3
light_dir = -tri_center
light_dir = light_dir / np.linalg.norm(light_dir)
# Intensity
intensity = np.dot(normal, light_dir)
intensity = np.clip(intensity, 0, 1) # clamp to 0..1
color_val = int(255 * intensity)
return (color_val, color_val, color_val)
def total_in_points(v1,v2,v3):
count = 0
in_points = []
for v in [v1, v2, v3]:
if v[2] > near :
count += 1
in_points.append(v[2])
return in_points
def clipping_triangles(v1,v2,v3): points_arr = total_in_points(v1,v2,v3)
#points_arr[0] = point,z,1
#points_arr[1] = point,z,2
if len(points_arr) == 3 :
tri = np.array([v1,v2,v3])
return tri
elif len(points_arr) == 2:
# Separate inside vs outside vertices
vertices_list = [v1, v2, v3]
inside = [v for v in vertices_list if v[2] > near]
outside = [v for v in vertices_list if v[2] <= near][0]
i1 = outside + (inside[0] - outside) * (near - outside[2]) / (inside[0][2] - outside[2])
i2 = outside + (inside[1] - outside) * (near - outside[2]) / (inside[1][2] - outside[2])
# 2 new triangles
tri_new_1 = np.array([inside[0], inside[1], i1])
tri_new_2 = np.array([i1, inside[1], i2])
return tri_new_1, tri_new_2
elif len(points_arr) == 1:
vertices_list = [v1 , v2 , v3]
inside = [v for v in vertices_list if v[2] > near][0]
outside = [v for v in vertices_list if v[2] <= near]
i1 = inside + (outside[0] - inside) * (near - inside[2])/(outside[0][2] - inside[2])
i2 = inside + (outside[1] - inside) * (near - inside[2])/(outside[1][2] - inside[2])
tri_new = np.array([inside,i2,i1])
return tri_new
if len(points_arr) == 0 : return None
def projected_tris(list_proj): list_proj = [] return list_proj
def get_points(tri, projected_triangles):
projected_new = []
for v in tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
projected_new.append((x1, y1))
# Append the projected triangle
projected_triangles.append(projected_new)
def draw_tri(tri, vertexes): projected_triangles = [] for tri1 in tri: v1, v2, v3 = [vertexes[i] for i in tri1]
clipped_points = clipping_triangles(v1, v2, v3)
if clipped_points is None:
continue
tris_to_draw = clipped_points if isinstance(clipped_points, tuple) else [clipped_points]
for tri_tri in tris_to_draw:
tri_proj = []
for v in tri_tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
tri_proj.append((x1, y1))
projected_triangles.append((tri_proj))
return projected_triangles
def draw_triangels(tri_angle): for tri_proja 3D renderer I made in tri_angle: pygame.draw.polygon(screen,(255,255,255),tri_proj,1)
tx,ty,tz = 0,0,800 def translated_matrix(tx,ty,tz): return np.array([[1,0,0,tx], [0,1,0,ty], [0,0,1,tz], [0,0,1,0]])
def rotation_matrix(ang): c = math.cos(ang) s = math.sin(ang) return npPython.array([ [c, 0, s, 0], [0, 1, 0, 0], [-s,0, c, 0], [0, 0, 0, 1] ]) def sort_tri_z(v1,v2,v3) The code is pasted below:pass
def topygamecoords(x,y): x = x + 400 y = y + 300 return x,y
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import numpy as np
pygame.init()
screen_size = (800, 600)
HEIGHT = 600
color = 0
WIDTH = 800
screen = pygame.display.set_mode(screen_size)
angle = 0
clock = pygame.time.Clock()
# z = 600
f = 400
centre_x = 400
centre_y = 300
line_width = 600
v4 = 0
z = 100
FOV = math.tan(60)
x_left,x_right,y_left,y_rigth=0,0,0,0
# vertices1 = np.array([
# [-200, -200, -200],
# [-100, -200, -200],
# [-100, -100, -200],
# [-200, -100, -200],
# [-200, -200, -100],
# [-100, -200, -100],
# [-100, -100, -100],
# [-200, -100, -100]
# ])
dt = 0
vertices = []
triangles = []
texture = pygame.image.load("D:/3d objects/pexels-pixabay-207142_texture.jpg").convert_alpha() #not used
# with open("C:/Users/Sohan/Desktop/game final/Low_Poly_AK_47.obj", "r") as g:
# for line in g:
# if line.startswith("v "): # vertex
# parts = line.strip().split()[1:]
# vertices.append([float(p) for p in parts])
# elif line.startswith("f "): # face
# parts = line.strip().split()[1:]
# face = []
# for p in parts:
# # OBJ is 1-indexed, we convert to 0-indexed
# idx = int(p.split("/")[0]) - 1
# face.append(idx)
# # triangulate if face has more than 3 vertices
# if len(face) == 3:
# triangles.append(tuple(face))
# elif len(face) == 4:
# # quad → 2 triangles
# triangles.append((face[0], face[1], face[2]))
# triangles.append((face[0], face[2], face[3]))
# else:
# # For polygons >4, simple fan triangulation
# for i in range(1, len(face)-1):
# triangles.append((face[0], face[i], face[i+1]))
# vertices = np.array(vertices)
vertices = np.array([
[-1, -1, -1],
[ 1, -1, -1],
[ 1, 1, -1],
[-1, 1, -1],
[-1, -1, 1],
[ 1, -1, 1],
[ 1, 1, 1],
[-1, 1, 1]
], dtype=float)
triangles = np.array([
[0, 1, 2], [0, 2, 3], # back
[4, 5, 6], [4, 6, 7], # front
[0, 1, 5], [0, 5, 4], # bottom
[2, 3, 7], [2, 7, 6], # top
[0, 3, 7], [0, 7, 4], # left
[1, 2, 6], [1, 6, 5] # right
])
uvs = np.array([
[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0, 0],
[1, 0],
[1, 1],
[0, 1]
], dtype=float)
near = 1
print("Vertices:", len(vertices))
print("Triangles:", len(triangles))
scale = 50
vertices = vertices * (scale)/10
# def get_normal(v1,v2,v3):
# normal = np.cross((v2 - v1),(v3 - v1))
# return normal*-1
# def get_shadow(v1,v2,v3):
# #camera = 0,0,0
# normal = get_normal(v1,v2,v3)
# l1_cam = (v1 + v2 + v3)/3
# unit_l1_cam = l1_cam/np.linalg.norm(l1_cam)
# unit_normal = normal/np.linalg.norm(normal)
# theta = np.dot(unit_normal,unit_l1_cam)
# # camera/light at origin
# light_unit = unit_l1_cam
# intensity = max(0, theta)
# color = int(255 * intensity)
# theta_rad = np.arccos(np.clip(theta, -1.0, 1.0))
# theta_deg = np.degrees(theta_rad)
# print(theta_deg)
# return (color,color,color)
# def get_shadow(v1, v2, v3):
# # Compute normal
# normal = get_normal(v1, v2, v3)
# normal = normal / np.linalg.norm(normal)
# # Light direction from origin (camera)
# tri_center = (v1 + v2 + v3) / 3
# light_dir = -tri_center
# light_dir = light_dir / np.linalg.norm(light_dir)
# # Intensity
# intensity = np.dot(normal, light_dir)
# intensity = np.clip(intensity, 0, 1) # clamp to 0..1
# color_val = int(255 * intensity)
# return (color_val, color_val, color_val)
def total_in_points(v1,v2,v3):
count = 0
in_points = []
for v in [v1, v2, v3]:
if v[2] > near :
count += 1
in_points.append(v[2])
return in_points
def clipping_triangles(v1,v2,v3):
points_arr = total_in_points(v1,v2,v3)
#points_arr[0] = point,z,1
#points_arr[1] = point,z,2
if len(points_arr) == 3 :
tri = np.array([v1,v2,v3])
return tri
elif len(points_arr) == 2:
# Separate inside vs outside vertices
vertices_list = [v1, v2, v3]
inside = [v for v in vertices_list if v[2] > near]
outside = [v for v in vertices_list if v[2] <= near][0]
i1 = outside + (inside[0] - outside) * (near - outside[2]) / (inside[0][2] - outside[2])
i2 = outside + (inside[1] - outside) * (near - outside[2]) / (inside[1][2] - outside[2])
# 2 new triangles
tri_new_1 = np.array([inside[0], inside[1], i1])
tri_new_2 = np.array([i1, inside[1], i2])
return tri_new_1, tri_new_2
elif len(points_arr) == 1:
vertices_list = [v1 , v2 , v3]
inside = [v for v in vertices_list if v[2] > near][0]
outside = [v for v in vertices_list if v[2] <= near]
i1 = inside + (outside[0] - inside) * (near - inside[2])/(outside[0][2] - inside[2])
i2 = inside + (outside[1] - inside) * (near - inside[2])/(outside[1][2] - inside[2])
tri_new = np.array([inside,i2,i1])
return tri_new
if len(points_arr) == 0 : return None
def projected_tris(list_proj):
list_proj = []
return list_proj
def get_points(tri, projected_triangles):
projected_new = []
for v in tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
projected_new.append((x1, y1))
# Append the projected triangle
projected_triangles.append(projected_new)
def draw_tri(tri, vertexes):
projected_triangles = []
for tri1 in tri:
v1, v2, v3 = [vertexes[i] for i in tri1]
clipped_points = clipping_triangles(v1, v2, v3)
if clipped_points is None:
continue
tris_to_draw = clipped_points if isinstance(clipped_points, tuple) else [clipped_points]
for tri_tri in tris_to_draw:
tri_proj = []
for v in tri_tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
tri_proj.append((x1, y1))
projected_triangles.append((tri_proj))
return projected_triangles
def draw_triangels(tri_angle):
for tri_proj in tri_angle:
pygame.draw.polygon(screen,(255,255,255),tri_proj,1)
tx,ty,tz = 0,0,800
def translated_matrix(tx,ty,tz):
return np.array([[1,0,0,tx],
[0,1,0,ty],
[0,0,1,tz],
[0,0,1,0]])
def rotation_matrix(ang):
c = math.cos(ang)
s = math.sin(ang)
return np.array([
[c, 0, s, 0],
[0, 1, 0, 0],
[-s,0, c, 0],
[0, 0, 0, 1]
])
def sort_tri_z(v1,v2,v3):pass
def topygamecoords(x,y):
x = x + 400
y = y + 300
return x,y
while True:
for event in pygame.event.get():
if event.type == QUIT:
#running = False
pygame.quit()
exit()
screen.fill((0,0,0))
dt = clock.tick(60) / 1000.0
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
tz -= 5
if keys[pygame.K_s]:
tz += 5
if keys[pygame.K_a]:
tx += 5
if keys[pygame.K_d]:
tx -= 5
if keys[pygame.K_q]:
ty += 5
if keys[pygame.K_e]:
ty -= 5
# angle = math.cos(a)
line_width = (WIDTH*600)/z
rotated = rotation_matrix(angle)
translate = translated_matrix(tx,ty,tz)
#rotated_finish = np.dot(vertices, rotation_matrix(angle).T)
vertices_transformed = []
projected_triangles = []
for v in vertices:
v4 = np.append(v,1)
v_trans = translate@rotated@v4
vertices_transformed.append(v_trans[:3])
projected = draw_tri(tri=triangles,vertexes=vertices_transformed)
#texture_scan(vertices_transformed[0],vertices_transformed[1],vertices_transformed[2],screen)
if not keys[pygame.K_k]:
for tri_proj in projected:
#tri_color = get_shadow(tri_proj[0],tri_proj[1],tri_proj[2])
pygame.draw.polygon(screen, (255,255,255), tri_proj)
# if len(tri_proj) == 3:
# texture_scan(tri_proj[0], tri_proj[1], tri_proj[2], screen)
pygame.display.set_caption(str(clock.get_fps()))
pygame.display.flip()
angle+=0.01
# clock.tick(60)
while True: for event in pygame.event.get(): if event.type == QUIT: #running = False pygame.quit() exit() screen.fill((0,0Now,0)) dt = clock.tick(60) / 1000.0 keys = pygame.key.get_pressed() if keys[pygame.K_w]: tz -= 5 if keys[pygame.K_s]: tz += 5 if keys[pygame.K_a]: tx += 5 if keys[pygame.K_d]: tx -= 5 if keys[pygame.K_q]: ty += 5 if keys[pygame I've tried a basic method of taking the normal to a triangle in the commented out sections.K_e]: ty -= 5 # angle = math Now the shape is sort of shading but it's not correct.cos(a)
line_width = (WIDTH*600)/z
rotated = rotation_matrix(angle)
translate = translated_matrix(tx,ty,tz)
#rotated_finish = np.dot(vertices, rotation_matrix(angle).T)
vertices_transformed = []
projected_triangles = []
for v in vertices:
v4 = np.append(v,1)
v_trans = translate@rotated@v4
vertices_transformed.append(v_trans[:3])
projected = draw_tri(tri=triangles,vertexes=vertices_transformed)
#texture_scan(vertices_transformed[0],vertices_transformed[1],vertices_transformed[2],screen)
if not keys[pygame.K_k]:
for tri_proj in projected:
#tri_color = get_shadow(tri_proj[0],tri_proj[1],tri_proj[2])
pygame.draw.polygon(screen, (255,255,255), tri_proj)
# if len(tri_proj) == 3:
# texture_scan(tri_proj[0], tri_proj[1], tri_proj[2], screen)
pygame.display.set_caption(str(clock.get_fps()))
pygame.display.flip()
angle+=0.01
# clock.tick(60)
Where am I going wrong with my calculations? I did try playing around a bit with the signs but that didn't help. Also I stopped the rotation to see if things got better(they didn't).
Now, ive tried a basic method of taking the normal to a triangle in the commented out sections.. Now welp the shape is sort of shading but idk its not correct. Where am i going wrong with my calculations. I did try playing around a bit with the signs but that didnt help. Also i stopped the rotation to see if things got better(they didnt). One issue might be that imI'm not really taking the correct slope from my camera to the triangle, but imI'm not sure if thatsthat's the sole reason. I did try rendering it as a static object placed very close to my camera, so i didntit didn't move / change its coordinates, but that too gave me flawed results. Any sort of help is appreciated.
Need help on how to apply grey scale shading to objects in a 3d renderer i made in python, the code is pasted below
import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import * import math import numpy as np
pygame.init() screen_size = (800, 600) HEIGHT = 600 color = 0 WIDTH = 800 screen = pygame.display.set_mode(screen_size) angle = 0 clock = pygame.time.Clock()
z = 600
f = 400 centre_x = 400 centre_y = 300 line_width = 600 v4 = 0 z = 100 FOV = math.tan(60) x_left,x_right,y_left,y_rigth=0,0,0,0
vertices1 = np.array([
[-200, -200, -200],
[-100, -200, -200],
[-100, -100, -200],
[-200, -100, -200],
[-200, -200, -100],
[-100, -200, -100],
[-100, -100, -100],
[-200, -100, -100]
])
dt = 0
vertices = [] triangles = [] texture = pygame.image.load("D:/3d objects/pexels-pixabay-207142_texture.jpg").convert_alpha() #not used
with open("C:/Users/Sohan/Desktop/game final/Low_Poly_AK_47.obj", "r") as g:
for line in g:
if line.startswith("v "): # vertex
parts = line.strip().split()[1:]
vertices.append([float(p) for p in parts])
elif line.startswith("f "): # face
parts = line.strip().split()[1:]
face = []
for p in parts:
# OBJ is 1-indexed, we convert to 0-indexed
idx = int(p.split("/")[0]) - 1
face.append(idx)
# triangulate if face has more than 3 vertices
if len(face) == 3:
triangles.append(tuple(face))
elif len(face) == 4:
# quad → 2 triangles
triangles.append((face[0], face[1], face[2]))
triangles.append((face[0], face[2], face[3]))
else:
# For polygons >4, simple fan triangulation
for i in range(1, len(face)-1):
triangles.append((face[0], face[i], face[i+1]))
vertices = np.array(vertices)
vertices = np.array([ [-1, -1, -1], [ 1, -1, -1], [ 1, 1, -1], [-1, 1, -1], [-1, -1, 1], [ 1, -1, 1], [ 1, 1, 1], [-1, 1, 1] ], dtype=float)
triangles = np.array([ [0, 1, 2], [0, 2, 3], # back [4, 5, 6], [4, 6, 7], # front [0, 1, 5], [0, 5, 4], # bottom [2, 3, 7], [2, 7, 6], # top [0, 3, 7], [0, 7, 4], # left [1, 2, 6], [1, 6, 5] # right ])
uvs = np.array([ [0, 0], [1, 0], [1, 1], [0, 1], [0, 0], [1, 0], [1, 1], [0, 1] ], dtype=float) near = 1
print("Vertices:", len(vertices)) print("Triangles:", len(triangles)) scale = 50 vertices = vertices * (scale)/10
def get_normal(v1,v2,v3):
normal = np.cross((v2 - v1),(v3 - v1))
return normal*-1
def get_shadow(v1,v2,v3):
#camera = 0,0,0
normal = get_normal(v1,v2,v3)
l1_cam = (v1 + v2 + v3)/3
unit_l1_cam = l1_cam/np.linalg.norm(l1_cam)
unit_normal = normal/np.linalg.norm(normal)
theta = np.dot(unit_normal,unit_l1_cam)
# camera/light at origin
light_unit = unit_l1_cam
intensity = max(0, theta)
color = int(255 * intensity)
theta_rad = np.arccos(np.clip(theta, -1.0, 1.0))
theta_deg = np.degrees(theta_rad)
print(theta_deg)
return (color,color,color)
def get_shadow(v1, v2, v3):
# Compute normal
normal = get_normal(v1, v2, v3)
normal = normal / np.linalg.norm(normal)
# Light direction from origin (camera)
tri_center = (v1 + v2 + v3) / 3
light_dir = -tri_center
light_dir = light_dir / np.linalg.norm(light_dir)
# Intensity
intensity = np.dot(normal, light_dir)
intensity = np.clip(intensity, 0, 1) # clamp to 0..1
color_val = int(255 * intensity)
return (color_val, color_val, color_val)
def total_in_points(v1,v2,v3):
count = 0
in_points = []
for v in [v1, v2, v3]:
if v[2] > near :
count += 1
in_points.append(v[2])
return in_points
def clipping_triangles(v1,v2,v3): points_arr = total_in_points(v1,v2,v3)
#points_arr[0] = point,z,1
#points_arr[1] = point,z,2
if len(points_arr) == 3 :
tri = np.array([v1,v2,v3])
return tri
elif len(points_arr) == 2:
# Separate inside vs outside vertices
vertices_list = [v1, v2, v3]
inside = [v for v in vertices_list if v[2] > near]
outside = [v for v in vertices_list if v[2] <= near][0]
i1 = outside + (inside[0] - outside) * (near - outside[2]) / (inside[0][2] - outside[2])
i2 = outside + (inside[1] - outside) * (near - outside[2]) / (inside[1][2] - outside[2])
# 2 new triangles
tri_new_1 = np.array([inside[0], inside[1], i1])
tri_new_2 = np.array([i1, inside[1], i2])
return tri_new_1, tri_new_2
elif len(points_arr) == 1:
vertices_list = [v1 , v2 , v3]
inside = [v for v in vertices_list if v[2] > near][0]
outside = [v for v in vertices_list if v[2] <= near]
i1 = inside + (outside[0] - inside) * (near - inside[2])/(outside[0][2] - inside[2])
i2 = inside + (outside[1] - inside) * (near - inside[2])/(outside[1][2] - inside[2])
tri_new = np.array([inside,i2,i1])
return tri_new
if len(points_arr) == 0 : return None
def projected_tris(list_proj): list_proj = [] return list_proj
def get_points(tri, projected_triangles):
projected_new = []
for v in tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
projected_new.append((x1, y1))
# Append the projected triangle
projected_triangles.append(projected_new)
def draw_tri(tri, vertexes): projected_triangles = [] for tri1 in tri: v1, v2, v3 = [vertexes[i] for i in tri1]
clipped_points = clipping_triangles(v1, v2, v3)
if clipped_points is None:
continue
tris_to_draw = clipped_points if isinstance(clipped_points, tuple) else [clipped_points]
for tri_tri in tris_to_draw:
tri_proj = []
for v in tri_tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
tri_proj.append((x1, y1))
projected_triangles.append((tri_proj))
return projected_triangles
def draw_triangels(tri_angle): for tri_proj in tri_angle: pygame.draw.polygon(screen,(255,255,255),tri_proj,1)
tx,ty,tz = 0,0,800 def translated_matrix(tx,ty,tz): return np.array([[1,0,0,tx], [0,1,0,ty], [0,0,1,tz], [0,0,1,0]])
def rotation_matrix(ang): c = math.cos(ang) s = math.sin(ang) return np.array([ [c, 0, s, 0], [0, 1, 0, 0], [-s,0, c, 0], [0, 0, 0, 1] ]) def sort_tri_z(v1,v2,v3):pass
def topygamecoords(x,y): x = x + 400 y = y + 300 return x,y
while True: for event in pygame.event.get(): if event.type == QUIT: #running = False pygame.quit() exit() screen.fill((0,0,0)) dt = clock.tick(60) / 1000.0 keys = pygame.key.get_pressed() if keys[pygame.K_w]: tz -= 5 if keys[pygame.K_s]: tz += 5 if keys[pygame.K_a]: tx += 5 if keys[pygame.K_d]: tx -= 5 if keys[pygame.K_q]: ty += 5 if keys[pygame.K_e]: ty -= 5 # angle = math.cos(a)
line_width = (WIDTH*600)/z
rotated = rotation_matrix(angle)
translate = translated_matrix(tx,ty,tz)
#rotated_finish = np.dot(vertices, rotation_matrix(angle).T)
vertices_transformed = []
projected_triangles = []
for v in vertices:
v4 = np.append(v,1)
v_trans = translate@rotated@v4
vertices_transformed.append(v_trans[:3])
projected = draw_tri(tri=triangles,vertexes=vertices_transformed)
#texture_scan(vertices_transformed[0],vertices_transformed[1],vertices_transformed[2],screen)
if not keys[pygame.K_k]:
for tri_proj in projected:
#tri_color = get_shadow(tri_proj[0],tri_proj[1],tri_proj[2])
pygame.draw.polygon(screen, (255,255,255), tri_proj)
# if len(tri_proj) == 3:
# texture_scan(tri_proj[0], tri_proj[1], tri_proj[2], screen)
pygame.display.set_caption(str(clock.get_fps()))
pygame.display.flip()
angle+=0.01
# clock.tick(60)
Now, ive tried a basic method of taking the normal to a triangle in the commented out sections.. Now welp the shape is sort of shading but idk its not correct. Where am i going wrong with my calculations. I did try playing around a bit with the signs but that didnt help. Also i stopped the rotation to see if things got better(they didnt). One issue might be that im not really taking the correct slope from my camera to the triangle, but im not sure if thats the sole reason. I did try rendering it as a static object placed very close to my camera, so i didnt move/ change its coordinates, but that too gave me flawed results. Any sort of help is appreciated.
Applying greyscale shading to objects in a Python 3D renderer
I need help with how to apply greyscale shading to objects in a 3D renderer I made in Python. The code is pasted below:
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import numpy as np
pygame.init()
screen_size = (800, 600)
HEIGHT = 600
color = 0
WIDTH = 800
screen = pygame.display.set_mode(screen_size)
angle = 0
clock = pygame.time.Clock()
# z = 600
f = 400
centre_x = 400
centre_y = 300
line_width = 600
v4 = 0
z = 100
FOV = math.tan(60)
x_left,x_right,y_left,y_rigth=0,0,0,0
# vertices1 = np.array([
# [-200, -200, -200],
# [-100, -200, -200],
# [-100, -100, -200],
# [-200, -100, -200],
# [-200, -200, -100],
# [-100, -200, -100],
# [-100, -100, -100],
# [-200, -100, -100]
# ])
dt = 0
vertices = []
triangles = []
texture = pygame.image.load("D:/3d objects/pexels-pixabay-207142_texture.jpg").convert_alpha() #not used
# with open("C:/Users/Sohan/Desktop/game final/Low_Poly_AK_47.obj", "r") as g:
# for line in g:
# if line.startswith("v "): # vertex
# parts = line.strip().split()[1:]
# vertices.append([float(p) for p in parts])
# elif line.startswith("f "): # face
# parts = line.strip().split()[1:]
# face = []
# for p in parts:
# # OBJ is 1-indexed, we convert to 0-indexed
# idx = int(p.split("/")[0]) - 1
# face.append(idx)
# # triangulate if face has more than 3 vertices
# if len(face) == 3:
# triangles.append(tuple(face))
# elif len(face) == 4:
# # quad → 2 triangles
# triangles.append((face[0], face[1], face[2]))
# triangles.append((face[0], face[2], face[3]))
# else:
# # For polygons >4, simple fan triangulation
# for i in range(1, len(face)-1):
# triangles.append((face[0], face[i], face[i+1]))
# vertices = np.array(vertices)
vertices = np.array([
[-1, -1, -1],
[ 1, -1, -1],
[ 1, 1, -1],
[-1, 1, -1],
[-1, -1, 1],
[ 1, -1, 1],
[ 1, 1, 1],
[-1, 1, 1]
], dtype=float)
triangles = np.array([
[0, 1, 2], [0, 2, 3], # back
[4, 5, 6], [4, 6, 7], # front
[0, 1, 5], [0, 5, 4], # bottom
[2, 3, 7], [2, 7, 6], # top
[0, 3, 7], [0, 7, 4], # left
[1, 2, 6], [1, 6, 5] # right
])
uvs = np.array([
[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0, 0],
[1, 0],
[1, 1],
[0, 1]
], dtype=float)
near = 1
print("Vertices:", len(vertices))
print("Triangles:", len(triangles))
scale = 50
vertices = vertices * (scale)/10
# def get_normal(v1,v2,v3):
# normal = np.cross((v2 - v1),(v3 - v1))
# return normal*-1
# def get_shadow(v1,v2,v3):
# #camera = 0,0,0
# normal = get_normal(v1,v2,v3)
# l1_cam = (v1 + v2 + v3)/3
# unit_l1_cam = l1_cam/np.linalg.norm(l1_cam)
# unit_normal = normal/np.linalg.norm(normal)
# theta = np.dot(unit_normal,unit_l1_cam)
# # camera/light at origin
# light_unit = unit_l1_cam
# intensity = max(0, theta)
# color = int(255 * intensity)
# theta_rad = np.arccos(np.clip(theta, -1.0, 1.0))
# theta_deg = np.degrees(theta_rad)
# print(theta_deg)
# return (color,color,color)
# def get_shadow(v1, v2, v3):
# # Compute normal
# normal = get_normal(v1, v2, v3)
# normal = normal / np.linalg.norm(normal)
# # Light direction from origin (camera)
# tri_center = (v1 + v2 + v3) / 3
# light_dir = -tri_center
# light_dir = light_dir / np.linalg.norm(light_dir)
# # Intensity
# intensity = np.dot(normal, light_dir)
# intensity = np.clip(intensity, 0, 1) # clamp to 0..1
# color_val = int(255 * intensity)
# return (color_val, color_val, color_val)
def total_in_points(v1,v2,v3):
count = 0
in_points = []
for v in [v1, v2, v3]:
if v[2] > near :
count += 1
in_points.append(v[2])
return in_points
def clipping_triangles(v1,v2,v3):
points_arr = total_in_points(v1,v2,v3)
#points_arr[0] = point,z,1
#points_arr[1] = point,z,2
if len(points_arr) == 3 :
tri = np.array([v1,v2,v3])
return tri
elif len(points_arr) == 2:
# Separate inside vs outside vertices
vertices_list = [v1, v2, v3]
inside = [v for v in vertices_list if v[2] > near]
outside = [v for v in vertices_list if v[2] <= near][0]
i1 = outside + (inside[0] - outside) * (near - outside[2]) / (inside[0][2] - outside[2])
i2 = outside + (inside[1] - outside) * (near - outside[2]) / (inside[1][2] - outside[2])
# 2 new triangles
tri_new_1 = np.array([inside[0], inside[1], i1])
tri_new_2 = np.array([i1, inside[1], i2])
return tri_new_1, tri_new_2
elif len(points_arr) == 1:
vertices_list = [v1 , v2 , v3]
inside = [v for v in vertices_list if v[2] > near][0]
outside = [v for v in vertices_list if v[2] <= near]
i1 = inside + (outside[0] - inside) * (near - inside[2])/(outside[0][2] - inside[2])
i2 = inside + (outside[1] - inside) * (near - inside[2])/(outside[1][2] - inside[2])
tri_new = np.array([inside,i2,i1])
return tri_new
if len(points_arr) == 0 : return None
def projected_tris(list_proj):
list_proj = []
return list_proj
def get_points(tri, projected_triangles):
projected_new = []
for v in tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
projected_new.append((x1, y1))
# Append the projected triangle
projected_triangles.append(projected_new)
def draw_tri(tri, vertexes):
projected_triangles = []
for tri1 in tri:
v1, v2, v3 = [vertexes[i] for i in tri1]
clipped_points = clipping_triangles(v1, v2, v3)
if clipped_points is None:
continue
tris_to_draw = clipped_points if isinstance(clipped_points, tuple) else [clipped_points]
for tri_tri in tris_to_draw:
tri_proj = []
for v in tri_tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
tri_proj.append((x1, y1))
projected_triangles.append((tri_proj))
return projected_triangles
def draw_triangels(tri_angle):
for tri_proj in tri_angle:
pygame.draw.polygon(screen,(255,255,255),tri_proj,1)
tx,ty,tz = 0,0,800
def translated_matrix(tx,ty,tz):
return np.array([[1,0,0,tx],
[0,1,0,ty],
[0,0,1,tz],
[0,0,1,0]])
def rotation_matrix(ang):
c = math.cos(ang)
s = math.sin(ang)
return np.array([
[c, 0, s, 0],
[0, 1, 0, 0],
[-s,0, c, 0],
[0, 0, 0, 1]
])
def sort_tri_z(v1,v2,v3):pass
def topygamecoords(x,y):
x = x + 400
y = y + 300
return x,y
while True:
for event in pygame.event.get():
if event.type == QUIT:
#running = False
pygame.quit()
exit()
screen.fill((0,0,0))
dt = clock.tick(60) / 1000.0
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
tz -= 5
if keys[pygame.K_s]:
tz += 5
if keys[pygame.K_a]:
tx += 5
if keys[pygame.K_d]:
tx -= 5
if keys[pygame.K_q]:
ty += 5
if keys[pygame.K_e]:
ty -= 5
# angle = math.cos(a)
line_width = (WIDTH*600)/z
rotated = rotation_matrix(angle)
translate = translated_matrix(tx,ty,tz)
#rotated_finish = np.dot(vertices, rotation_matrix(angle).T)
vertices_transformed = []
projected_triangles = []
for v in vertices:
v4 = np.append(v,1)
v_trans = translate@rotated@v4
vertices_transformed.append(v_trans[:3])
projected = draw_tri(tri=triangles,vertexes=vertices_transformed)
#texture_scan(vertices_transformed[0],vertices_transformed[1],vertices_transformed[2],screen)
if not keys[pygame.K_k]:
for tri_proj in projected:
#tri_color = get_shadow(tri_proj[0],tri_proj[1],tri_proj[2])
pygame.draw.polygon(screen, (255,255,255), tri_proj)
# if len(tri_proj) == 3:
# texture_scan(tri_proj[0], tri_proj[1], tri_proj[2], screen)
pygame.display.set_caption(str(clock.get_fps()))
pygame.display.flip()
angle+=0.01
# clock.tick(60)
Now, I've tried a basic method of taking the normal to a triangle in the commented out sections. Now the shape is sort of shading but it's not correct.
Where am I going wrong with my calculations? I did try playing around a bit with the signs but that didn't help. Also I stopped the rotation to see if things got better(they didn't).
One issue might be that I'm not really taking the correct slope from my camera to the triangle, but I'm not sure if that's the sole reason. I did try rendering it as a static object placed very close to my camera, so it didn't move / change its coordinates, but that too gave me flawed results.
Need help on how to apply grey scale shading to objects in a 3d renderer i made in python, the code is pasted below
import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import * import math import numpy as np
pygame.init() screen_size = (800, 600) HEIGHT = 600 color = 0 WIDTH = 800 screen = pygame.display.set_mode(screen_size) angle = 0 clock = pygame.time.Clock()
z = 600
f = 400 centre_x = 400 centre_y = 300 line_width = 600 v4 = 0 z = 100 FOV = math.tan(60) x_left,x_right,y_left,y_rigth=0,0,0,0
vertices1 = np.array([
[-200, -200, -200],
[-100, -200, -200],
[-100, -100, -200],
[-200, -100, -200],
[-200, -200, -100],
[-100, -200, -100],
[-100, -100, -100],
[-200, -100, -100]
])
dt = 0
vertices = [] triangles = [] texture = pygame.image.load("D:/3d objects/pexels-pixabay-207142_texture.jpg").convert_alpha() #not used
with open("C:/Users/Sohan/Desktop/game final/Low_Poly_AK_47.obj", "r") as g:
for line in g:
if line.startswith("v "): # vertex
parts = line.strip().split()[1:]
vertices.append([float(p) for p in parts])
elif line.startswith("f "): # face
parts = line.strip().split()[1:]
face = []
for p in parts:
# OBJ is 1-indexed, we convert to 0-indexed
idx = int(p.split("/")[0]) - 1
face.append(idx)
# triangulate if face has more than 3 vertices
if len(face) == 3:
triangles.append(tuple(face))
elif len(face) == 4:
# quad → 2 triangles
triangles.append((face[0], face[1], face[2]))
triangles.append((face[0], face[2], face[3]))
else:
# For polygons >4, simple fan triangulation
for i in range(1, len(face)-1):
triangles.append((face[0], face[i], face[i+1]))
vertices = np.array(vertices)
vertices = np.array([ [-1, -1, -1], [ 1, -1, -1], [ 1, 1, -1], [-1, 1, -1], [-1, -1, 1], [ 1, -1, 1], [ 1, 1, 1], [-1, 1, 1] ], dtype=float)
triangles = np.array([ [0, 1, 2], [0, 2, 3], # back [4, 5, 6], [4, 6, 7], # front [0, 1, 5], [0, 5, 4], # bottom [2, 3, 7], [2, 7, 6], # top [0, 3, 7], [0, 7, 4], # left [1, 2, 6], [1, 6, 5] # right ])
uvs = np.array([ [0, 0], [1, 0], [1, 1], [0, 1], [0, 0], [1, 0], [1, 1], [0, 1] ], dtype=float) near = 1
print("Vertices:", len(vertices)) print("Triangles:", len(triangles)) scale = 50 vertices = vertices * (scale)/10
def get_normal(v1,v2,v3):
normal = np.cross((v2 - v1),(v3 - v1))
return normal*-1
def get_shadow(v1,v2,v3):
#camera = 0,0,0
normal = get_normal(v1,v2,v3)
l1_cam = (v1 + v2 + v3)/3
unit_l1_cam = l1_cam/np.linalg.norm(l1_cam)
unit_normal = normal/np.linalg.norm(normal)
theta = np.dot(unit_normal,unit_l1_cam)
# camera/light at origin
light_unit = unit_l1_cam
intensity = max(0, theta)
color = int(255 * intensity)
theta_rad = np.arccos(np.clip(theta, -1.0, 1.0))
theta_deg = np.degrees(theta_rad)
print(theta_deg)
return (color,color,color)
def get_shadow(v1, v2, v3):
# Compute normal
normal = get_normal(v1, v2, v3)
normal = normal / np.linalg.norm(normal)
# Light direction from origin (camera)
tri_center = (v1 + v2 + v3) / 3
light_dir = -tri_center
light_dir = light_dir / np.linalg.norm(light_dir)
# Intensity
intensity = np.dot(normal, light_dir)
intensity = np.clip(intensity, 0, 1) # clamp to 0..1
color_val = int(255 * intensity)
return (color_val, color_val, color_val)
def total_in_points(v1,v2,v3):
count = 0
in_points = []
for v in [v1, v2, v3]:
if v[2] > near :
count += 1
in_points.append(v[2])
return in_points
def clipping_triangles(v1,v2,v3): points_arr = total_in_points(v1,v2,v3)
#points_arr[0] = point,z,1
#points_arr[1] = point,z,2
if len(points_arr) == 3 :
tri = np.array([v1,v2,v3])
return tri
elif len(points_arr) == 2:
# Separate inside vs outside vertices
vertices_list = [v1, v2, v3]
inside = [v for v in vertices_list if v[2] > near]
outside = [v for v in vertices_list if v[2] <= near][0]
i1 = outside + (inside[0] - outside) * (near - outside[2]) / (inside[0][2] - outside[2])
i2 = outside + (inside[1] - outside) * (near - outside[2]) / (inside[1][2] - outside[2])
# 2 new triangles
tri_new_1 = np.array([inside[0], inside[1], i1])
tri_new_2 = np.array([i1, inside[1], i2])
return tri_new_1, tri_new_2
elif len(points_arr) == 1:
vertices_list = [v1 , v2 , v3]
inside = [v for v in vertices_list if v[2] > near][0]
outside = [v for v in vertices_list if v[2] <= near]
i1 = inside + (outside[0] - inside) * (near - inside[2])/(outside[0][2] - inside[2])
i2 = inside + (outside[1] - inside) * (near - inside[2])/(outside[1][2] - inside[2])
tri_new = np.array([inside,i2,i1])
return tri_new
if len(points_arr) == 0 : return None
def projected_tris(list_proj): list_proj = [] return list_proj
def get_points(tri, projected_triangles):
projected_new = []
for v in tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
projected_new.append((x1, y1))
# Append the projected triangle
projected_triangles.append(projected_new)
def draw_tri(tri, vertexes): projected_triangles = [] for tri1 in tri: v1, v2, v3 = [vertexes[i] for i in tri1]
clipped_points = clipping_triangles(v1, v2, v3)
if clipped_points is None:
continue
tris_to_draw = clipped_points if isinstance(clipped_points, tuple) else [clipped_points]
for tri_tri in tris_to_draw:
tri_proj = []
for v in tri_tri:
x1, y1 = topygamecoords(v[0] * f / v[2], -v[1] * f / v[2])
tri_proj.append((x1, y1))
projected_triangles.append((tri_proj))
return projected_triangles
def draw_triangels(tri_angle): for tri_proj in tri_angle: pygame.draw.polygon(screen,(255,255,255),tri_proj,1)
tx,ty,tz = 0,0,800 def translated_matrix(tx,ty,tz): return np.array([[1,0,0,tx], [0,1,0,ty], [0,0,1,tz], [0,0,1,0]])
def rotation_matrix(ang): c = math.cos(ang) s = math.sin(ang) return np.array([ [c, 0, s, 0], [0, 1, 0, 0], [-s,0, c, 0], [0, 0, 0, 1] ]) def sort_tri_z(v1,v2,v3):pass
def topygamecoords(x,y): x = x + 400 y = y + 300 return x,y
while True: for event in pygame.event.get(): if event.type == QUIT: #running = False pygame.quit() exit() screen.fill((0,0,0)) dt = clock.tick(60) / 1000.0 keys = pygame.key.get_pressed() if keys[pygame.K_w]: tz -= 5 if keys[pygame.K_s]: tz += 5 if keys[pygame.K_a]: tx += 5 if keys[pygame.K_d]: tx -= 5 if keys[pygame.K_q]: ty += 5 if keys[pygame.K_e]: ty -= 5 # angle = math.cos(a)
line_width = (WIDTH*600)/z
rotated = rotation_matrix(angle)
translate = translated_matrix(tx,ty,tz)
#rotated_finish = np.dot(vertices, rotation_matrix(angle).T)
vertices_transformed = []
projected_triangles = []
for v in vertices:
v4 = np.append(v,1)
v_trans = translate@rotated@v4
vertices_transformed.append(v_trans[:3])
projected = draw_tri(tri=triangles,vertexes=vertices_transformed)
#texture_scan(vertices_transformed[0],vertices_transformed[1],vertices_transformed[2],screen)
if not keys[pygame.K_k]:
for tri_proj in projected:
#tri_color = get_shadow(tri_proj[0],tri_proj[1],tri_proj[2])
pygame.draw.polygon(screen, (255,255,255), tri_proj)
# if len(tri_proj) == 3:
# texture_scan(tri_proj[0], tri_proj[1], tri_proj[2], screen)
pygame.display.set_caption(str(clock.get_fps()))
pygame.display.flip()
angle+=0.01
# clock.tick(60)
Now, ive tried a basic method of taking the normal to a triangle in the commented out sections.. Now welp the shape is sort of shading but idk its not correct. Where am i going wrong with my calculations. I did try playing around a bit with the signs but that didnt help. Also i stopped the rotation to see if things got better(they didnt). One issue might be that im not really taking the correct slope from my camera to the triangle, but im not sure if thats the sole reason. I did try rendering it as a static object placed very close to my camera, so i didnt move/ change its coordinates, but that too gave me flawed results. Any sort of help is appreciated.