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.