0
\$\begingroup\$

I copied a perspective projection matrix from here (https://jsantell.com/3d-projection/) and applied it to my vertices. It looks ok but without depth. Rotation matrices also work.

When I try to divide all components by the w-component after applying the projection matrix I get some weird geometry. This is what I have understand should be done to apply depth projection.

Below is my code, I'm using python and pygame for drawing the polygons of a cube.

What the cube looks like if I don't divide by w:

With division:

import pygame
import math 
import copy

pygame.init()

screen_width = 960
screen_height = 800
screen = pygame.display.set_mode([screen_width, screen_height])
clock = pygame.time.Clock()
FPS = 60
run = True

class Renderer():
    def __init__(self):
    
        self.cube = [
        [[0, 0, 0, 1],    [0, 1, 0, 1],    [1, 1, 0, 1]],
        [[0, 0, 0, 1],    [1, 1, 0, 1],    [1, 0, 0, 1]],                                 
        [[1, 0, 0, 1],    [1, 1, 0, 1],    [1, 1, 1, 1]],
        [[1, 0, 0, 1],    [1, 1, 1, 1],    [1, 0, 1, 1]],                                       
        [[1, 0, 1, 1],    [1, 1, 1, 1],    [0, 1, 1, 1]],
        [[1, 0, 1, 1],    [0, 1, 1, 1],    [0, 0, 1, 1]],                                         
        [[0, 0, 1, 1],    [0, 1, 1, 1],    [0, 1, 0, 1]],
        [[0, 0, 1, 1],    [0, 1, 0, 1],    [0, 0, 0, 1]],                                          
        [[0, 1, 0, 1],    [0, 1, 1, 1],    [1, 1, 1, 1]],
        [[0, 1, 0, 1],    [1, 1, 1, 1],    [1, 1, 0, 1]],                                         
        [[1, 0, 1, 1],    [0, 0, 1, 1],    [0, 0, 0, 1]],
        [[1, 0, 1, 1],    [0, 0, 0, 1],    [1, 0, 0, 1]]]

        
        for tri in self.cube:
            for point in tri:
                point[0] -= 0.5; point[1] -= 0.5; point[2] -= 0.5
            
        a = screen_height/screen_height
        n = 1
        f = 1000
        t = n * math.tan(math.radians(90/2))
        b = -t
        r = a * t
        l = -r

        self.projection = [
            [(2*n)/(r-l), 0, (r+l)/(r-l), 0],
            [0, (2*n)/(t-b), (t+b)/(t-b), 0], 
            [0, 0, (f+n)/(n-f), (2*f*n)/(n-f)],
            [0, 0, -1, 0]
        ]
        
        self.rot = 0
        self.update_roty()
    
    def update_roty(self):
        self.rotation_y = [
            [math.cos(self.rot), 0, -math.sin(self.rot), 0],
            [0, 1, 0, 0],
            [math.sin(self.rot), 0, math.cos(self.rot), 0],
            [0, 0, 0, 1]]

    def Matrix_MultiplyVector(self, i, m):

        vx = i[0] * m[0][0] + i[1] * m[0][1] + i[2] * m[0][2] + m[0][3]
        vy = i[0] * m[1][0] + i[1] * m[1][1] + i[2] * m[1][2] + m[1][3]
        vz = i[0] * m[2][0] + i[1] * m[2][1] + i[2] * m[2][2] + m[2][3]
        vw = i[0] * m[3][0] + i[1] * m[3][1] + i[2] * m[3][2] + m[3][3]
        
        vx /= vw
        vy /= vw
        vz /= vw
        vw /= vw

        return [vx,vy,vz,vw]

    def draw(self):

        self.rot += 0.0175
        self.update_rotx()
        self.update_roty()
        self.update_rotz()

        a = screen_height/2
        
        for tri in self.cube:

            tricopy = copy.deepcopy(tri)
            proj_points = []

            for i, point in enumerate(tricopy):

                tricopy[i] = self.Matrix_MultiplyVector(tricopy[i], self.rotation_y)
                tricopy[i] = self.Matrix_MultiplyVector(tricopy[i], self.projection)

                tricopy[i][0] *= 50; tricopy[i][0] += a  
                tricopy[i][1] *= 50; tricopy[i][1] += a

                proj_points.append((tricopy[i][0], tricopy[i][1]))

            pygame.draw.polygon(screen, [230,168,50], proj_points, 1)

renderer = Renderer()

while run:

    screen.fill([47,79,83])
    mx, my = pygame.mouse.get_pos()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                pygame.quit()
                
    keys = pygame.key.get_pressed()
    renderer.user_input(keys)

    renderer.draw()

    clock.tick(FPS)
    pygame.display.flip()

pygame.quit()```
\$\endgroup\$
9
  • \$\begingroup\$ Can you show us some screenshots to help readers understand what it means to "look OK but without depth" or in what way your geometry gets "weird" after the perspective divide? It helps if you can contrast this against what you expect it to look like — what specifically should be different? \$\endgroup\$
    – DMGregory
    Commented Jul 27, 2023 at 18:38
  • \$\begingroup\$ Yes so here is without dividing by w during vector x matrix mutplication: imgur.com/a/ybZSceu And this is with division:imgur.com/a/sfcWDMm \$\endgroup\$
    – Zoler1337
    Commented Jul 27, 2023 at 19:03
  • \$\begingroup\$ Please embed the images in your question so they display in-line, rather than as external links. You can click the 🖼️ icon when editing to embed an image via file browser, URL, or pasting from the clipboard. Visit the help center if you need any support with markdown formatting. Be sure to replace the "Enter image description here" placeholder text with descriptive text for screen readers. \$\endgroup\$
    – DMGregory
    Commented Jul 27, 2023 at 19:19
  • \$\begingroup\$ It's videos, how do I embedd them? \$\endgroup\$
    – Zoler1337
    Commented Jul 27, 2023 at 19:44
  • 1
    \$\begingroup\$ Looks like you're missing near plane clipping. Perspective transformation flips vertices to the wrong side when triangles cross the camera plane, so you need to clip those triangles so that you only draw the portion in front of the camera, rather than connecting valid vertices in front of the camera to invalid vertices behind it. \$\endgroup\$
    – DMGregory
    Commented Jul 27, 2023 at 20:31

1 Answer 1

0
\$\begingroup\$

Apparently it was a problem of the cube being "inside" the "camera".

I tried before to add += 3 to the z-axis of every triangle but it didn't work. Then I noticed it could only be added after the rotation matrix had been applied for some reason, but before the projection matrix.

\$\endgroup\$
2
  • \$\begingroup\$ Adding it before the rotation will cause the cube to orbit a radius of 3 around the origin, rather than rotating around its own center. For that reason, we apply transformations in the order 1. scale, 2. rotate, 3. translate. You can also apply a view transform (the inverse of the camera's transformation matrix) before projecting to make it easier to move the viewpoint around. \$\endgroup\$
    – DMGregory
    Commented Jul 28, 2023 at 16:05
  • \$\begingroup\$ Thank you for the explanation, that makes a lot of sense! \$\endgroup\$
    – Zoler1337
    Commented Jul 31, 2023 at 1:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .