Pseudo 3d Road
Long before the invention of 3d rendering engines, we had some crazy "3d" games - like Wolfenstein, Doom and Outrun. These games managed to simulate depth and immersion using clever pixel manipulation techniques, broadly called "Pseudo 3d".
Without going too much into what 3d rendering engines are and how they work, a "true" 3d engine creates an actual 3d space and navigates through it using a camera object inside that world. The computer creates points in space known as vertexes that add up to a "3d mesh" - and fills the space between these vertexes with textures. Each frame rendered to the screen takes into account all the vertexes currently seen by the camera, and the 2d screen position of these vertexes is extrapolated from their 3d "world" position.
In a pseudo 3d world we use flat images that undergo manipulation in order to simulate depth.
What's the difference? The difference is that if we were to simulate driving on a road in a true 3d engine, we would be dealing with a large mesh of a road, complete with grass and mountains, all created with millions of vertexes, and a camera traveling through it.
In a pseudo 3d world there is no actual depth, and no camera moving through space. There is a single image being manipulated at every frame to simulate depth.
In this post I'd like to demonstrate exactly that - an example of driving on an endless road Outrun style, created with a single static road image.
I used a very user friendly c++ game engine called sfml to create this example.
Drawing the road:
The illusion is achieved by calculating percentages. We're dealing with 2 textures - the original image of the road (flat top down view), and the second - an empty canvas to be drawn on.
Pixel by pixel we copy the image from source to destination - shrinking each row by a predetermined factor. If our road shrinks into the distance, that means the bottom row in our canvas should be 100% (or more) in size of the original image's bottom row. The top row should be 0%.
The width of each row in between the 2 extremes is its percentage in the total number of rows in the destination canvas. Using percentages we can get the correct pixel in the source image and draw it to the corresponding pixel in that target canvas.
Illusion of depth:
Drawing the image using only the method described so far will not render the illusion of depth. We will only get this illusion if the lines on the road get smaller as they progress into the distance. This effect can be achieved using an "easing" function - a function that counts from a start to end value in a non linear fashion (for instance quickly at first with a gradual slowdown). Here I used an easing function known as "easeOutQuad", which gradually slows down to zero. This gives the illusion of the road getting further away at a growing factor into the distance.
Road Movement:
We have a road and a simulation of depth. In order to make it move, all we have to do is run an update loop 60 times a second that changes the row we begin sampling pixels from in the source image. It's important to remember to do a "wrap around" - meaning that once we start our sampling in a row that is not the top row, and progress down the image rows, we're bound to overshoot the number of rows, and need to start over at the first row as if we've completed full circle.
It's worth noting that if you we're to create a racing game you could tie the row progression to the "speed" of your car. An idle car has a speed of zero and thus keeps sampling the same row in the source image - rendering a static road. Acceleration that keeps getting faster can be tied to the number of rows "jumped" at each update loop - rendering a seemingly faster and faster driving experience.
Simulating turns in the road:
The distance point of our road doesn't necessarily have to be in the center of the screen. By calculating percentages in the destination canvas we can determine what column should be the start column to draw each row in our image. In my example I set the start column on each row to grow by a factor that renders a curved road.
By the way, since we are taking a flat road and rendering it as a cone, the sides of our road will be rendered white since no pixels we drawn on them. We need to fill them up with grass corresponding the the current row. Just fill all blank pixels in each row with the corresponding grass color.
As decoration I added mountains, drawn to the canvas from a second image - and moved them in the the opposite direction of the road.
Everything was eventually tied to mouse movements on screen - the height of the road image and curve of the road.
I'm a bit of a pseudo 3d buff. I find the ability to bring a static image to life through simple pixel manipulation to be mesmerizing.
Ipromise to upload more examples of pixel manipulation, including a 3d pseudo isometric view and first person shooter.
A link to the original road image used in this lesson:
תגובות
הוסף רשומת תגובה