another build I’ll never touch again
Love the clean look on this.
Cel Shading v2.0 demonstration
Join the discussion (tutorial here):
https://devforum.roblox.com/t/how-would-you-approach-cel-shading-in-roblox/1790336
Thank you so much! Very much appreciated. I personally just used shadowmap, lowered ambience, and messed with the Lighting Effects. I also made sure to use light fixtures with surfacelights only, and used these as a primary source of lighting.
Gives me Genshin Impact energy, very nice! You’re clearly very talented when it comes to texturing.
looks spectacular! i’m an international school student myself (different country)
Making more random stuff to stall on what I’m actually “supposed” to be doing. This is a really simple script that changes the OutdoorAmbient and ColorShift_Top to a random color at a given interval.
This is the code behind it. Anyone who has any knowledge of scripting whatsoever should be able to determine how it works… just input the BPM of whatever song you’re listening to and go.
while true do
local bpm = 160 -- Change this value to whatever you want!
local bpmformula = (1/(bpm/60))
local lighting = game:GetService("Lighting")
local color = Color3.fromRGB((math.random(1,255)),(math.random(1,255)),(math.random(1,255)))
lighting.OutdoorAmbient = color
lighting.ColorShift_Top = color
wait(bpmformula)
end
I think what I want to do next is implement multiple modes, with this being the “classic” mode and another mode having less frequent color changes, but the colors lerp(??? tween??? I’m not familiar with this terminology) between each other making for a smoother effect.
I also want to make this GUI-based so I don’t have to dig around in the Properties window to check on it.
Update on my previous post.
This is the code so far. Feel free to tell me how to improve it.
local BPM = 120
local waitTime = 0.5
local textBox = script.Parent.TextBox
textBox.FocusLost:Connect(function(enterPressed)
local newBPM = textBox.Text
if enterPressed then
if newBPM == nil then
BPM = 120
waitTime = 0.5
else
BPM = math.clamp(newBPM,1,400)
waitTime = 1/(BPM/60)
end
end
textBox.Text = ""
print(BPM)
end)
while true do
local lighting = game:GetService("Lighting")
local color = Color3.fromRGB(math.random(1,255),math.random(1,255),math.random(1,255))
local label = script.Parent.TextLabel
label.BackgroundColor3 = color
label.Text = tostring(math.floor(color.R*255))..", "..tostring(math.floor(color.G*255))..", "..tostring(math.floor(color.B*255))
lighting.OutdoorAmbient = color
lighting.ColorShift_Top = color
wait(waitTime)
end
You should use task.wait(n) as it is more efficient and performant than wait(n), and you should reference services and other variables outside of loops for performance reasons:
--//Services
local Lighting = game:GetService("Lighting")
--//Variables
local textBox = script.Parent.TextBox
local label = script.Parent.TextLabel
--//Controls
local BPM = 120
local waitTime = 0.5
--//Intialization
local newRandom = Random.new()
--//Functions
textBox.FocusLost:Connect(function(enterPressed)
local newBPM = textBox.Text
if enterPressed then
if newBPM then
BPM = math.clamp(newBPM, 1, 400)
waitTime = 1/(BPM/60)
else
BPM = 120
waitTime = 0.5
end
end
textBox.Text = ""
print(BPM)
end)
while true do
local color = Color3.fromRGB(newRandom:NextInteger(1, 255), newRandom:NextInteger(1, 255), newRandom:NextInteger(1, 255))
label.BackgroundColor3 = color
label.Text = tostring(tostring(math.floor(color.R*255))..", "..tostring(math.floor(color.G*255))..", "..tostring(math.floor(color.B*255)))
Lighting.OutdoorAmbient = color
Lighting.ColorShift_Top = color
task.wait(waitTime)
end
(looks pretty cool though)
Thank you for the advice! By the way, what’s the difference between calling Random.new
and using math.random
? Just curious
I personally just use Random.new() as it has more functions and more customizability. Also because most of the backend of math.random was taken from Random.new() as far as I know.
That’s actually the exact video I watched to make this, I just had no idea how to recreate the global illumination.
Yes I do. it’s actually the only property for the objects in the world (aside from colour of course). I use the roughness value to spread the rays out and to merge colours together for reflections.
local OffsetAngle = math.pi * Roughness
MirroredColour = MirroredColour:Lerp(OriginalColor, Roughness)
I don’t have a metallicness property for the materials.
The light source doesn’t really have a brightness property. The light colour is a single color3 value that just get’s multiplied depending on hit objects and light angle, as explained in the video for the shadows.
If you want to know more, you can have a look at my process reflection function:
function ProcessReflection(Roughness, Normal, IntersectPos, Direction, OriginalColor, CurrentBounces, InitialReflection) -- Per pixel function
if not ReflectionsEnabled or CurrentBounces > MaxReflectionBounces then
return OriginalColor
end
CurrentBounces += 1
local ReflectedDirection = Direction - (2 * Direction:Dot(Normal) * Normal) -- Relfect the direction.
ReflectedDirection = ReflectedDirection * RenderDistance -- New reflected direction
local ReflectedDirectionCFrame = CFrame.new(ReflectedDirection)
local OffsetAngle = MaxReflectionAngle * Roughness
local HitColours = {}
local MirroredColour
for i = 1, RaySamplesPerPixel do
local OffsetReflectedDirection = CFrame.Angles(RanAxis(OffsetAngle), RanAxis(OffsetAngle), RanAxis(OffsetAngle)) * ReflectedDirectionCFrame.Position
local ReflectionRay = workspace:Raycast(IntersectPos, OffsetReflectedDirection)
local ReflectedColour
if ReflectionRay then
local HitPart = ReflectionRay.Instance
if HitPart ~= LightSource then
local HitNormal = ReflectionRay.Normal
local IntersectionPos = ReflectionRay.Position + (HitNormal / 1000)
ReflectedColour = HitPart.Color
ReflectedColour = ProcessShadows(ReflectedColour, OffsetReflectedDirection, IntersectionPos, HitNormal)
-- Recursive reflections
ReflectedColour = ProcessReflection(GetPartRoughness(HitPart), HitNormal, IntersectionPos, OffsetReflectedDirection, ReflectedColour, CurrentBounces)
else
ReflectedColour = LightColour -- Light source colour
end
else
ReflectedColour = Black -- No light
end
--HitColours[i] = ReflectedColour
table.insert(HitColours, ReflectedColour)
if not InitialReflection then
break
end
end
MirroredColour = GetAverageColour(HitColours)
--if not InitialReflection then
MirroredColour = MirroredColour:Lerp(OriginalColor, Roughness)
--MirroredColour = MultiplyColourByColour(MirroredColour, OriginalColor)
--end
return MirroredColour
end