Skip to main content
fixed typo
Source Link
Pikalek
  • 13.4k
  • 5
  • 49
  • 54
Formatting, grammar
Source Link
DMGregory
  • 140.8k
  • 23
  • 257
  • 401

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.

Source Link

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.