Skip to content

Instantly share code, notes, and snippets.

@michlbro
Created December 30, 2022 23:42
Show Gist options
  • Save michlbro/2bb08a0ef3557fd5c41394253037ecae to your computer and use it in GitHub Desktop.
Save michlbro/2bb08a0ef3557fd5c41394253037ecae to your computer and use it in GitHub Desktop.
local Lighting = game:GetService("Lighting")
local colour = require(script.ColourModule)
local config = require(script.Config)
-- CONFIG Is a module
--[[
return {
Camera = {
CFrame = CFrame.new(-78.37872314453125, 7.211678504943848, -20.33516502380371) * CFrame.fromOrientation(-0.18555288016796112, 2.489468812942505, -0),
Size = Vector2.new(100, 100),
PixelPerStud = 10,
Fov = math.rad(70),
RaycastDistance = 500
},
Scene = {
Terrain = false,
Sun = {
Enabled = true,
Intensity = 1.4,
Colour = Color3.new(1, 1, 1)
},
Objects = {workspace.Scene, workspace.Baseplate}, -- {...}
Lights = {workspace.Lights}, -- {...}
Materials = {
[Enum.Material.Plastic] = {
Reflectance = 0.6
},
[Enum.Material.Neon] = {
Intensity = 1
}
}
},
Render = {
Resolution = Vector2.new(200, 200),
PixelSize = .05,
PixelProperties = {
Anchored = true,
CanCollide = false,
CanQuery = false,
CanTouch = false,
Color = Color3.new(),
Locked = true
},
ScreenCFrame = CFrame.new(-70.379, 7.212, -20.335) * CFrame.fromOrientation(0, math.pi, 0),
ScreenParent = workspace,
ScreenName = "Screen"
}
}
]]
-- colour is my own color3 module
--[[
local colourMethods = {}
function colourMethods:ToColour3()
return Color3.new(math.clamp(self.r, 0, 1), math.clamp(self.g, 0, 1), math.clamp(self.b, 0, 1))
end
local function New(r, g, b)
local colourClass = setmetatable({
r = math.clamp(r, 0, 1),
g = math.clamp(g, 0, 1),
b = math.clamp(b, 0, 1)
}, {__index = colourMethods, __mul = function(tbl, value)
if type(value) == "number" then
return New(tbl.r * value, tbl.g * value, tbl.b * value)
end
return New(tbl.r * value.r, tbl.g * value.g, tbl.b * value.b)
end, __add = function(tbl, value)
return New(tbl.r + value.r, tbl.g + value.g, tbl.b + value.b)
end, __div = function(tbl, value)
if type(value) == "number" then
return New(tbl.r / value, tbl.g / value, tbl.b / value)
end
return New(tbl.r / value.r, tbl.g / value.g, tbl.b / value.b)
end, __sub = function(tbl, value)
return New(tbl.r + value.r, tbl.g + value.g, tbl.b + value.b)
end})
return colourClass
end
local function FromColour3(colour3: Color3)
return New(colour3.r, colour3.g, colour3.b)
end
return setmetatable({New = New, FromColour3 = FromColour3}, {__index = colourMethods})
]]--
local function Setup()
local renderConfig = config.Render
-- Setup pixel template.
local pixelTemplate = script:FindFirstChild("Pixel")
if pixelTemplate then
pixelTemplate:Destroy()
end
pixelTemplate = Instance.new("MeshPart")
pixelTemplate.Size = Vector3.one * renderConfig.PixelSize
for property, value in renderConfig.PixelProperties do
if not pcall(function() return pixelTemplate[property] end) then
continue
end
pixelTemplate[property] = value
end
pixelTemplate.Parent = script
-- Setup screenModel
local screenModel = renderConfig.ScreenParent:FindFirstChild(renderConfig.ScreenName)
if screenModel and screenModel:IsA("Model") then
warn("[PathTracer Setup]: Found an instance with the same name and type. Caution, instance will be delete.")
screenModel:Destroy()
end
screenModel = Instance.new("Model")
screenModel.Name = renderConfig.ScreenName
screenModel.Parent = workspace
return screenModel, pixelTemplate
end
local function ComputeIllumination(raycastData)
local sceneConfig = config.Scene
local sunDirection = Lighting:GetSunDirection()
local sun = sceneConfig.Sun
for x, row in raycastData do
for y, pixelData in row do
if not pixelData.Color then
continue
end
local pixelColour = colour.FromColour3(Color3.new())
local intensity = 0
local validIllumination = false
if pixelData.Material == Enum.Material.Neon then
pixelData.Color *= (sceneConfig.Materials[pixelData.Material].Intensity or 2)
continue
end
if sceneConfig.Sun.Enabled then
local angle = math.acos(pixelData.Normal:Dot(sunDirection))
if angle > math.pi/2 then
intensity = 0
else
pixelColour += colour.FromColour3(sceneConfig.Sun.Colour) * sceneConfig.Sun.Intensity * (1 - (angle / (math.pi/2)) )
validIllumination = true
end
end
for _, lightSource in sceneConfig.Lights do
if lightSource:IsA("Model") or lightSource:IsA("Folder") then
for _, lightSource in lightSource:GetDescendants() do
if not lightSource:IsA("BasePart") then
continue
end
local angle = math.acos(pixelData.Normal:Dot( (lightSource.Position - pixelData.Position).Unit ))
if angle > math.pi/2 then
intensity = 0
else
pixelColour += colour.FromColour3(lightSource.Color) * (sceneConfig.Materials[lightSource.Material].Intensity or 2) * (1 - (angle / (math.pi/2)) )
validIllumination = true
end
end
continue
end
local angle = math.acos(pixelData.Normal:Dot( (lightSource.Position - pixelData.Position).Unit ))
if angle > math.pi/2 then
intensity = 0
else
pixelColour += colour.FromColour3(lightSource.Color) * (sceneConfig.Materials[lightSource.Material].Intensity or 2) * (1 - (angle / (math.pi/2)) )
validIllumination = true
end
end
if validIllumination then
pixelData.Color *= pixelColour
else
pixelData.Color = colour.FromColour3(Color3.new())
end
end
end
return raycastData
end
local function PathTrace()
local cameraConfig = config.Camera
local sceneConfig = config.Scene
local renderConfig = config.Render
local raycastParams = RaycastParams.new()
local filterDescendantsInstances = {}
if sceneConfig.Terrain then
table.insert(filterDescendantsInstances, workspace.Terrain)
end
for _, instance in sceneConfig.Objects do
table.insert(filterDescendantsInstances, instance)
end
for _, instance in sceneConfig.Lights do
table.insert(filterDescendantsInstances, instance)
end
raycastParams.FilterDescendantsInstances = filterDescendantsInstances
raycastParams.FilterType = Enum.RaycastFilterType.Whitelist
local imagePlaneDistance = ((cameraConfig.Size.X * cameraConfig.PixelPerStud)/2) / math.tan(cameraConfig.Fov/2)
local topLeftPixel = cameraConfig.CFrame * CFrame.new(-(cameraConfig.Size.X * cameraConfig.PixelPerStud)/2, (cameraConfig.Size.Y * cameraConfig.PixelPerStud)/2, -imagePlaneDistance)
local raycastData = {}
local resolution = renderConfig.Resolution
local cameraCFrame = cameraConfig.CFrame
local raycastDistance = cameraConfig.RaycastDistance
local yScale = (cameraConfig.Size.Y * cameraConfig.PixelPerStud)/resolution.Y
local xScale = (cameraConfig.Size.X * cameraConfig.PixelPerStud)/resolution.X
for x = 1, resolution.X do
raycastData[x] = {}
for y = 1, resolution.Y do
raycastData[x][y] = {}
end
end
for x, row in raycastData do
for y, pixelData in row do
local pixelPosition = topLeftPixel * CFrame.new(xScale * x, -yScale * y, 0)
local pixelDirection = (pixelPosition.Position - cameraConfig.CFrame.Position).Unit
local raycastResult = workspace:Raycast(cameraCFrame.Position, pixelDirection * raycastDistance, raycastParams)
if raycastResult then
local instance = raycastResult.Instance
pixelData.Color = colour.FromColour3(instance.Color)
if instance.ClassName == "Terrain" then
pixelData.Color = colour.FromColour3(workspace.Terrain:GetMaterialColor(instance.Material))
end
pixelData.Normal = raycastResult.Normal
pixelData.Position = raycastResult.Position
pixelData.Material = raycastResult.Material
pixelData.Direction = pixelDirection
end
end
end
return ComputeIllumination(raycastData)
end
local function Render(screenModel, pixelTemplate, raycastData)
local renderConfig = config.Render
local resolution = renderConfig.Resolution
local screenCFrame = renderConfig.ScreenCFrame
local pixelSize = renderConfig.PixelSize
local topLeftPixel = screenCFrame * CFrame.new(-(resolution.X*pixelSize)/2, (resolution.Y*pixelSize)/2, 0)
for x, rows in raycastData do
if x % 50 == 0 then
task.wait()
end
for y, row in rows do
local pixel = pixelTemplate:Clone()
pixel.CFrame = topLeftPixel * CFrame.new((x-1) * pixelSize, -(y-1) * pixelSize, 0)
if row.Color then
pixel.Color = row.Color:ToColour3()
end
pixel.Parent = screenModel
end
end
end
local screenModel, pixelTemplate = Setup()
local raycastData = PathTrace()
Render(screenModel, pixelTemplate, raycastData)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment