Renderer math or something else not working

I am trying to write a script that takes in a list of parameters as an input and outputs a rendered image so far I have this code:

local RenderScale = 1

local Camera = {
	Pos = Vector3.new(0,0,0),
	Rot = Vector3.new(0,0,0),
	Fov = 90,
	Range = 50
}
local Objects = {
	{
		Type = "Sphere",
		Pos = Vector3.new(0,0,-5),
		Radius = 2,
		Color = Color3.new(0.843137, 0.235294, 0.294118)
	}
}

local function CreatePixel(PosX,PosY,Color)
	local pixel = script.Pixel:Clone()
	pixel.Parent = script.Parent.Parent.Pixels
	pixel.BackgroundColor3 = Color
	pixel.Position = UDim2.new(0,PosX/RenderScale,0,PosY/RenderScale)
	pixel.Size = UDim2.new(0,1/RenderScale,0,1/RenderScale)
end

local function SphereIntersect(c,r,d)
	local m = Camera.Pos - c
	local b = m:Dot(d)
	local c = 1 - (r * r)
	
	if c > 0 and b > 0 then
		return false
	end
	
	local discriminant = b * b - c
	
	if discriminant < 0 then
		return false
	end
	
	local t = math.clamp(-b - math.sqrt(discriminant),0,math.huge)
	local q = Camera.Pos + Vector3.new(t,t,t) * Vector3.new(discriminant,discriminant,discriminant)
	
	return q
end


local function Render()

	local ResolutionX = game.Players.LocalPlayer:GetMouse().ViewSizeX * RenderScale
	local ResolutionY = game.Players.LocalPlayer:GetMouse().ViewSizeY * 0.95 * RenderScale
	local DegreeIncrement = Camera.Fov/ResolutionX/RenderScale
	
	for Y = 1,ResolutionY*RenderScale,1 do
		print(Y.." / "..ResolutionY*RenderScale)
		wait()
		for X = 1,ResolutionX*RenderScale,1 do
			local ClosestPos = Camera.Pos+Vector3.new(Camera.Range,Camera.Range,Camera.Range)
			local ClosestDist = Camera.Range
			local Closest = nil
			for o = 1,#Objects,1 do
				
				if Objects[o].Type == "Sphere" then
					result = SphereIntersect(Objects[o].Pos,Objects[o].Radius,Vector3.new(math.sin((Camera.Rot.Y+X*DegreeIncrement)*math.pi/180),math.tan((Camera.Rot.X+Y*DegreeIncrement)*math.pi/180),math.cos((Camera.Rot.Y+X*DegreeIncrement)*math.pi/180)))
				end
				
				if result ~= false then
					if (Camera.Pos-result).Magnitude < ClosestDist then
						ClosestPos = result
						ClosestDist = (Camera.Pos-result).Magnitude
						Closest = Objects[o]
					end
				end
			end
			if Closest ~= nil then
				CreatePixel(X,Y,Closest.Color)
			end
		end
	end
end

script.Parent.Render.MouseButton1Down:Connect(function()
	print("Clearing screen")
	script.Parent.Parent.Pixels:ClearAllChildren()
	print("Cleared")
	print("Rendering in 3 seconds")
	wait(1)
	print("Rendering in 2 seconds")
	wait(1)
	print("Rendering in 1 seconds")
	wait(1)
	print("RENDERING")
	Render()
end)

script.Parent.Turn.MouseButton1Down:Connect(function()
	Camera.Rot = Camera.Rot + Vector3.new(0,90,0)
end)

But it just makes the entire screen red and when it calculates the distance between the ray intersect point and camera it outputs 0 every time instead of displaying a sphere. I suspect there is something wrong with the SphereIntersect() math, but since it already took me an hour to find some math that somewhat understand how to implement, I have no idea how to debug this.

Take a step back, and just try running your SphereIntersect function in a loop with something easy to test.

Maybe use a sphere in workspace as a visual, and use your player position as the test position.

If it’s intersecting, print “true” or color the sphere green or something. If it’s not, do the opposite.

That will tell you whether that function is working correctly or not with confidence.

The line where you call SphereIntersect is also pretty hard to understand. To clean it up a little, I suggest using math.rad(...) instead of (...)*math.pi/180—it does the same thing, but the former is a little easier to see what you meant IMO. You could also break it into a few variables/lines to help readability.

It would also help if there were some comments or better parameter names on SphereIntersect. I assume c is “center” and r is “radius”? What’s d?

d is a normalised vector to where the current ray is pointing, that ray being the camera’s look vector + an offset calculated by which pixel is currently being rendered. I tried printing the distance of the hit position to the camera and every pixel reports 0. I copied the math for the sphere intersection (not code only math) from google.

This is where I copied over the same equations but in lua not c++

Your SphereIntersect function is incorrect.

local c = 1 - (r * r)

should be

local c = m:Dot(m) - (r * r)

because m:Dot(m) is equal to m.Magnitude * m.Magnitude, which is not usually 1.

still doesn’t work. I could give you a link to the game if want

it is kinda hard to know what you did wrong if we don’t know what equation you are working with, there are multiple ways to represent a sphere equation. Also, it’s probably not a good idea to just return the sphere intersection. Here is how I did mine

    local o_minus_c = r.Origin - sphere.position

    local p = ray.Dir:Dot(o_minus_c)
    local q = o_minus_c:Dot(o_minus_c) - (sphereRadius * sphereRadius)
    
    local discriminant = (p * p) - q;
    if (discriminant < 0) then return 0 end
    float dRoot = math.sqrt(discriminant);
    dist1 = -p - dRoot;
    dist2 = -p + dRoot;

    return (dist1>dist2 and dist2 or dist1)

also, your ray direction code looks sussy as hecc. Sure there are perspective equations but most people just do something like

local uv = Vector3.new(x/width, y/height,0)
--this depends on how your pixels are generated
uv = uv - 0.5
uv = uv * Vector3.new(uv.x/uv.y, 1,0) 
--
ray.Direction = Vector3.new(uv.x, uv.y, 1).Unit

also this is just kinda useless when you could have just used your t scalar

if (Camera.Pos-result).Magnitude < ClosestDist then

also your way of generating pixels is super slow

you could also just try flipping your z for your sphere, you probably just forgor to account for negative t vales and get undefined behavior like that, also lowering the radius could probably help a tad. 0.5 should be fine

1 Like