IF YOU HAVEN'T DONE PART 1, GO TO PART 1
Good job! you have reached the final step.
All the code shown here needs to go in the "Raytracer" module script that we made.
The comments in the code should explain it, if you have more questions ask in the replies.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Utils = require(ReplicatedStorage:WaitForChild("Utils"))
local Settings = require(script:WaitForChild("Settings"))
local Raycast = Utils.Raycast
local Reflect = Utils.Reflect
local RequestEvent = ReplicatedStorage:WaitForChild("requestEvent")
local Player = Players.LocalPlayer
local Camera = workspace.CurrentCamera
-- Gets the direction between a and b
local function getDir(a,b)
return (b-a).Unit
end
-- We use this to convert a Color3 to a table to be sent to the python server.
local function colToTable(col)
return {col.R*255,col.G*255,col.B*255}
end
-- Calculates the shadow of a pixel
local function calcShadow(Color,Position,Normal,Direction,ShouldShadow)
-- Raycast to the sun
local P = Position + (Normal/100)
local D = getDir(P,workspace.Sun.Position)
local Shadow = Raycast(P,D,Settings.MaxDistance,{workspace.Trace,workspace.Sun})
if Shadow.Instance ~= workspace.Sun and not ShouldShadow then
local h,s,v = Color:toHSV()
local NormalShadow = .25
-- Darken the pixel by NormalShadow
return Color3.fromHSV(h,s,v-NormalShadow)
elseif Shadow.Instance == workspace.Sun then
-- specular highlighting
local h,s,v = Color:toHSV()
local NormalBright = .25
local Reflection = Reflect(Direction,Normal)
local DirectionToSun = getDir(Position,workspace.Sun.Position)
-- Multiple .25 by the dot product of our reflection vector and the direction of the sun from our position
NormalBright *= (Reflection:Dot(DirectionToSun))
NormalBright = math.clamp(NormalBright,0,1)
return Color3.fromHSV(h,s,v+NormalBright)
end
return Color
end
-- This function is a bit confusing im not sure how to describe it that well.
local function calculateReflection(Dir,Norm,Reflectivity,Position,Color,bounces)
if Reflectivity == 0 then
return Color
end
local Reflection = Reflect(Dir,Norm)
local Result = Raycast(Position,Reflection,Settings.MaxDistance,{workspace.Trace})
if Result then
-- Lerp the color from our default color to the reflected color depending on how reflective the surface is
Color = Color:lerp(Result.Instance.Color,Reflectivity)
if Result.Instance.Reflectance > 0 and bounces < Settings.Samples then
-- Does all the reflection bounces then calculates the shadow
local temp = calculateReflection(getDir(Position,Result.Position),Result.Normal,Result.Instance.Reflectance,Result.Position,Color,bounces)
Color = Color:lerp(temp,Reflectivity)
Color = calcShadow(Color,Result.Position,Result.Normal,Reflection)
else
-- Just calculates the shadow
Color = calcShadow(Color,Result.Position,Result.Normal,Reflection)
end
bounces += 1
else
-- If our reflection hit the sky then just lerp to the sky color
Color = Color:lerp(Settings.SkyColor,Reflectivity)
end
return Color
end
local Raytracer = {}
Raytracer.__index = Raytracer
function Raytracer.init(SizeX,SizeY,StartPos)
-- tells the server to init the image on the python server
RequestEvent:InvokeServer{
request_type = 1,
image_size_x = SizeX,
image_size_y = SizeY
}
local self = setmetatable({},Raytracer)
self.sizex = SizeX
self.sizey = SizeY
self.startpos = StartPos
return self
end
-- This doesnt need much info on it pretty straight forward
function Raytracer:Raytrace()
local pixelCache = {}
local finished = 0
for y = 1,self.sizey do
pixelCache[y] = {}
print(y)
task.spawn(function()
for x = 1,self.sizex do
local pixelPosition = Vector2.new(x,y)
pixelPosition += self.startpos
local pixelColor = nil
local ray = Camera:ScreenPointToRay(pixelPosition.X,pixelPosition.Y)
local MaxDistance = Settings.MaxDistance
local Result = Raycast(ray.Origin,ray.Direction,Settings.MaxDistance,{workspace.Trace})
if not Result then
pixelColor = Settings.SkyColor
pixelCache[y][x] = colToTable(pixelColor)
RunService.RenderStepped:Wait()
continue
else
pixelColor = Result.Instance.Color
end
local HasReflected = false
if Result.Instance.Reflectance > 0 then
pixelColor = calculateReflection(ray.Direction,Result.Normal,Result.Instance.Reflectance,Result.Position,pixelColor,0)
HasReflected = true
pixelColor = calcShadow(pixelColor,Result.Position,Result.Normal,ray.Direction,true)
else
pixelColor = calcShadow(pixelColor,Result.Position,Result.Normal,ray.Direction)
end
-- This was a really bad attempt at anti aliasing (it doesnt work but does create some cool outlines)
--if Result then
-- local ray1 = Camera:ScreenPointToRay(pixelPosition.X+1,pixelPosition.Y)
-- local ray2 = Camera:ScreenPointToRay(pixelPosition.X,pixelPosition.Y+1)
-- local ray3 = Camera:ScreenPointToRay(pixelPosition.X-1,pixelPosition.Y)
-- local ray4 = Camera:ScreenPointToRay(pixelPosition.X,pixelPosition.Y-1)
-- local Result1 = Raycast(ray1.Origin,ray1.Direction,Settings.MaxDistance,{workspace.Trace})
-- local Result2 = Raycast(ray2.Origin,ray2.Direction,Settings.MaxDistance,{workspace.Trace})
-- local Result3 = Raycast(ray3.Origin,ray3.Direction,Settings.MaxDistance,{workspace.Trace})
-- local Result4 = Raycast(ray4.Origin,ray4.Direction,Settings.MaxDistance,{workspace.Trace})
-- local blendTable = {}
-- if Result1 then
-- if Result1.Instance ~= Result.Instance then
-- table.insert(blendTable,Result1)
-- end
-- end
-- if Result2 then
-- if Result2.Instance ~= Result.Instance then
-- table.insert(blendTable,Result2)
-- end
-- end
-- if Result3 then
-- if Result3.Instance ~= Result.Instance then
-- table.insert(blendTable,Result3)
-- end
-- end
-- if Result4 then
-- if Result4.Instance ~= Result.Instance then
-- table.insert(blendTable,Result4)
-- end
-- end
-- local finalColor = pixelColor
-- for _,v in blendTable do
-- finalColor = Color3.new(
-- (finalColor.R + v.Instance.Color.R)/2,
-- (finalColor.G + v.Instance.Color.G)/2,
-- (finalColor.B + v.Instance.Color.B)/2
-- )
-- end
-- pixelColor = finalColor
--end
pixelCache[y][x] = colToTable(pixelColor)
end
finished += 1
end)
RunService.RenderStepped:Wait()
end
repeat
warn("waiting for all pixels to finish...")
warn(("%s/%s"):format(finished,self.sizey))
task.wait(.25)
until finished == self.sizey
-- tell the server to save our pixels
RequestEvent:InvokeServer{
request_type = 2,
pixel_data = pixelCache,
imageSize = Vector2.new(0,self.sizey)
}
-- tell the server to save the image
RequestEvent:InvokeServer{
request_type = 3,
}
end
return Raytracer
Remember when you use the raytracer you have the python server running, for details on the python server check Part 1.
Never move your camera while Raytracing or you are likely to get weird results.
Don't worry about it Raytracing your character as it only Raytraces what is in the "Trace" folder in workspace.
Thats about it. If i've missed anything out or there are any bugs be sure to tell me!
Oh and most importantly, have fun!
Psst.. Heres an image that I made with the raytracer: