Can you show player shadows in first person? (Shadowmapping)

I’m trying to make an immersive game in which you battle in first person. Unfortunately, because your body turns invisible, it doesn’t have a shadow. Is there any way to counter this besides making it visible with Transparency or LocalTransparencyModifier?

Shadow1

EDIT: Mistake.

1 Like

You could try to replicate the entire body when you zoom in, so that it all gets a shadow, just like what you did with the arms.

This may mean you’ll have some torso in the way of looking down, which sucks but it is inevitable.

I’m trying to avoid doing that (you don’t need to replicate it by the way you can just use the LocalTransparencyModifier property). Essentially I need to be able to cast shadows on parts that are not visible to the client.

The only way around this that I can think of is creating a duplicate of a player’s character behind the actual player far enough so that the player won’t see this clone. It will show the player’s shadow in first person, however you need to constantly position and rotate the clone’s body parts to the player’s body parts to avoid a static shadow effect.

3 Likes

Unfortunately, I think you’re right. Seems like more trouble than it’s worth to be honest.

this video is a toturial on how to make it
if this helps pls mark as solution :smiley:

local player = game.Players.LocalPlayer
local camera = workspace.CurrentCamera
local lighting = game:GetService("Lighting")
local rs = game:GetService("RunService")
local activeChar
local reg = {}

local function spawn(func,...)
	local b = Instance.new("BindableEvent")
	b.Event:connect(func)
	b:Fire(...)
end

local projector = Instance.new("Model",c)
projector.Name = "ShadowProjector"

local function onChildAdded(child)
	if child:IsA("BasePart") then
		local clone = child:Clone()
		clone.Name = clone.Name
		clone.Anchored = true
		
		for _,v in pairs(clone:GetChildren()) do
			if v:IsA("JointInstance") then
				v:Destroy()
			end
		end
		
		if clone.Transparency < 1 then
			clone.Transparency = 0
		end
		
		clone.Parent = projector
		reg[child] = clone
		
		child.ChildAdded:Connect(function (child2)
			if child2:IsA("SpecialMesh") then
				clone:ClearAllChildren()
				child2:Clone().Parent = clone
			end
		end)
	elseif child:IsA("Accoutrement") then
		spawn(function ()
			local handle = child:WaitForChild("Handle", 1)
			if handle then
				onChildAdded(handle)
			end
		end)
	elseif child:IsA("CharacterMesh") then
		local clone = child:clone()
		clone.Parent = projector
		reg[child] = clone
	end
end

local function update()
	-- Clean up garbage
	for realObj,projectorObj in pairs(reg) do
		if not realObj:IsDescendantOf(activeChar) then
			projectorObj:Destroy()
			reg[realObj] = nil
		end
	end
	
	-- Update Shadow
	local sunPos = lighting:GetSunDirection() * 10000
	
	if sunPos.Y > 0 then -- If the sun is actually out...
		local cf = camera.CFrame
		local focus = camera.Focus
		
		local focusPoint = focus.Position
		local dist = (focus.Position - cf.Position).magnitude
		
		if dist < 1 then -- If we are near first person...
			local facing = sunPos.Unit:Dot(cf.LookVector)
			
			if facing < 0 then -- If we are looking away from the sun...
				local sunDist = (focusPoint - sunPos).magnitude
				local rootPart = activeChar:FindFirstChild("HumanoidRootPart")
				
				if rootPart and reg[rootPart] then
					local projectorRoot = reg[rootPart]
					local objCF = rootPart.CFrame
					
					local objPos = objCF.Position
					local objRot = objCF - objPos
					
					-- Project the HumanoidRootPart towards the sun relative to the camera's position.
					local realDist = (focusPoint - objPos).Magnitude
					local lerpScale = (realDist / sunDist) * 9
					
					local projectPos = focusPoint:Lerp(sunPos, lerpScale)
					local camOffset = objPos - focusPoint
					
					projectorRoot.CFrame = CFrame.new(projectPos + camOffset) * objRot
					
					-- CFrame all shadow limbs relative to the shadow HumanoidRootPart with
					-- an object space relative to their corresponding real offsets.
					
					for realObj, projectorObj in pairs(reg) do
						if realObj ~= rootPart and realObj:IsA("BasePart") then
							local offset = objCF:ToObjectSpace(realObj.CFrame)
							projectorObj.CFrame = projectorRoot.CFrame * offset
							projectorObj:BreakJoints()
						end
					end
					
					-- All conditions were met, render the shadow.
					projector.Parent = camera
					return
				end
			end
		end
	end
	
	-- A condition wasn't met, stop rendering the shadow.
	projector.Parent = nil
end

local function onCharacterAdded(char)
	-- Reset everything.
	if next(reg) ~= nil then
		projector:ClearAllChildren()
		reg = {}
	end
	
	local ph = Instance.new("Humanoid")
	ph.Name = "Projector"
	ph.Parent = projector
	ph:ChangeState("Physics")
	
	activeChar = char
	
	for _,v in pairs(char:GetChildren()) do
		spawn(onChildAdded, v)
	end
	
	char.ChildAdded:connect(onChildAdded)
end

if player.Character then
	onCharacterAdded(player.Character)
end

player.CharacterAdded:Connect(onCharacterAdded)
rs:BindToRenderStep("FirstPersonShadows", 201, update)
lighting.Changed:Connect(update)

put this into starterplayerscript with the name FirstPersonShadows

6 Likes

This is a very nice job! I think I will be using this script, thanks!

Hey, this shadow projector is nice. But whenever I reset, it still stuck on camera and not destroyed.
image