fivem icon indicating copy to clipboard operation
fivem copied to clipboard

feat(extra-natives/five): add light natives

Open Ekinoxx0 opened this issue 6 months ago • 3 comments

Goal of this PR

This PR introduces several new natives to create, delete, and manage all properties of scripted lights. This enables more flexible lighting setups and access to features that were not previously available through the traditional DRAW_LIGHT_... natives, such as texture projection, capsule shapes, and volumetric flags.

h9vY2odmrp ne9Redg3TE

How is this PR achieving the goal

Instead of using a per-frame draw approach, this PR implements a handle-based system to manage lights. With the CREATE_LIGHT and DELETE_LIGHT natives, users can create lights that persist across frames, improving both performance and usability.

Persisting light objects means they are reused every frame, instead of being recreated, using a draw-based native would have required a large number of arguments to expose all the fields of the CLight class.

This PR is a draft because it introduces a significant number of new natives. I’m looking for feedback on the implementation approach and suggestions for improvement before finalizing the design.

Implementation note: The everyFrame hook seems weird at first reading, but I didn't find any better way to implement it. Using fwEvent OnGameFrame doesn't render the lights properly. We need too add lights just after natives finished executing, but before it's cleaned by the game, this hook was the simpliest way I found.

This PR applies to the following area(s)

FiveM Natives

Successfully tested on

Game builds: 1604 to 3258 Platforms: Windows

CreateThread(function()
	for i = 0, 1024 do DeleteLight(i) end -- This could be replaced with a mission clean-up thing ?

	local color = { r = 255, g = 255, b = 0 }
	local intensity = 1.0
	local radius = 1.0

	local light = CreateLight(1, (1<<12), 0.0, 0.0, 0.0, color.r, color.g, color.b, intensity)
	SetLightRadius(light, radius)

	CreateThread(function()
		while true do
			local camPos = GetFinalRenderedCamCoord()
			local endPoint, normal = GetWorldCoordFromScreenCoord(GetDisabledControlNormal(0, 239), GetDisabledControlNormal(0, 240))
			endPoint = endPoint + normal * 10.0

			local rayHandle = StartExpensiveSynchronousShapeTestLosProbe(camPos.x, camPos.y, camPos.z, endPoint.x, endPoint.y, endPoint.z, -1, PlayerPedId(), 0)
			local _, hit, worldPosition = GetShapeTestResultIncludingMaterial(rayHandle)

			if hit then
				SetLightCoords(light, worldPosition.x, worldPosition.y, worldPosition.z)
			end
			Wait(0)
		end
	end)
end)

Checklist

  • [x] Code compiles and has been tested successfully.
  • [x] Code explains itself well and/or is documented.
  • [x] My commit message explains what the changes do and what they are for.
  • [x] No extra compilation warnings are added by these changes.

Fixes issues

Resolve https://forum.cfx.re/t/more-drawblanklight-natives-drawvolumetricspotlight-drawcapsule/5327113

Ekinoxx0 avatar Jun 18 '25 20:06 Ekinoxx0

This looks fantastic! My only thought is about how we can manage effects like bright flashes that only happen for a short time. Of course we could use the current natives, but they miss out on the new and powerful features you've added. Also, using these new natives just for permanent lights feels like it would really restrict what we can do with all these fantastic new features. It would be great to have an easy way to create quick, momentary effects without all the trouble of manually managing the handle. While we can create helper functions for this, so it's just some food for thought on making life easier for developers.

Yum1x avatar Jun 19 '25 18:06 Yum1x

My only thought is about how we can manage effects like bright flashes that only happen for a short time.

this is a specific light parameter called Flashiness, should be possible to add a similar native for it too

ook3D avatar Jun 19 '25 18:06 ook3D

My only thought is about how we can manage effects like bright flashes that only happen for a short time.

this is a specific light parameter called Flashiness, should be possible to add a similar native for it too

alternatively they may mean just doing a quick blip of light but you could just create, wait, delete?

Lucas7yoshi avatar Jun 20 '25 04:06 Lucas7yoshi

Thanks for the PR. That system itself looks exciting as an idea, but I don't really like script handles implementation. I don't think that lights should persist even after script unloaded. To delegate control of light persistence I would suggest to wrap them into fwScriptGuid, but it won't be easy. Also I can see that your PR doesn't include streaming logic, so all the created lights from the whole map will try to render, what is not good because most of people won't even know why is it lagging for them. I also think that IM is better for any light related stuff in our case.

