This project is being reworked from scratch and in-progress updates and features can be found here.
The below post is the original version of the project!
Introduction
I recently started working on a custom, realtime rendering engine in Studio that uses basic raytracing, normal mapping, and hard shadows that come to together to mimic Roblox’s old graphics style. I plan on open-sourcing this upon completion, but am curious if anyone has recommendations or features to implement before release!
You can play the demo here. Keep in mind that performance may be impacted because of the resolution the game is running at, something that I did to improve performance in-game was locking my FPS around 120fps. Performance should still be decent even at the current resolution, but I plan to add an option to change this in the near future!
Features include…
Custom materials with albedo maps, normal maps, specular maps that fade in and out with distance.
Procedural skies and fog.
Parallel Luau with interlacing for performance improvements.
Global lighting with diffuse and specular shading.
Local light sources (point lights, spotlights, area lights).
Offline rendering mode.
60fps gameplay at 128 x 100 pixels on my machine, though more optimizations to come!
Upcoming Features may include…
Mipmapping and antialiasing to reduce the visible noise caused by materials.
Those look absolutely stunning.
(I bet this took a lot of hard work and time )
But I always wonder if mipmapping would actually work and optimize this and maybe look the textures look a bit better cause of that moire pattern… Otherwise, magnificent work!!
Thank you! Mipmapping is totally possible but might have a performance hit due to sampling several texture reads in parallel depending on the number of mipmaps. I definitely want to try it out though!
This has to be the best ray tracer I have seen so far (especially open source), no lag, no artifacts, just stunning. The amount effort you put into this is unbelievable. Cant wait for release!
This looks amazing! The demo is really impressive, and I love the offline renders!
How did you implement the interlacing? It’s a really nice effect, and it has so much style to it which I’d love to use in my own games (Despite being for performance improvements lol!)
The idea is simple, but it can be a little tricky to implement at first! Each frame, the rendering engine splits the screen up into slices. I use the Y coordinate of each slice and check if it’s an odd or even number. The rendering engine switches to drawing odd slices, then even slices the next frame, and repeat. That way, the engine only has to render half of the entire screen every frame, then the other half the next frame, and repeat. The cool thing about this method is that you can also set whether you want to switch between odd and even slices each frame, or even every third or fourth slice, meaning you can increase your screen resolution even further while maintaining performance. The only downside is that pixels can warp slightly due to the fact that each group of slices are rendered in separate frames.
Before interlacing the engine drew each pixel on the screen individually, but with interlaced slices, you can allocate a parallel Actor to each individual slice, and draw the entire slice as a pixel buffer with editableImage:WritePixelsBuffer(…) in parallel.
Project is very close to an open-source release! A few people that played the demo brought up the idea that making a custom renderer comes with a lot of potential for customizable lighting, so I ran with that idea and started working on an OpenGL-style customizable shader system!
Customizable “Shader” System
Right now, the per-pixel rendering pipeline looks something like Graphics Pipeline Overview (below). Only the default passes are implemented so far, but the system is modular and designed to allow developers to insert their own logic between any two stages.
By splitting the pipeline into distinct passes, users can hook in and write their own code that mimics how fragment shaders work in OpenGL or GLSL. You’ll be able to insert code at any step of the graphics pipeline.
This design makes it possible to write pre-processing shaders (that manipulate geometry or normals before lighting) and post-processing shaders (that modify final color or screen coordinates). For example, you could bend the screen with a barrel distortion post-processing shader for a cool CRT effect!
You are Literally the most talented developer I have ever seen. I don’t think people realize how incredible this is, as it literally makes all the payed raytracers look like a joke.
I beg you, please continue working on this, I can’t wait for the public release!
I’m so glad to hear you’re looking forward to the release! However, it’s good to keep in mind that we don’t know the situations developers of the paid raytracers face that result in their pricing decisions! I work on open source projects during my free time, but many devs make a living off of their software!
Thank you! A lot of the optimizations are a result of multithreading, trying my best to prevent high-cost memory operations, and doing some image manipulation so that the raytracer doesn’t have to render each pixel on the screen every frame. During each frame, the engine splits up the screen (EditableImage) into rows, and attaches one parallel Actor per row. With interlacing enabled, you can include every row with an odd Y coordinate each frame, then switch to the even rows on the next frame, meaning the engine only has to render half of the rows it normally would when interlacing is off for the current frame. Each Actor then renders the pixels for that row, puts the data inside of a Buffer, and then writes the entire buffer to the current row with WritePixelsBuffer() (you could also write each pixel to the screen individually, but it will impact performance). When the frame is finished rendering, you get one half of the interlaced rows drawn on the screen, and in the next frame, the other half, so it gives the illusion that it’s all being rendered in one frame while keeping performance manageable. Another optimization technique is using native codegen and type checking whenever possible. It’s really cool that you’re making a pathtracer, but it also might be difficult to optimize enough to a playable frame rate. Right now, I’m averaging around 115fps, but it usually drops to 60-80fps depending on the screen resolution when multiple light sources are visible on the screen, and that’s typically only with 2-3 rays per pixel. You may want to look into other optimization methods that lower noise and the amount of rays that are typically fired for indirect lighting, like spherical harmonics or radiance cascades!
Good question! The engine has a custom material system that uses an underlying roblox material to render a custom material on top. Right now I have a “Studs” material that is drawn in place of the “Plastic” material. Other materials are supported as well, but it currently gets drawn on top of every surface of a part. I could add a surface property on top of each material to allow you to customize this!