I have decided to try to make 3D renderer on roblox. It goes somewhat well - I made ZBuffer, triangle rendering, but I have got this problems:
-
If 1 point of triangle is invisible - entire triangle will be invisible.
-
If object is transparent, some triangle pixels will overlap.
-
Sometimes. pixels from 1 object will be rendered ontop of another one, incorrectly. Transparency is not a thing here.
Can anyone tell me, how I can fix this issues? I will accept ideas too.
CODE PART:
Renderer
local Players = game:GetService("Players")
local Player = Players.LocalPlayer
local PlayerGui = Player:WaitForChild("PlayerGui")
local RunService = game:GetService("RunService")
local Camera = workspace.CurrentCamera
local Scene = workspace.Scene
local Modules = {
Canvas = require(script.Canvas),
Objects = require(script.ObjectsData),
ZBuffer = require(script.ZBuffer),
}
local ScreenSize = Vector2.new(130, 100)
local Canvas = Modules.Canvas(ScreenSize)
Canvas.Gui.Parent = PlayerGui
local ZBuffer
local ScreenDistance = 0.001
local ObserveRange = 70*2
local Scale = ScreenSize.X / (2 * ScreenDistance * math.tan(ObserveRange / 2))
local function Vector3OnScreen(Position)
local Relative = Camera.CFrame:Inverse() * Position
Relative = Vector3.new(Relative.X, Relative.Y, -Relative.Z)
--print(Relative)
--[[if Relative.Z < ScreenDistance then
return nil
end]]
local Delta = ScreenDistance / Relative.Z * Scale
--print(Delta)
local Projection = Vector2.new(Relative.X, Relative.Y) * Delta
local Screen = Projection + Vector2.new(ScreenSize.X / 2, -ScreenSize.Y / 2)
local ScreenCoords = Vector2.new(math.floor(Screen.X+0.5), math.floor(-Screen.Y+0.5))
--if ScreenCoords.X >= 0 and ScreenCoords.X < ScreenSize.X and ScreenCoords.Y >= 0 and ScreenCoords.Y < ScreenSize.Y then
return ScreenCoords, Relative.Z
--end
--return nil
end
local function DrawLine(Start, End)
local x1 = math.floor(Start.X)
local x2 = math.floor(End.X)
if (x1 > x2) then
x1, x2 = x2, x1
Start, End = End, Start
end
local d = (End.Y - Start.Y) / (x2 - x1)
local y = Start.Y
for i = x1, x2, 1 do
local pixelY = math.floor(y)
Canvas:SetPixelSilent(Vector2.new(i, pixelY), 255, 0, 0)
y += d
end
end
local function DrawTriangle(Point1, Point2, Point3, R, G, B, A)
if Point1.X < 0 or Point1.X > ScreenSize.X-1 or Point1.Y < 0 or Point1.Y > ScreenSize.Y-1 then
return
end
if Point2.X < 0 or Point2.X > ScreenSize.X-1 or Point2.Y < 0 or Point2.Y > ScreenSize.Y-1 then
return
end
if Point3.X < 0 or Point3.X > ScreenSize.X-1 or Point3.Y < 0 or Point3.Y > ScreenSize.Y-1 then
return
end
if (Point1.X > Point2.X) then
Point1, Point2 = Point2, Point1
end
if (Point2.X > Point3.X) then
Point2, Point3 = Point3, Point2
if (Point1.X > Point2.X) then
Point1, Point2 = Point2, Point1
end
end
--print(Point1.X, Point2.X, Point3.X)
local steps12 = math.max(Point2.X-Point1.X, 1)
local steps13 = math.max(Point3.X-Point1.X, 1)
local steps32 = math.max(Point3.X-Point2.X, 1)
local steps31 = math.max(Point3.X-Point1.X, 1)
local upDelta = (Point2.Y - Point1.Y) / steps12
local downDelta = (Point3.Y - Point1.Y) / steps13
if upDelta < downDelta then
upDelta, downDelta = downDelta, upDelta
end
local Top, Bottom = math.max(Point1.Y, Point2.Y, Point3.Y), math.min(Point1.Y, Point2.Y, Point3.Y)
local up = Point1.Y
local down = Point1.Y
local y23 = Point2.Y-Point3.Y
local y31 = Point3.Y-Point1.Y
local y12 = Point1.Y-Point2.Y
local Volume = Point1.X*y23+Point2.X*y31+Point3.X*y12
for i = math.floor(Point1.X), math.floor(Point2.X), 1 do
for g = math.floor(down), math.floor(up), 1 do
local y14 = Point1.Y-g
local y24 = Point2.Y-g
local y34 = Point3.Y-g
local Volume1 = i*y23+Point2.X*y34+Point3.X*-y24
local Volume2 = Point1.X*-y34+i*y31+Point3.X*y14
local Volume3 = Point1.X*y24+Point2.X*-y14+i*y12
local Weight1, Weight2, Weight3 = Volume1/Volume, Volume2/Volume, Volume3/Volume
local TotalZ = Point1.Z*Weight1+Point2.Z*Weight2+Point3.Z*Weight3
ZBuffer:Add(Vector2.new(i, g), R, G, B, A, TotalZ)
end
up += upDelta
down += downDelta
end
upDelta = (Point3.Y - Point2.Y) / steps32
downDelta = (Point3.Y - Point1.Y) / steps31
if upDelta < downDelta then
upDelta, downDelta = downDelta, upDelta
end
up = Point3.Y
down = Point3.Y
for i = math.floor(Point3.X), math.floor(Point2.X), -1 do
for g = math.floor(down), math.floor(up), -1 do
local y14 = Point1.Y-g
local y24 = Point2.Y-g
local y34 = Point3.Y-g
local Volume1 = i*y23+Point2.X*y34+Point3.X*-y24
local Volume2 = Point1.X*-y34+i*y31+Point3.X*y14
local Volume3 = Point1.X*y24+Point2.X*-y14+i*y12
local Weight1, Weight2, Weight3 = Volume1/Volume, Volume2/Volume, Volume3/Volume
local TotalZ = Point1.Z*Weight1+Point2.Z*Weight2+Point3.Z*Weight3
--local R, G, B = Volume1/Volume*255, Volume2/Volume*255, Volume3/Volume*255
ZBuffer:Add(Vector2.new(i, g), R, G, B, A, TotalZ)
--print(i, g)
--Canvas:SetPixel(Vector2.new(i, g), R, G, B)
end
up -= upDelta
down -= downDelta
end
end
RunService.RenderStepped:Connect(function(Delta)
ZBuffer = Modules.ZBuffer(ScreenSize)
Canvas:Clear(0, 0, 0)
for _, Object in pairs(Scene:GetChildren()) do
local ObjectData = Modules.Objects.GetObjectData(Object)
local ScreenPoints = {}
for i = 1, #ObjectData.Vertex, 1 do
local ScreenPos, Depth = Vector3OnScreen(ObjectData.Vertex[i])
if ScreenPos then
ScreenPoints[i] = Vector3.new(math.floor(ScreenPos.X), math.floor(ScreenPos.Y), Depth)
end
end
for i = 1, #ObjectData.Faces/3, 1 do
local p1, p2, p3 = ObjectData.Faces[i*3-2], ObjectData.Faces[i*3-1], ObjectData.Faces[i*3]
if ScreenPoints[p1] and ScreenPoints[p2] and ScreenPoints[p3] then
DrawTriangle(ScreenPoints[p1], ScreenPoints[p2], ScreenPoints[p3], Object.Color.R*255, Object.Color.G*255, Object.Color.B*255, Object.Transparency)
end
--[[if ScreenPoints[p1] and ScreenPoints[p2] then
DrawLine(ScreenPoints[p1], ScreenPoints[p2])
end
if ScreenPoints[p1] and ScreenPoints[p3] then
DrawLine(ScreenPoints[p1], ScreenPoints[p3])
end
if ScreenPoints[p2] and ScreenPoints[p3] then
DrawLine(ScreenPoints[p2], ScreenPoints[p3])
end]]
end
end
ZBuffer:Sort()
ZBuffer:Calculate()
local PixelData = buffer.create(ScreenSize.X * ScreenSize.Y*3)
local Buffer = ZBuffer.Buffer
for x = 0, ScreenSize.X-1, 1 do
for y = 0, ScreenSize.Y-1, 1 do
local Cursor = (x+y*ScreenSize.X)*3
buffer.writeu8(PixelData, Cursor, math.clamp(Buffer[x][y][1], 0, 255))
buffer.writeu8(PixelData, Cursor+1, math.clamp(Buffer[x][y][2], 0, 255))
buffer.writeu8(PixelData, Cursor+2, math.clamp(Buffer[x][y][3], 0, 255))
end
end
Canvas:DrawPixels(Vector2.zero, ScreenSize, PixelData)
Canvas:RedrawCanvas()
end)
ZBuffer
local Module = {}
local ModuleMeta = {}
ModuleMeta.__index = ModuleMeta
function Module.Create(Size)
local self = {
Size = Size,
Buffer = {}
}
for x = 0, Size.X-1, 1 do
self.Buffer[x] = {}
for y = 0, Size.Y-1, 1 do
self.Buffer[x][y] = {}
end
end
return setmetatable(self, ModuleMeta)
end
function ModuleMeta:Add(Point, R, G, B, A, Z)
table.insert(self.Buffer[Point.X][Point.Y], {R, G, B, A, Z})
end
local function Compare(a, b)
return a[5] < b[5]
end
function ModuleMeta:Sort()
for x = 0, self.Size.X-1, 1 do
for y = 0, self.Size.Y-1, 1 do
table.sort(self.Buffer[x][y], Compare)
local This = self.Buffer[x][y]
for i = #This, 2, -1 do
local Diff = This[i][5] - This[i-1][5]
if Diff < 0.0001 and Diff > -0.0001 then
table.remove(This, i)
end
end
end
end
end
function ModuleMeta:Calculate()
for x = 0, self.Size.X-1, 1 do
for y = 0, self.Size.Y-1, 1 do
local Pixel = self.Buffer[x][y]
local StartIndex = #Pixel
local StackedA = 1
if StartIndex > 0 then
for i = 1, #Pixel, 1 do
StackedA *= Pixel[i][4]
if StackedA <= 0.01 then
StartIndex = i
break
end
end
end
if StartIndex > 0 then
local R, G, B = 0, 0, 0
for i = StartIndex, 1, -1 do
local ThisPixel = Pixel[i]
--print(ThisPixel)
local NA = 1 - ThisPixel[4]
R = R * ThisPixel[4] + ThisPixel[1] * NA
G = G * ThisPixel[4] + ThisPixel[2] * NA
B = B * ThisPixel[4] + ThisPixel[3] * NA
end
--R, G, B = math.clamp(R, 0, 255), math.clamp(G, 0, 255), math.clamp(B, 0, 255)
self.Buffer[x][y] = {R, G, B, 0, 65535}
else
self.Buffer[x][y] = {0, 0, 0, 0, 65535}
end
end
end
end
return Module.Create
Canvas is simply Frames with UIGradients (Cuz roblox has decided to not unleash power of Editables to not ID verified persons)