Help with raytracer engine

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Make a Raytracing engine following Sebastian Lague Unity’s Raytracer engine logic (https://www.youtube.com/watch?v=Qz0KTGYJtUk)

  2. What is the issue? I cant see anything

  3. What solutions have you tried so far? ive tried searching on the dev hub for any solutions to this but to no avail

Wierd purple and White pixels appear on my “Screen” which in realty is just a grid of frames

Screen drawer

local ResY = 100
local ResX = 100

local Camera = workspace.CurrentCamera
local Screen = script.Parent
local FakeScreen = Screen:WaitForChild("FakeScreen")
local Tracer = require(script.Tracer)

local PixelArray = {}

local RaycastParam = RaycastParams.new()
RaycastParam.FilterDescendantsInstances = {}
RaycastParam.FilterType = Enum.RaycastFilterType.Exclude

for Y = 0, ResY do
	local Column = Instance.new("Frame", FakeScreen)
	Column.Name = Y
	Column.Size = UDim2.new(1,0,1/ResY,0)
	Column.Position = UDim2.new(0,0,Y/ResY,0)
	Column.Transparency = 1
	Column.BorderSizePixel = 0
	PixelArray[Y] = {}
	for X = 0, ResX do
		local Pixel = Instance.new("Frame", Column)
		Pixel.Name = X .. "_" .. Y
		Pixel.Size = UDim2.new(1/ResX,0,1,0)
		Pixel.Position = UDim2.new(X/ResX,0,0,0)
		Pixel.BorderSizePixel = 0
		Pixel.Transparency = 0
		PixelArray[Y][X] = Pixel
	end
end

function Frame()
	for _, Y in pairs(PixelArray) do
		for z, Pixel in pairs(Y) do
			Pixel.BackgroundColor3 = Tracer.Trace(Pixel)
		end
	end
	task.wait()
	Frame()
end

Frame()

Tracer Module

local Tracer = {}

local RaycastParam = RaycastParams.new()
RaycastParam.FilterDescendantsInstances = {game.Players.LocalPlayer.Character}
RaycastParam.FilterType = Enum.RaycastFilterType.Exclude

local Bounces = 5
local Samples = 5 -- Not in use for now

local function RandomDirection(RNG: number) --, normal: Vector3)
	local Randomf = Random.new(RNG)
	local Dir = Vector3.new(Randomf:NextNumber(), Randomf:NextNumber(), Randomf:NextNumber())
	return Dir --* math.sign(normal:Dot(Dir))
end

function Tracer.Trace(Pixel)
	local InLight = Vector3.new(0,0,0)
	local Color = Vector3.new(255,255,255)

	local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(Pixel.AbsolutePosition.X, Pixel.AbsolutePosition.Y)
	local directionVector = screenToWorldRay.Direction * 5000
	local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector,RaycastParam)
	
	local OriginRay = workspace.CurrentCamera.CFrame.Position
	if raycastResult then
		for i = 1, Bounces do
			local RayInstance = raycastResult.Instance
			local RayDir = (raycastResult.Position - OriginRay).Unit
			
			local DiffusseDir = (raycastResult.Normal + RandomDirection(Pixel.AbsolutePosition.X * 2 + Pixel.AbsolutePosition.Y * 3)).Unit
			local ReflectDir = RayDir - (2 * RayDir:Dot(raycastResult.Normal) * raycastResult.Normal)
			RayDir = DiffusseDir:Lerp(ReflectDir, RayInstance.Smoothness.Value)
			
			raycastResult = workspace:Raycast(raycastResult.Position, RayDir, RaycastParam)
			if raycastResult then
				RayInstance = raycastResult.Instance
				local EmittedLight = Vector3.new(RayInstance.EmissionColor.Value.R * 255 * RayInstance.EmissionStrenght.Value,RayInstance.EmissionColor.Value.G * 255 * RayInstance.EmissionStrenght.Value,RayInstance.EmissionColor.Value.B * 255 * RayInstance.EmissionStrenght.Value)
				InLight += EmittedLight * Color
				Color *= Vector3.new(RayInstance.Color.R * 255, RayInstance.Color.G * 255, RayInstance.Color.B * 255)
			else
				Color = Vector3.new(0,0,0)
				break
			end
			print(InLight)
		end
	end
	return Color3.fromRGB(InLight.X, InLight.Y, InLight.Z)
end

return Tracer

The green ball is supossed to be the light source but only white and purple Pixels appear

2 Likes

From your code it seems like each ray has to make it the full five bounces to get any color at all, which seems unlikely for most pixels. As for the color, it seems like the color is added to after every bounce. Why shouldn’t the color be whatever the color was of the last object it hit?

1 Like

I think the issue is with this function. Firstly, you can just use Randomf:NextUnitVector and invert the direction if it’s outside the reflection hemisphere (it looks like you had this sorted with the commented line), but it looks like the main problem is that a pixel will always have the exact random direction.

This completely negates having multiple bounces, because the bounces will all go in the same direction. The seed value Pixel.AbsolutePosition.X * 2 + Pixel.AbsolutePosition.Y * 3 should probably include a term for the number of frames or total time elapsed.

Actually, you probably don’t even need a custom seed. You can just re-use the same Randomf variable, because the seed changes with every call to one of it’s :Next[xxx] functions. This will also probably give you a little time save as well.

1 Like

i changed the code and now the pixels do change randomly, altough the result is much the same

Changes

local Randomf = Random.new()

local function RandomDirection(normal: Vector3)
	local Dir = Randomf:NextUnitVector()
	return Dir * math.sign(normal:Dot(Dir))
end
local DiffusseDir = RandomDirection(raycastResult.Normal)

Results

1 Like

I’m not familiar with the engine video you were following, but what happens when you remove the Color = Vector3.new(0,0,0) line before you break due to not getting a ray cast result? My guess is this line is making most of your pixels black

I realized that when i do the bounce raycast the direction wasnt being multiplied by the distance, hence why only a few pixels where illuminated, although it made more pixels appear, most of the screen is still black

I think a big issue is that you only have one sample per pixel, which isn’t really isn’t all that great because you are only accounting for a very small number of actual light rays. I remember watching a video on someone making their own raytracer on Unity, and at exactly 17:55, the images shown look quite similar to what you see in your own raytracer.

So, for your case, you need to call the Tracer.Trace function multiple times and average out the color returned.

I also think a majority of your pixels are dark because the light source is quite small, thus the majority of the relatively few rays you are casting miss it entirely, so making the light source bigger should make the scene more visible.

I think that you could also gain quite a bit of time save if you used one of those new EditableImages instead of hundreds of UI Frames.

2 Likes

Funny enough that’s the exact video OP linked to, was just watching it and thought similar about the number of samples.

Something else is amiss though. Many rays that should be just flying directly into the light source sphere are black here. In the latest video it looks like only rays that hit the shadow of the sphere get illuminated. I think it would be a good idea to print what the InLight is every iteration of the loop and maybe get a sense of what’s happening with the color calculation?

1 Like

Oh, whoops! Didn’t catch the link.