Created
December 30, 2022 23:42
-
-
Save michlbro/2bb08a0ef3557fd5c41394253037ecae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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