prikolium-cfx avatar Jun 20 '25 13:06 prikolium-cfx

Modified the PR:

  • Removed the handle system (CLightManager class)
  • Removed the CREATE_LIGHT and REMOVE_LIGHT natives
  • Eliminated the need for CLight_ctor the CLight class is now initialized directly
  • Removed the CLight_everyFrame hook, lights are now drawn only when the DRAW_LIGHT native is invoked
  • Added PREPARE_LIGHT, allowing users to easily initialize basic properties (similar to what CREATE_LIGHT did)

Example usage drawn directly:

CreateThread(function()
	while true do
		SetLightCoords(-50.32, 71.73, 72.79)
		DrawLight()
		Wait(0)
	end
end)

Example usage PREPARE_LIGHT:

local color = { r = 255, g = 255, b = 0 }
local intensity = 1.0
local radius = 2.0

CreateThread(function()
	while true do
		local camPos = GetFinalRenderedCamCoord()
		local endPoint, normal = GetWorldCoordFromScreenCoord(GetDisabledControlNormal(0, 239), GetDisabledControlNormal(0, 240))
		endPoint = endPoint + normal * 10.0

		local rayHandle = StartExpensiveSynchronousShapeTestLosProbe(camPos.x, camPos.y, camPos.z, endPoint.x, endPoint.y, endPoint.z, -1, PlayerPedId(), 0)
		local _, hit, worldPosition = GetShapeTestResultIncludingMaterial(rayHandle)

		if hit then
			PrepareLight(1, (1<<1) | (1<<12), worldPosition.x, worldPosition.y, worldPosition.z, color.r, color.g, color.b, intensity)
			SetLightRadius(radius)
			DrawLight()
		end
		Wait(0)
	end
end)

Ekinoxx0 avatar Jun 29 '25 14:06 Ekinoxx0

As someone who's worked with the limited FiveM light environment before, I can confidently say this PR is a solid step forward.

Even in its revised form, the addition of PREPARE_LIGHT and the more flexible DRAW_LIGHT implementation opens up a ton of new use cases that just weren’t possible before—especially with support for features like volumetric lighting, capsule shapes, and texture projection.

While the handle system had potential for persistent lights and optimization, I understand the concerns about persistence after script unload and streaming limits. The updated approach still achieves the main goal: giving developers a modern, more powerful lighting toolkit without overcomplicating implementation or impacting performance unpredictably.

For quick flash effects, as mentioned in earlier comments, I agree that helper functions or a native handling flashiness could make this even more powerful—but that feels like a great next step rather than a blocker for this PR.

This is a meaningful quality-of-life improvement for the community, especially for those of us trying to push the engine's visuals. I strongly support merging this.

fishiidev avatar Jul 09 '25 23:07 fishiidev

Question/concern... Is this limited to one light in a given frame? That might greatly limit its usefullness (atleast for me)... I may be misunderstanding though

Additionally, I have some concern over the performance benefits of this. I feel like the prior iteration where it had persistent lights you could update would be more beneficial. The main performance hit with this example https://forum-cfx-re.akamaized.net/original/5X/8/0/2/f/802f017d665f1afb9b288d0a42e65bf425cad3b1.mp4 is that I had to run a native per light and that native was quite expensive, however to replicate that and say, tweak the light radius in some nice manner it would require setting up a light with multiple natives, times all the lights, every frame...

Lucas7yoshi avatar Jul 13 '25 13:07 Lucas7yoshi

To expand on it, my end game (which is probably more involved than others usages of it) is to have dozens of lights that I would (ideally) only need to run native calls on when there s actual changes to them, while still having the ability to rapidly change them as needed.

If I wanted say, 50 lights, on top of the already sizable GPU performance hit that would entail, I would then need to do 5 or more native calls per light, per frame. thats 250 native calls, and that would be the same static scene or otherwise. The prior system definitely seemed preferable for this scenario and while I will not pretend to be an expert on it, I cant imagine that cleaning up lights on script shutdown would be too hard.

I think this PR has the roots down just fine, and the natives make sense... albeit they make more sense in the prior implementation. If it had to be done per frame it'd be better if it could be done in less native calls or something... I am very glad this PR exists and would either way make use of it, but I think it has larger potential in its prior form with the script-destroy-fix requested by prikolium. <3

Lucas7yoshi avatar Jul 13 '25 14:07 Lucas7yoshi