How would I go on making an "Auto Exposure" system in an horror game?

  1. What do you want to achieve? I want to achieve an “Auto Exposure” system in my game, which means increasing exposure when a player is in a dark region and decreasing when in an illuminated place, so it simulates the eye’s comfort/adjustment to light. Because it is a backrooms horror game, I have several lights hanging on the ceiling, with different brightness and range.

  2. What is the issue? The problem is that I can’t find an efficient way to capture this eye adjustment. I tried getting all lights within a range of 200 studs of the player, getting their average brightness between them, get the mouse position and player position, find the distance from light, and do some count; however, ends up the results are kinda disappointing and is not even close to what I want. The script solely makes the exposure fluctuate values between X and Y, but it does not create that feeling of adjustment to light.

  3. What solutions have you tried so far? As I said before, I already tried creating a small system that gets the global lights average, but it’s complicated and certainly not the most efficient and proper way to do this, as I am looping through all lights consecutively to try to find the next light, which is bad. I already tried searching for community resources here, but all I could find was this post, using Capture Service to reach this. But I’ve just learned that Capture Service is not recommended for that, although using it would be the best way, as I could get the pixel brightness and information. Using Raycast could work, but I didn’t find a way for it, and it’s possibly not performant either, as I would need to create raycasts from all directions and with a determined length, consequently lagging up the game.

For more information, this is my current script which attempts to simulate this effect, although failing.

Please note that the script is adapted, so I could send it here. The “Auto Exposure” system is enfolded inside a camera module, which encapsulates other types of functions, which are not correlated with this matter.

type influences = {Camera : number, Player : number}
export type light = BasePart & {Light : Light, Connector : (Attachment & {Light : light})?}

local camera = {}

local plrs = game:GetService("Players")
local RpS = game:GetService("ReplicatedStorage")
local lighting = game:GetService("Lighting")

local plr = plrs.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()

local head = char:WaitForChild("Head")
local hum = char:WaitForChild("Humanoid")

local map = workspace.Map
local lightFolder = map:WaitForChild("Lights")

local mainBrightnessWeight = 2
local secondaryBrightnessWeight = 1
local minLightDistance = 0
local maxLightDistance = 100

local lastAverage = 0
local adaptationAmount = 0
local adaptationSpeed = .01
local maxAdaptation = .15

local lights : {[string] : number} = {}
local lightSensitivities : {[string] : number} = {}

local function getCameraAngleToLight(camCFrame : CFrame, lightPosition : Vector3) : number
	local directionToLight = (lightPosition - camCFrame.Position).Unit

	local alignment = camCFrame.LookVector:Dot(directionToLight)

	return math.clamp(alignment, 0, 1)
end

local function getPlayerInfluence(distance : number) : number
	local influence = 1 - math.clamp(distance / maxLightDistance, 0, 1)

	return influence
end

local function findLightsBrightnessAverage(Lights : {Light}) : number	
	local totalBrightness

	if #Lights == 1 then
		totalBrightness = Lights[1].Brightness
	else		
		local mainBrightness = Lights[1].Brightness * mainBrightnessWeight
		local secondaryBrightness = Lights[2].Brightness * secondaryBrightnessWeight

		totalBrightness = (mainBrightness + secondaryBrightness) / (mainBrightnessWeight + secondaryBrightnessWeight)
	end

	return totalBrightness
end

local function findLightBrightnessWithInfluence(Influences : influences, Brightness : number) : number
	local cameraInfluence, playerInfluence = Influences.Camera, Influences.Player

	local totalBrightness = Brightness + (playerInfluence * math.pow(cameraInfluence/1.5, .7))

	return totalBrightness
end

local function findLightEmitters(lightPart : light) : {any?}
	local mainSource = lightPart.Light
	local secondary : Light?

	local connector = lightPart:FindFirstChild("Connector")

	if connector then
		secondary = connector.Light
	end

	return {mainSource, secondary}
end

local function returnLightInfluencesIfCloseTo(headPosition : Vector3, lightPosition : Vector3, camCFrame : CFrame) : influences?
	local distanceFromPlayer = (headPosition - lightPosition).Magnitude

	local influences

	if distanceFromPlayer >= minLightDistance and distanceFromPlayer <= maxLightDistance then

		influences = {
			Camera = getCameraAngleToLight(camCFrame, lightPosition),
			Player = getPlayerInfluence(distanceFromPlayer)
		}
	end

	return influences
end

local function updateLightBrightnessTable(lightPart : light) : ()
	local identifier = lightPart.Name -- Each light has a different name

	local lightEmitters = findLightEmitters(lightPart)

	if not lightEmitters then return end

	local lightnessAverage = findLightsBrightnessAverage(lightEmitters)

	for _, lightEmitter : PointLight in lightEmitters do
		lightEmitter.Changed:Connect(function(property)
			if property == "Brightness" then
				local lightnessAverage = findLightsBrightnessAverage(lightEmitters)

				lights[identifier] = lightnessAverage
			end
		end)
	end

	lights[identifier] = lightnessAverage
end

local function updateLightSensitivityTable(lightName : string, calculatedLightBrightness : number, camCFrame : CFrame, headPosition : Vector3) : ()
	local currentLight : light = lightFolder[lightName]

	local lightPosition = currentLight.Position

	local influences = returnLightInfluencesIfCloseTo(headPosition, lightPosition, camCFrame)

	if influences then
		local newLightbrightnessAverage = findLightBrightnessWithInfluence(influences, calculatedLightBrightness)

		lightSensitivities[lightName] = newLightbrightnessAverage
	else
		lightSensitivities[lightName] = nil
	end
end

function camera.AdaptToAmbientLight()
	local totalSensitivityInEnvironment = 0
	local totalLights = 0
	local average

	local camCFrame = cam.CFrame
	local headPosition = head.Position

	for lightName, calculatedLightBrightness in lights do
		updateLightSensitivityTable(lightName, calculatedLightBrightness, camCFrame, headPosition)
	end

	for _, lightSensitivity in lightSensitivities do
		totalSensitivityInEnvironment += lightSensitivity
		totalLights += 1
	end

	average = totalSensitivityInEnvironment/totalLights

	local deltaSensitivity = average - lastAverage

	local targetAdaptation = math.clamp(-deltaSensitivity * .5, -maxAdaptation, maxAdaptation)

	adaptationAmount += (targetAdaptation - adaptationAmount) * adaptationSpeed

	lighting.ExposureCompensation = adaptationAmount

	return adaptationAmount
end

for _, lightPart in lightParts do
	updateLightBrightnessTable(lightPart)
end

lightFolder.ChildAdded:Connect(updateLightBrightnessTable)

return camera

Please, I need help with this. I’ve been working on this for a while, and it’s frustrating to see that I didn’t reach what I wanted. Any help is highly appreciated. If there’s a way to make it using Capture Service, tell me. If not, teach me how I could achieve this. Thanks in advance and have a great night/day! :smiley:

You could try checking what the closest 2-4 light sources are and updating the closest 2-4 light sources every 2 seconds, raycast towards them and if the player can see them, get the average brightness and color, then using a ColorCorrection Instance

1 Like

Thanks for the answer. I’m using a system I found in pastebin that works well :slight_smile:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.