Microsoft MakeCode

3D graphics test

Controls: Use the A button to turn on a framerate display, B button to switch rendering modes, and the joystick to move the light direction.

I wanted to take a stab at simple 3D rendering on Arcade hardware, inspired by @eanders’s 3D Renderer Demo and @charliegregg’s My 3D Rendering Code.

This obviously isn’t what the hardware is intended for, but I was curious if it’s possible to reach passable framerates. On a PyGamer, I’m seeing 30-40fps with dithering, and 40-50fps with flat colors. This drops to 14/20fps when adding a full-screen background, so it seems to be limited by the pixel fill speed. I’m using scene.backgroundImage().setRows which was the fastest approach I’d found, along with avoiding floating-point arithmetic.

I don’t think this would be usable for full 3D scenery or models with more than a few dozen triangles, though it could be good enough for some very simple blocky spaceships similar to the original 8-bit Elite. (It had flat-shaded ports for Amiga and DOS PCs that replaced the wireframe graphics.)

At this point the code can do basic 3D transforms including perspective and backplane culling, but doesn’t do 3D occlusion, so applications need to use the painter’s algorithm and draw objects back to front. I was thinking about adding a scanline-based Z buffer, but I’m pretty sure it would be too slow to be useful since any code added to the inner pixel loop tends to tank the framerate.

I haven’t implemented clipping yet, so objects extending beyond the screen edges won’t be drawn correctly. This would be fairly straightforward to fix, I just didn’t get around to it.

11 Likes

The emulator has a limit of 50fps, but it shows the theoretical framerate thar it could have reached without that limit. In this case, it’s using less than half of the per-frame time available and could easily add more polygons.

It’s easier to get high framerate in the emulator than on hardware, for example floating point math is about the same speed as integer math on a PC browser, but a lot slower on arcade hardware.

This reminds me, I couldn’t get the stats display from the device menu working on my PyGamer - does it need extra steps or special setup to show that output?

Go to menu
Select the watch
SmartSelect_20210220-132628_Samsung Internet
And it displays stats!

1 Like

That works for me in the emulator, same as in your screenshot, but on my PyGamer hardware that menu function doesn’t seem to do anything, I don’t see any stats being displayed. That’s why I added an extra framerate display in the app, but I’m wondering if I’m doing something wrong. Does it work for you?

Don’t have one, but I wish I had one. I don’t have any hardware at all :sob:

Yeah I’m not sure about anything like this…
I implemented as many optimisations as I could find after stopping posting on My 3D Ren…
the best fps I could get while rendering 500 ish cubes was 15 so not very practical.

Still… It’s kinda fun to play around.

(Keep in mind this is without collisions! You can only break or place blocks, like Minecraft.)

1 Like

That’s actually quite a bit better than what I’d expect for running on embedded-class hardware, and 15fps isn’t all that bad. I think many early 8-bit 3D games were only getting single-digit frame rates.

I agree that this doesn’t seem very useful for full 3D rendered scenes, especially scenery that could have a lot of overdraw such as Minecraft, but I think it may work well enough for some special cases such as a space shooter with a small number of objects that don’t cover too much of the screen. I’ve been experimenting with a fairly lightweight starfield backdrop to go along with this, and initial results look somewhat promising. If I find time I’ll keep poking at it to see what I end up with.

ico-stars

3 Likes

image

This broke my brain

Sorry about that. The starfield movement wasn’t properly synchronized with the viewpoint movement yet when I recorded that animation, so the motion looked rather odd.

Here’s a fixed example with movement controls. Use the left stick for pitch and yaw, and tilt the device left/right to roll. The A button toggles speed, cycling through forward/backward/stopped. In the emulator, moving the mouse left/right simulates device tilt.

4 Likes

The emulator’s tilt controls aren’t really usable on a phone or tablet, so here’s an alternate version which doesn’t use the accelerometer. Use the B button to select the stick left/right movement function, it switches between yaw and roll control. (Stick up/down is still pitch, button A cycles speed.)

(As a side note for the Makecode Arcade team: would it make sense to use the accelerometer sensor API in the emulator to take advantage of a mobile device’s accelerometer where available? The current touch-based method makes it pretty much impossible to use the onscreen joystick and buttons at the same time as tilt controls.)

2 Likes

I believe we had some quick look at that and were a bit worried it might have significant battery draining perf, but it might also just have been that we talked about it and forgot to implement it! It’s definitely possible / we should probably file an issue and track / investigate :slight_smile:

1 Like

Overdraw?
I looked up that term and I’m not sure I understand it.
Why would you need to draw the same pixel twice?

He means updating how the scene looks like. Correct me If I am wrong @kwx.

Hmmm… Well I’ve never actually looked at a ‘real’ renderer so my method might be bad. I store things as grouped objects when they touch. So it automatically culls everything behind it so I draw front to back. As far as I am aware it’s normally back to front.

Yes, this depends on the type of renderer. If you have a special-purpose renderer, for example a raycaster which finds the closest wall for each Y pixel column, you only need to draw exactly the pixels for that single wall. This has limitations, for example you can’t do a “roll” rotation where the walls are no longer vertical, and you typically can’t draw more complex scenes such as bridges or overhangs.

If you have a more general renderer that displays objects which can partially overlap each other, you need to ensure that the occlusion is correct. The “painter’s algorithm” simply draws them back to front, resulting in overdraw. Or you can use a Z buffer or similar technique to determine which pixel needs to be drawn, but that needs more per-pixel calculations which would likely be too slow for the MakeCode Arcade target hardware.

Edit: here’s an example using back-to-front object sorting. This isn’t quite correct for intersecting objects (you can see the image pop a bit when the closest object changes), but works fairly well otherwise.

2 Likes