基于CMU图形库无额外依赖的3D方块渲染及视角控制技术咨询
Hey there, let's work through your two main issues—getting those polygon faces rendered and fixing wonky camera movement. You’ve already nailed the face culling foundation, so we’re halfway there!
一、先修复视角/移动方向异常问题
Your weird camera rotation and movement behavior is almost certainly from how you’re handling pitch/yaw clamping and coordinate mapping. Here’s what to fix:
Clamp pitch instead of wrapping it to 360°
Pitch (up/down look) shouldn’t loop 360°—in most 3D games like Minecraft, it’s restricted to between -90° (looking straight down) and 90° (looking straight up). When you let it wrap past those values, the camera flips, causing that "360° local view" glitch. Replace your pitch wrapping code with clamping:pitch = 180*(tPitch/math.pi) + target.pitch # Clamp pitch to -90 to 90 degrees (adjust if your coordinate system uses 0-180) pitch = max(-90, min(90, pitch))For yaw, wrapping to 0-360° is fine, but you can simplify it with
yaw = (tYaw + target.yaw) % 360instead of manual subtraction.Verify your angle calculation order
Make sure yourangleTo3dfunction is returning angles relative to the player’s local space, not world space. When combining the face’s angle with the player’s yaw/pitch, you should first rotate the face’s direction by the player’s yaw, then apply pitch—reverse order can cause inverted movement.Fix WASD movement to follow camera direction
If WASD isn’t moving in the direction you’re looking, your movement logic is probably using world coordinates instead of camera-relative coordinates. For example, to move forward (W):# Convert yaw to radians yaw_rad = math.radians(target.yaw) # Move along the camera's forward direction (adjust axis based on your coordinate system) target.x += math.cos(yaw_rad) * move_speed target.z += math.sin(yaw_rad) * move_speedLeft/right (A/D) would use
-math.sin(yaw_rad)andmath.cos(yaw_rad)respectively.
二、从面中心点渲染完整方块面
You have the face centers and distance data—now you just need to generate the four corners of each face, project them to screen space, and feed them to the Polygon class. Here’s a step-by-step approach:
1. Predefine face corners relative to the block center
Assuming your blocks are 1x1x1 units, each face has four corners offset from the center by ±0.5 units. For example:
- Front face (facing +Z direction):
[(cx+0.5, cy+0.5, cz+0.5), (cx-0.5, cy+0.5, cz+0.5), (cx-0.5, cy-0.5, cz+0.5), (cx+0.5, cy-0.5, cz+0.5)] - Top face (facing +Y direction):
[(cx+0.5, cy+0.5, cz+0.5), (cx-0.5, cy+0.5, cz+0.5), (cx-0.5, cy+0.5, cz-0.5), (cx+0.5, cy+0.5, cz-0.5)] - Define similar corner lists for left/right/bottom/back faces based on your coordinate system.
2. Project each corner to screen space
For every corner in the face’s corner list, repeat the angle-to-screen conversion you used for the center—but this time, calculate the pitch/yaw for each corner instead of just the center. Alternatively, use a proper projection method:
def project_point(x, y, z, player): # Convert point to player-relative coordinates rel_x = x - player.x rel_y = y - player.y rel_z = z - player.z # Rotate by player's yaw (around Y axis) yaw_rad = math.radians(player.yaw) rotated_x = rel_x * math.cos(yaw_rad) + rel_z * math.sin(yaw_rad) rotated_z = -rel_x * math.sin(yaw_rad) + rel_z * math.cos(yaw_rad) # Rotate by player's pitch (around X axis) pitch_rad = math.radians(player.pitch) rotated_y = rel_y * math.cos(pitch_rad) - rotated_z * math.sin(pitch_rad) rotated_z_final = rel_y * math.sin(pitch_rad) + rotated_z * math.cos(pitch_rad) # Skip if the point is behind the player (culling) if rotated_z_final <= 0: return None # Perspective projection (adjust focal length based on your screen size) focal_length = 200 screen_x = 200 + (rotated_x / rotated_z_final) * focal_length screen_y = 600 - (rotated_y / rotated_z_final) * focal_length # Flip Y since screen Y increases downward return (screen_x, screen_y)
3. Render the polygon
In your render function, instead of just drawing a circle for the center, generate the four screen points for the face, filter out any points behind the player, and pass them to Polygon:
if self.front == True: # Get block center (you'll need to track this in your block class) cx, cy, cz = self.center_x, self.center_y, self.center_z # Define front face corners front_corners = [ (cx+0.5, cy+0.5, cz+0.5), (cx-0.5, cy+0.5, cz+0.5), (cx-0.5, cy-0.5, cz+0.5), (cx+0.5, cy-0.5, cz+0.5) ] # Project each corner to screen screen_points = [] for corner in front_corners: pt = project_point(corner[0], corner[1], corner[2], target) if pt: screen_points.append(pt) # Only render if all 4 points are valid (in front of player) if len(screen_points) == 4: renderGroup.add(Polygon(screen_points))
Quick Tips
- Sort faces by distance: Draw faces that are farther away first, then closer ones—this fixes overlapping rendering issues.
- Reuse calculations: Cache the player’s yaw/pitch radians once per frame instead of recalculating for every point.
- Test with simple shapes: Start with a single block and one face to debug the projection before scaling up.
内容的提问来源于stack exchange,提问作者guywhocode

