0
\$\begingroup\$

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.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You'll often get better answers, faster, if you include image examples showing what's not correct about the shading output you're getting. \$\endgroup\$ Commented Oct 20 at 1:37

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.