LensFlare for WebGPU?

Are there any plans to create a version of LensFlare that works with WebGPU?

It appears that the problem is that LensFlare uses WebGL shaders that are not compatible with WebGPU.

In truth, it seems like this is something that might not even really require shaders, since all it does is draw 3 semi-transparent Sprites in a line from the sun to the viewer.

But there is no reason for me to try to create my own version if a WebGPU update is in the works.

If lens flare only for sun, then maybe simple solution will be checking intersection from camera to sun direction with objects.

1 Like

We will port such objects step by step so there will be something like LensFlareNode in upcoming releases.

4 Likes

Here is one idea. The Sprite code is at lines 162-175.
The sprites seem to stay fairly aligned with the LensFlare objects - at least vertically.
I may need to figure out how to make them align better horizontally.
I also need to figure out how to turn them off. I tried comparing the lat/lon of the sun with the camera.rotation. But I think the camera.rotation is in quaternions, not eulers.

Thanks. That’s good to know!

To get a -1 to 1 value for how much the camera is facing your light, you can do something like:

let dir = camera.worldToLocal( sun.position.clone() ).normalize();
let facingAmount = dir.z;

facingAmount should be -1 when fully facing towards, and 1 when facing away. (or maybe the opposite, but you get the idea :wink:

1 Like

For some reason dir.z always remains -0.9999. The dir.x and dir.y values do change, so I used that in my CodePen example. However, as you can see, that doesn’t work because the sprites reappear when facing the opposite direction and the dir.x and dir.y values decrease.

A couple of questions:

  1. As a general matter, when you have a DirectionalLight or a SpotLight should their positions be normalized values? There are some cases where that seems to be true (e.g. where computing reflections). However, that does not appear to work when creating shadows.
  2. Is there a way to convert camera rotation to a euler value? Then I could use lat(x) and lon(y) differences. (The z value would be irrelevant.) I could always create my own camera rotater that uses euler values, but I would prefer to make this compatible with OrbitControls since those are so popular.

The positions are just regular positions, and should be considered as in meters.
One Gotcha with Directional and Spot lights, is that they point at their own .target object, so if you move them, they will also change direction, unless you move the .target by the same amount.

One gotcha with cameras is that tehy don’t need to be added to the scene to work, you can still use them in renderer.render, but if you transform them, and they aren’t in the scene, their matrices won’t update. So sometimes if camera operations don’t seem to be working, its because the camera wasn’t .added to the scene.

1 Like

Okay, using some of the methods I used on other projects, I came up with this solution.

In the beginning section, I added the following variables and commands:

let quaternion = new THREE.Quaternion();
let CamMsh = makMsh();
	CamMsh.rotation.order = "YXZ";
let CamRot = new THREE.Vector2();
let SunOff = new THREE.Vector2();

I created the following subroutine which is called with each frame:

function moveSkyBox() {
	sprite0.visible = true;	
	sprite1.visible = true;
	let off = 0;
	camera.getWorldQuaternion(quaternion);
	CamMsh.setRotationFromQuaternion(quaternion);
	CamRot.x = CamMsh.rotation.x*RadDeg;
	CamRot.y = -CamMsh.rotation.y*RadDeg;
	SunOff.x = SunLLD.x - CamRot.x;
	SunOff.y = PoM360(Mod360(SunLLD.y - CamRot.y));
	SunOff.x = Math.abs(Math.round(SunOff.x));
	SunOff.y = Math.abs(Math.round(SunOff.y));
	let ratio = window.innerWidth / window.innerHeight;
	if (SunOff.x > 45 || SunOff.y > 45*ratio) off = 1;
	if (off) sprite0.visible = false;
	if (off) sprite1.visible = false;
}

At the bottom, I added the following subroutines: makMsh and PoM360.

For some reason, it does not work right on CodePen. But it works fine on GitHub.

Let me know if you have any suggestions for improvement.

Now I just have to use the LensFlare materials - which was causing me some difficulty earlier. Even though I made them transparent and sent an opacity value, they were not acting like transparent textures.

1 Like

blending: THREE.AdditiveBlending :smiley:
depthTest:false,
depthWrite:false

1 Like

Perfect!

We now have a “poor man’s LensFlare” - until the official version comes along.
The test program now uses the latest version of three.js and WebGPU. I should probably convert to using node materials. And the last step would be to convert it into module form.

I have noticed that my programs now sometimes require you to hit (soft) reload to load the skybox. It has a loading manager that is supposed to insure that all textures are loaded before initializing things. Or do I need to throw an async in there someplace?

1 Like

Not sure how your skybox stuff is set up… but yeah couldn’t hurt to

texture = await loader.loadAsync( url );
instead of using .load
to make it synchronous.

1 Like

You are looking for hidding sun then it is not visible at screen? Try this

let clipPosition=new THREE.Vector4(0.2992532451515344,0.9396926207859088,0.16560577799939802,0.0);
clipPosition.applyMatrix4(camera.matrixWorldInverse).applyMatrix4(camera.projectionMatrix);
clipPosition.x=(clipPosition.x/clipPosition.w+1)/2;
clipPosition.y=(clipPosition.y/clipPosition.w+1)/2;
let isVisible = clipPosition.z > 0;
1 Like

Thanks.
That will be helpful if I add something that is right at the light source - like the big circle in the three.js example. I found that one a bit annoying because it seemed to disappear too soon. At a minimum, it should not disappear before the light does and possibly even later.

Add timer for trigger with decreasing and increasing lens opacity like into games.
Example: was invisible, opacity 0, became visible, then disable checking visibility and start increase opacity to 1 by delta time. Then enable checking visibility.

1 Like

I am using a “loading manager”, which (I think) shouldn’t allow the program to proceed to initialization until everything is loaded:

//- Loading Manager
	// Create a loading manager to set RESOURCES_LOADED when appropriate.
	// Pass loadingManager to all resource loaders.
let loadingManager = new THREE.LoadingManager();
let RESOURCES_LOADED = false;
	loadingManager.onLoad = function(){
		console.log("loaded all resources");
		RESOURCES_LOADED = true;
		initAll();
	};
let txtrLoader = new THREE.TextureLoader(loadingManager);
let imagLoader = new THREE.ImageLoader(loadingManager);
let cubeLoader = new THREE.CubeTextureLoader(loadingManager); // new

But I could be wrong.

Consistent with the above, I now load the SkyBox, with CubeTextureLoader, like this:

let envMap = cubeLoader
	.setPath(SBPath)
	.load(["px.jpg", "nx.jpg", "py.jpg", "ny.jpg", "pz.jpg", "nz.jpg"]);

But this will accept neither await before the cubeLoader (reserved word error) or loadAsync.

I had added an async before the entire loading function, but that did help.

ALSO (and this may be beyond what you want to discuss) …

On my larger programs, I set up a separate loading screen which displays until everything is loaded. The render function includes this:

if(RESOURCES_LOADED == false){
	renderer.render(loadingScreen.scene, loadingScreen.camera);
	return;							// Stop the function here.
}

and the initialization routine sets a LodFlg when it is done, so that I do not allow the various subroutines called by render function routines to run unless that flag is set.

That may all be a bit kludgy, but it seems to work. It seems to force everything to be loaded and initialized before things display and the render subroutines starts working. If there a better overall approach, I would appreciate your thoughts.

I just have the feeling that the better approach is to use await and async to handle these tasks.

I think that’s actually fine. Using await/async can block the js thread… so if you had a separate “loading” scene that was animating, it would get stalled by an

await loader.loadAsync()

that said, sometimes I’ll use:

let thing = await loader.loadAsync(url);
let thing1 = await loader.loadAsync(url);
let thing2 = await loader.loadAsync(url);
just because it’s easier to reason about and happens in a predictable order.
This approach will stall the js thread though while it’s await’ing, so in that case I use an animated gif (runs on the UI thread) as a loading indicator.

I don’t know that there is explicitly a “right” way to do it… for instance loading things dynamically at runtime you pretty much can’t use the .loadAsync or your animation would stall until the load is done.

1 Like

Thanks! Yes, I have an animated indicator (a spinning prop) for my loading indicator. :grinning:

Now that I am testing the LensFlare, I realize that when I scroll in and out, the position of the reflections get closer and farther away. The three.js version of LensFlare does that too.

Since I am creating LensFlare of the sun, which is a long ways away, I would assume that they should not be changing position - since the dots are an artifact of the lens.

After several unsuccessful attempts to adjust their location in real space, it strikes me that they really should be attached to the camera. So it’s back to the drawing boards!

Attached to ortho camera.