This would be amazing for so many games, really hope it goes open source
I TOTALLY plan to make this an easy to implement open source project!
As a matter of fact it is currently literally just a single ScreenGui
. When you put it in your game it just works, and it will stay that way! You’ll be able to plop this into your game and not even touch the code!
Yeah it really improves performance too! Yeah I knew it was all on one core, but I was hoping it would provide at least a little extra speed, it did not ahaha.
Alright, I’m totally ready to make this open source, and I would love to hear critique. If you can find even a single line that could be causing a fraction of lag please tell me!
In this reply I will be demonstrating the use of each script and documenting how they’re written and perform.
This image depicts everything there is with this, and the Settings
and FPS
parts are entirely optional, you can use them to find the settings you want, then delete them.
The GuiRemover
script just get’s rid of all default roblox UI, this can also be removed if you really want.
The Background
Frame itself really just serves to be a black background, but again this can also be removed and will not affect the pixelation at all. Really you can think of it as a filter on the screen, as long as you still have that Main
Frame, it will function.
Getting into the Main
Frame!
There are two scripts, one is a regular local script, and the other is a module script used for creating and saving your screen settings.
The ModuleScript
‘FrameCreator
’ has the following properties and methods:
Properties: [Property Datatype] FrameCreator.Property (Limits)
-
[Integer] FrameCreator.Resolution (Current Limits: 1 to 200)
The Resolution of the image to be rendered, it is in the form of X by X pixel resolution, X being the number given. It is read only! There is a method to change it, use that! -
[Number] FrameCreator.RenderDist
The length that each raycast for each pixel will travel before stopping and defaulting to sky. It is read only! There is a method to change it, use that! -
[Integer] FrameCreator.PaxelSize (Unfortunate Limits: 1 to 10)
The number of pixels that will exist in each paxel, this number is automatically calculated currently, it will set itself to the highest number between it’s limits that is a modulo of the Resolution. It is read only and updates automatically! -
[Number] FrameCreator.Center
The exact center of theMain
Frame, used to generate thePixelToWorldspace
that provides the direction of the raycast. It is read only and updates automatically! -
[Boolean] FrameCreator.Materialization
Dictates whether or not to perform the color calculations to generate persistent materials onto the projected parts, Materials are currently performed by a random number generator whos seed is determined by a calculation based on the position and orientation of the part and raycast position. The system is very good, but will take a lot of number playing to make all of the materials look good. This property is Read and Write and will update as soon as it’s changed without needing to recreate the frame. -
[Boolean] FrameCreator.Shading
Dictates whether or not to calculate the proper shading for each pixel based on it’s cross product of the angle of the normal and the direction toward the sun, it takes close to no additional processing power, and adds a lot of detail. This property is Read and Write and will update as soon as it’s changed without needing to recreate the frame. -
[Boolean] FrameCreator.Shadows
Dictates whether or not to cast an additional ray for each pixel that hits a part to test if it is blocking the sun, in which case a shadow will be drawn by making that pixel darker. This takes a considerable amount of processing power, but less than the power it takes to calculate each ray regardless, I’m unsure why. This property is Read and Write and will update as soon as it’s changed without needing to recreate the frame. -
[Boolean] FrameCreator.LineByLine
Dictates whether or not to partition the pixel calculations betweenStepped
loop intervals. It will, instead of calculating each frame and then drawing it all at once, calculate 10 lines of the frame 10 I have found to be the best number in this case for performance at a time and then display them in order until reaching the bottom and restarting at the top. This allows for higher Game FPS at the expense of the FPS of theMain
Frame. In short, it will allow you to not crash regardless of what you make the settings. This property is Read and Write and will update as soon as it’s changed without needing to recreate the frame. -
[Boolean] FrameCreator.Skybox
Dictates whether or not to calculate a gradient color to represent the sky when a raycast did not hit a part, similarly to Shading, it takes very little processing power, and adds good detail. I am considering also making the sun visible, and maybe clouds as well. This property is Read and Write and will update as soon as it’s changed without needing to recreate the frame.
Methods: [Return Datatype] FrameCreator.MethodName([Parameter Datatype])
-
[Table of ColorSequenceKeypoint] FrameCreator.CreateKeyPoints(Table of Color3)
When given a table of 1 to 10 Color3 values it will return a table of ColorSequenceKeypoint values that will allow you to create a ColorSequence value of distinct sharp pixels, hard edges and no gradient. It’s used to calculate the color for each paxel. -
[Sync Void] FrameCreator.SetResolution([Integer]Res, [Number]Render)
When given a resolution or Res, as well as an optional render distance or Render, it will set the Resolution and RenderDist accordingly as well as recalculate the PaxelSize and Center and then it will delete the current frame and re create it with our new settings. This method is also how a first frame is initialized. Additionally, it’s synchronous; I did this so that you could be certain the code beneath calling this method won’t run until the frame is ready. -
[BaseGui Frame] FrameCreator.GetPaxel([Integer]X, [Integer]Y)
When given an X in the paxel space, not pixel, and a Y assuming these numbers are within the bounds of the frame it will return the BaseGui Frame Object of that paxel, this can then be used later to set each pixel color. -
[Async Void] FrameCreator.SetPaxelColors([BaseGui Frame]Paxel, [ColorSequence]ColorSeq)
When given a paxel or Frame in the form of a BaseGui Frame Object obtained via the GetPaxel() method as well as a ColorSequence or ColorSeq obtained via the CreateKeyPoints() method this method will set the color of the paxle’s UIGradient object to ColorSeq. This is used to set the color of each paxel and each pixel individually. It is not synchronous, code will not wait for it to finish to continue.
Alright! That pretty much covers everything there is worth knowing about the FrameCreator module.
The only thing left to go over is the RayCaster script which does all of the heavy lifting and calculations for each pixel on the screen as well as handles what to do with all of the settings given in FrameCreator.
Really it’s not as formatted as FrameCreator is and frankly not worth me going over is as much detail, but there are some major point I want to explain in case people are confused.
The Material Persistence Calculations:
local MaterialColorOffset = {
{Enum.Material.Brick, -10, 0, 10, nil, function(Position)
return Position.X * Position.Y * Position.Z
end},
{Enum.Material.Grass, -10, 0, 0.01, nil, function(Position)
return Position.X + Position.Y + Position.Z
end},
{Enum.Material.Cobblestone, -10, 0, 1, {31, 16, 6}},
{Enum.Material.Pebble, -20, 0, 10, nil, function(Position)
return Position.X * Position.Y * Position.Z
end},
{Enum.Material.Concrete, -5, 0, 5, nil, function(Position)
return Position.X * Position.Y * Position.Z
end},
{Enum.Material.Metal, -3, 0, 3, {-30, -30, -30}, function(Position)
return Position.X + Position.Y + Position.Z
end},
{Enum.Material.Neon, 0, 0, 1, {81, 81, 81}, function(Position)
return 0
end},
{Enum.Material.Sand, -10, 0, 0.1, nil, function(Position)
return math.sin(Position.X + Position.Z) * 5
end},
{Enum.Material.WoodPlanks, -20, 0, 20, nil, function(Position)
return Position.X
end},
}
This lookup table defines how each material will behave and be calculated. It isn’t a dictionary so it must be hard to understand, allow me to explain.
The MaterialColorOffset table contains tables of the following format to describe the calculation behavior of each material:
{
Material,
Lowest Brightness Offset,
Highest Brightness Offset,
Brightness Offset Step Size,
{
Red Offset,
Green Offset,
Blue Offset
},
Material Seed Calculation()
}
-
The first value given is the Material that this data refers to.
-
The second value is the Lowest Brightness Offset. The Brightness offset is how materials are performed, using math to calculate particular areas to shade in order to recreate each material. This value represents the lowest it will randomly generate to subtract from the brightness.
-
The third value is the Highest Brightness Offset. This value represents the highest it will randomly generate to subtract from the brightness of each pixel.
-
The fourth value is the Brightness Offset Step Size. This is essentially in what steps can the random number between Lowest Brightness Offset and Highest Brightness Offset be calculated, assume that your lowest is -10, and your highest is 0, if your step size is 1 then you will get 10 possible random numbers, -10 through 0; However, if your step size were 5 then you would only have 3 possible random numbers, -10, -5, and 0. These numbers are in the RGB 0 to 255 space.
-
The fifth value is that Color Offset. This is represented by a table of 3 numbers all of which are in the RGB 0 to 255 space. They represent the R, G, and B offset respectively, they will be added to the final color to tint the material, as some roblox materials change the color of the part slightly.
-
The sixth and final value is the Material Seed Calculation. This is a function that will receive a parameter in the form of a Vector3 position value. This position value is calculated with the following code:
Raycast.Instance.CFrame:inverse() * CFrame.new(Raycast.Position)
‘Racycast’ being the current cast for each pixel. Using the calculation, the material shading is able to be persistent with both position and orientation of any part.
That is all folks!
To anyone that actually read all of that, uhm great now you’re 100% equipped to work on this with me!
To everyone else, the majority of you, I hope that my use of bold and italics will help you skim through it when you need specific information.
Thanks all, and I hope we can make this efficient enough to put into any project!
I have so many ideas how to use and abuse this hehehehe
@Ethanthegrand14 is so cool for this: Super Optimised Pixel Raytracer with Textures! (120+ FPS!)
Don’t read my thread go to this one.
Okay so I really hate to drag this post on and keep bumping it, but I don’t really have a place to post updates so this reply will serve as a board for me to add stuff as it is found.
From this point forward all updates will be added as edits to this reply so that this board can stay as clean as possible, thanks for your patience.
Updates:
-
Raycasts are not as expensive as you think! I found this out by mimicking raycast results with a simple table of pure results. I found that with these static results performance was only increased by about 40%. This means that 60% of the work right now is just in rendering and calculations, most of which are for the ray casts but still. It makes sense considering my paxels took this idea from a funny joke to a viable pixel filter for games. What needs to be managed now is optimizing the renderer. I need to find a way to set each pixel in a faster, less loopy manner. I believe making the renderer more efficient is the only way to proceed, if I can get it to a better order then I can reiterate my calculations, maybe even make them less expensive. [11/5/20 - 1:54am]
-
I’ve added transparency! It took me so long to figure out all of the color combining and everything, I’m so glad it’s done with.
Thank you to this post for basically solving my issue with the colors. Fixed bug where overlapping transparencies would infinite loop! -
Added fog as well as the sun. When
Skybox
is enabled you’ll be able to see the sun, and now you can alter fog settings. -
Upgraded the sun. I made the sun look a little bit better.
-
Implemented @Ethanthegrand14’s reflectance!! Now mirrors are a simple click away, and it’s very cost efficient!
Now that is awesome. I haven’t tried this out yet. But it looks much better and faster than my old raycast pixelater.
It’s unbelievable that you got this to run so smoothly. You even implement textures into it! I mean almost everyone thought that renderers would cause too much lag or would NEVER be this fast.
Keep up the great work!
This is so interesting! I hope this can be used in games in the future
I have found a bug with your renderer.
It appears the script breaks when I attempt to look at these parts that I created:
ALSO
You should make it so that transparent objects work with this renderer.
Hey! Uh so quick thing here sorry. That’s not an actual bug haha, I’m working on it at this very second and happen to have some things disabled, causing the bug you’re experiencing.
Sorry about that!!
Oh, lol. What are you working on?
I’m currently trying to make it scale with FOV. You probably haven’t tested this yet, but this only works with 70 FOV so far. Actually could you help me out with this one?
So there is a simple calculation I’m doing to get the world position of a pixel, it goes like this:
Camera.CFrame * CFrame.new((x + i) - FC.Center, FC.Center - y, -(FC.Resolution / 1.4))
x being the xth paxel in the row.
y being the yth pixel in the column.
i being the ith pixel in the paxel.
FC.Center being the center of the Frame.
FC.Resolution being the X by X pixel resolution.
finally 1.4 It is the number that I found via brute force a while ago that works with 70 FOV.
It’s unfortunately not linear, as in the number that replaces 1.4 does not scale linearly with the FOV.
Here is my data from a few tests.
FOV | Divider
_________________
100 | 2.38
90 | 2
80 | 1.68
70 | 1.4
60 | 1.15
52.25 | 0.98
50 | 0.93
40 | 0.725
35 | 0.63
30 | 0.535
20 | 0.353
17.25 | 0.304
10 | 0.175
Any chance you can figure out the correlation among these numbers?
Hm. Maybe I can make up a calculation that allows you to remove the 1.4 (the divider) and instead goes by the camera’s FOV property. I’ll keep you up to date.
Also may I ask what paxel is?
This explains paxels, let me know if you have any deeper questions about them.
Using ColorSequence
values, I can set the UIGradient
to have 10 side by side colors that look identical to single pixels, but it’s all in a single frame.
Oh my. That is so smart! I would have never thought of using the new UIGradient as a solution to frame count.
You should also probably get the frame to go all the way across the screen, rather than just a small portion.
This would also in theory increase framerate by a lot!
Unfortunately ColorSequence
values only accept 10 colors, so it’s only possible to make a Paxel
of a maximum of 10 pixels.
Also, I put everything back together if you want to regrab your copy to play with.
I’m pretty sure that bug you found was legit though now that I’m thinking about it. It wasn’t the material that was the issue though, it was the color.
I’ll need to set some more hard limits on color tinting and variation, please let me know if that bug persists.
Oh and another thing, it would be wicked if you could make like ideas for materials. It’s kinda hard to explain, but I’ve already done it in my solution post. Basically just imagine a pattern you could make with math, and shoot the idea. I’m running low on ideas for what the heck I’m supposed to make brick look like.
Oh and by the way, here’s that data turned into a line graph:
Is there any reason that’s stopping you from making a simple equation to calculate FOV for your renderer?
Here’s my simple equation that uses the camera’s FOV to calculate the renderer FOV.
Its also pretty close to your results
Using ‘FOV value DIVIDED BY 50’ e.g. Camera.FieldOfView ÷ 50 = 1.4
Input FOV | Output Divider
_____________________
100 = 2
90 = 1.8
80 = 1.6
70 = 1.4
60 = 1.2
50 = 1
40 = 0.8
30 = 0.6
20 = 0.4
10 = 0.2
local Divider = Camera.FieldOfView / 50
local PixelToWorldspace = Camera.CFrame * CFrame.new(Vector3.new((x + i) - FC.Center, FC.Center - y, -(FC.Resolution / Divider)))
Basically what was stopping me from doing this is that I would like to to be more precise than “close to”.
Even a tenth of the dividend could throw the image off my a factor of 2.
EDIT:
I made transparency!
Oh I see.
Unless I somehow find a way to get a math formula for roblox’s field of view, I don’t think there are any possible solutions to recreate that FOV effect with math.
EDIT:
I’ve just figured out how to change linear graphs to non linear graphs and my results are getting very damn close to your graph. All I have to do is some fine tuning to the formula, which I can do tomorrow. So expect a reply from me then.
I can’t test this (the website is dead?) but you might wanna try using textlabel rich text and the block text character rather than gradients