Jump to content
The Dark Mod Forums

Light frustums different in DR and TDM


Recommended Posts

Not sure if it's a bug in DR or (most likely) TDM but I've been hit with this issue where a light ends up having different frustum vertices in TDM than it was set up in DR

Map attached. Look at light_1

Its "far" corners seems to have the X coordianate of -125

image.png.2fc8106c1ea12e18c8375a4cb2359277.png

When the light loads in TDM, the frustum corners can be rebuilt using different methods:

  • R_PolytopeSurface gives -147 image.png.2ded46e03199ef390cde4192230e6fac.png
  • idRenderMatrix::GetFrustumCorners gives -213 image.png.b1d90837f807ed4fbbd59350af081e99.png

This really messes up my volumetric shader

Which one is supposed to be correct? idRenderMatrix::GetFrustumCorners is much farther from DR but R_PolytopeSurface's output is not even air-tight @stgatilov?

@greebo Where can I find the code that calculates frustum vertices in DR? Maybe I should just copy-paste it in TDM and relax.

volumetric.map duzenko.mtr

Link to comment
Share on other sites

I'd lean towards the view that the TDM code is the leading one, DR has to follow where it can.

It has been years since I've touched the light projection code, but it's all in radiantcore/entity/light/Light.cpp, look at Light::updateProjection(). Not sure if this is related to the phenomenon at hand.

Link to comment
Share on other sites

27 minutes ago, greebo said:

I'd lean towards the view that the TDM code is the leading one, DR has to follow where it can.

It has been years since I've touched the light projection code, but it's all in radiantcore/entity/light/Light.cpp, look at Light::updateProjection(). Not sure if this is related to the phenomenon at hand.

It does not produce the actual corner coordinates though, does it?

Link to comment
Share on other sites

It's using the same frustum, if I'm not mistaken. In radiantcore/entity/light/Renderables.cpp => RenderLightProjection::render() you can see it referencing the same Frustum object to calculate the points from the frustum planes.

  • Thanks 1
Link to comment
Share on other sites

I can see that DR does it corners in local space using plane intersections

While TDM relies mostly on projection matrices

But already the initial setup in R_DeriveLightData is wrong when it comes to building frustum planes

Link to comment
Share on other sites

Can anyone please explain what the start, target, end, up, right points for lights are defining?

I mean, I can understand what they're defining, but how they're supposed to map to the projection matrix?

Compare, for example

	const float targetDistSqr = light->parms.target.LengthSqr();
	const float invTargetDist = idMath::InvSqrt(targetDistSqr);
	const float targetDist = invTargetDist * targetDistSqr;

	const idVec3 normalizedRight = light->parms.right * (0.5f * targetDist / light->parms.right.LengthSqr());

	localProject[0][0] = normalizedRight[0];
	localProject[0][1] = normalizedRight[1];
	localProject[0][2] = normalizedRight[2];
	localProject[0][3] = 0.0f;

and

    auto rLen = _lightRightTransformed.getLength();
    Vector3 right = _lightRightTransformed / rLen;
    Vector3 normal = up.cross(right).getNormalised();

    auto dist = _lightTargetTransformed.dot(normal);
    if ( dist < 0 ) {
        dist = -dist;
        normal = -normal;
    }

    right *= ( 0.5 * dist ) / rLen;

    lightProject[0] = Plane3(right, 0);

This already gives different results

Link to comment
Share on other sites

@stgatilov apparently the BFG code we switched to last year produces a different result compared to the old code

It's R_SetLightProject vs R_ComputeSpotLightProjectionMatrix

The old R_SetLightProject was inline with DR

I'm not sure about why they changed it, maybe it makes more sense, but we need to maintain compatibility so revert that?

Link to comment
Share on other sites

4 hours ago, duzenko said:

@stgatilov apparently the BFG code we switched to last year produces a different result compared to the old code

It's R_SetLightProject vs R_ComputeSpotLightProjectionMatrix

The old R_SetLightProject was inline with DR

I'm not sure about why they changed it, maybe it makes more sense, but we need to maintain compatibility so revert that?

Yes, I suppose using the code from original Doom 3 is a better option for us, in case they are different.

Link to comment
Share on other sites

After some more investigation it seems that BFG projection matrix is different because it gives different transformation

  • projection divider goes into W instead of Z
  • falloff texture coordinate is not normalized

image.thumb.png.9cef764b29a632f6da8a0443e7c75df2.png

This is produced by debug code in the end of R_DeriveLightData

	auto test = [light](idVec3 vert, int i) {
		idVec4 v4;
		light->baseLightProject.TransformPoint( vert, v4 );
		auto& M2 = *(idMat4*) light->lightProject;
		idVec4 tmp( 0, 0, 0, 1 );
		tmp.ToVec3() = vert;
		auto x = M2 * tmp;
		common->Printf( "%2d%30s%30s\n  ", i, v4.ToString(), x.ToString() );
		/*for ( int j = 0; j < 6; j++ ) {
			auto d = light->frustum[j].Distance( vert );
			common->Printf( "%.1e ", d );
		}
		common->Printf( "\n" );*/
	};
	for ( int i = 0; i < light->frustumTris->numIndexes; i++ ) {
		auto index = light->frustumTris->indexes[i];
		auto vert = light->frustumTris->verts[index];
		test( vert.xyz, i );
	}
	ALIGNTYPE16 frustumCorners_t corners;
	idRenderMatrix::GetFrustumCorners( corners, light->inverseBaseLightProject, bounds_zeroOneCube );
	for ( int i = 0; i < 8; i++ ) {
		test( idVec3( corners.x[i], corners.y[i], corners.z[i] ), i );
	}

Strangely here both frustumTris and frustumCorners_t map to texture space corners...

  • Like 1
Link to comment
Share on other sites

Be aware that the light projection texture calculation in DR is horribly broken, in particular if you "shear" the light by sliding the light_target point parallel to the target plane, the texture projection does not update at all and the light preview no longer matches the rendered frustum.

If any of our maths wizards want to dive into the code and fix this, it would certainly be welcomed.

Link to comment
Share on other sites

I can make renderer work a priority after the next release. Initially I wanted to work on that back in April (already sketched out a few issues for myself), but then got completely side-tracked by the VCS, Merging and Texture Tool projects.

Overall the DR renderer is not a priority for both DR developers and mappers, with authors usually relying on the engine drawing things correctly. That's why we could get away with that DR renderer we have for such a long time in the first place. But on the other hand I still think that it'd be worthwile for DR to come up with a better renderer, even something that supports shadow calculations.

  • Like 2
Link to comment
Share on other sites

On 10/11/2021 at 2:53 AM, duzenko said:

After some more investigation it seems that BFG projection matrix is different because it gives different transformation

  • projection divider goes into W instead of Z
  • falloff texture coordinate is not normalized

image.thumb.png.9cef764b29a632f6da8a0443e7c75df2.png

I think you exactly described the difference.

Looking at D3BFG source, we see:

Quote

old style light projection where Z and W are flipped and
projected lights lightProject[3] is divided by ( zNear + zFar )


The idea of swapping Z and W is merely a convention. Everyone in computer graphics uses W as divisor, so using Z instead is going to confuse people every time they have to deal with it. Of course, both ways work as long as every piece of code (including shaders) where it is used knows which coordinate to divide by. Given that D3BFG added a bunch of matrix-related code in SIMD, I suppose they had issues with maintaining the old convention.
 

As for scaling of the last column, I think it is also merely a convention. The only difference would be scaled Z value (in standard terms, i.e. Z is non-divisor coordinate) after transformation. It means displacement along light rays, and I'm not sure anything in the original Doom 3 depended on it.

Once again: you can work equally well with any of these conventions, as long as you understand what Z (non-divisor) means when you have to use it. Moreover, you can always pass (far+near) coeff as separate uniform into shader to get the same behavior regardless of what is written in the matrix, although that would look stupid.

I'd say the better convention is where Z coordinate (non-divisor) is always in same [0..1] range regardless of the far/near clipping distances. Somehow, it seems to be D3BFG matrix, but better check it.

Quote

Strangely here both frustumTris and frustumCorners_t map to texture space corners...

Most likely because the code which build corners in each case knows the conventions it works with


So, the bottom line is: both matrices are the same thing as long as you apply them according to the conventions they have in mind. D3BFG conventions seem to be more standard and convenient.

Link to comment
Share on other sites

@stgatilovThanks for looking into this

I think I got sidetracked and distracted you from the real problem

Yes, the thing I wrote about in the last post is a non-issue. Since the baseLightProject is always based on the lightProject, they'll both produce the same result

My problem is the code that builds lightProject. R_SetLightProject vs R_ComputeSpotLightProjectionMatrix will give different matrix sets and different frustums

I'm going to try another thing now. Transform local start/end coordinates (in the spawn args) to global space and see where their "projections" are

EDIT. Also note, that locally I reverted to

	return 1.0f / ( zNear + zFar );

in the end of R_ComputeSpotLightProjectionMatrix but did not commit to svn as I'm still investigation this

Link to comment
Share on other sites

Added the test code for that (placed in the end of R_DeriveLightData)

Spoiler
	auto test = [light](idVec3 vert, int i) {
		idVec4 v4;
		light->baseLightProject.TransformPoint( vert, v4 );
		auto& M2 = *(idMat4*) light->lightProject;
		idVec4 tmp( 0, 0, 0, 1 );
		tmp.ToVec3() = vert;
		auto x = M2 * tmp;
		common->Printf( "%2d%30s%30s\n", i, v4.ToString(), x.ToString() );
		v4 /= v4.w;
		x.ToVec3() /= x.z;
		common->Printf( "  %30s%30s\n", v4.ToString(), x.ToString() );
	};
	for ( int i = 0; i < light->frustumTris->numIndexes; i++ ) {
		auto index = light->frustumTris->indexes[i];
		auto vert = light->frustumTris->verts[index];
		test( vert.xyz, i );
	}
	ALIGNTYPE16 frustumCorners_t corners;
	idRenderMatrix::GetFrustumCorners( corners, light->inverseBaseLightProject, bounds_zeroOneCube );
	for ( int i = 0; i < 8; i++ ) {
		test( idVec3( corners.x[i], corners.y[i], corners.z[i] ), i );
	}
	idVec4 startInGlobal;
	lightMatrix.TransformPoint( light->parms.start, startInGlobal );
	common->Printf( "start %s >> %s\n", light->parms.start.ToString(), startInGlobal.ToString() );
	test( startInGlobal.ToVec3(), 0 );
	idVec4 endInGlobal;
	lightMatrix.TransformPoint( light->parms.end, endInGlobal );
	common->Printf( "end   %s >> %s\n", light->parms.end.ToString(), endInGlobal.ToString() );
	test( endInGlobal.ToVec3(), 1 );
	
	idPlane d3Project[4];
	R_SetLightProject( d3Project, vec3_origin, light->parms.target, light->parms.right, light->parms.up, light->parms.start, light->parms.end );
	auto M1 = *(idMat4*) d3Project;
	auto to4 = []( idVec3 v ) { return idVec4( v.x, v.y, v.z, 1 ); };
	auto st1 = M1 * to4( light->parms.start );
	auto en1 = M1 * to4( light->parms.end );
	common->Printf( "d3 start %s >> %s\n", light->parms.start.ToString(), st1.ToString() );
	st1.ToVec3() /= st1.z;
	common->Printf( "         >> %s\n", st1.ToString() );
	common->Printf( "d3 end   %s >> %s\n", light->parms.end.ToString(), en1.ToString() );
	en1.ToVec3() /= en1.z;
	common->Printf( "         >> %s\n", en1.ToString() );

 

Results

image.thumb.png.708a1833cbe74120390c552be9ece9bc.png

Unless I'm not mistaking it means that the BFG matrix does not map the end pos from the spawn args to light frustum "far" plane, unlike the D3 code

At this point we should decide if it was a bug or a feature and what their intention was when they changed the matrix.

Link to comment
Share on other sites

3 hours ago, stgatilov said:

The idea of swapping Z and W is merely a convention. Everyone in computer graphics uses W as divisor, so using Z instead is going to confuse people every time they have to deal with it. Of course, both ways work as long as every piece of code (including shaders) where it is used knows which coordinate to divide by. Given that D3BFG added a bunch of matrix-related code in SIMD, I suppose they had issues with maintaining the old convention.

I was confused when I was looking at this code (to see if I could integrate it into DR to fix projected light rendering). I wasn't sure why there were "old" and "new" conventions, but if some of this code came from BFG and some from Doom 3, this would explain the distinction.

Our shaders use W for the divider as per normal expectations, and use projective texture lookups for S and T (i.e. S/W and T/W are what gets rendered) but not for Z, which is undivided. As you say it would be possible to put the divider in Z if the shader was updated but I can't really see the point of it.

3 hours ago, stgatilov said:

As for scaling of the last column, I think it is also merely a convention. The only difference would be scaled Z value (in standard terms, i.e. Z is non-divisor coordinate) after transformation. It means displacement along light rays, and I'm not sure anything in the original Doom 3 depended on it.

In DR the Z coordinate is used for the falloff texture, so the light will become dimmer as you move further from the origin. Is this not what the game does too? Do D3 projected lights have the same brightness along the whole target vector, or do they dim based on distance?

3 hours ago, stgatilov said:

I'd say the better convention is where Z coordinate (non-divisor) is always in same [0..1] range regardless of the far/near clipping distances. Somehow, it seems to be D3BFG matrix, but better check it.

Our (DR) Z goes from 0 to 1, but this is actually wrong: the falloff texture is symmetric about the 0.5 position, so our lights are actually dim near the origin, brightest halfway along the target vector, and then dim to black. I'm trying to fix this using my limited math skills so that Z goes from 0.5 to 1, resulting in maximum brightness near the "light bulb" which dims to 0 as you reach the end of the target vector. But if the game doesn't do this, DR shouldn't either.

Link to comment
Share on other sites

25 minutes ago, OrbWeaver said:

I was confused when I was looking at this code (to see if I could integrate it into DR to fix projected light rendering). I wasn't sure why there were "old" and "new" conventions, but if some of this code came from BFG and some from Doom 3, this would explain the distinction.

Our shaders use W for the divider as per normal expectations, and use projective texture lookups for S and T (i.e. S/W and T/W are what gets rendered) but not for Z, which is undivided. As you say it would be possible to put the divider in Z if the shader was updated but I can't really see the point of it.

Forget about shaders, this is not about that

Quote

In DR the Z coordinate is used for the falloff texture, so the light will become dimmer as you move further from the origin. Is this not what the game does too? Do D3 projected lights have the same brightness along the whole target vector, or do they dim based on distance?

No, the game, same as DR, uses the falloff texture. The texture decides where the light is brighter or dimmer. There's no shader math for that.

Quote

Our (DR) Z goes from 0 to 1, but this is actually wrong: the falloff texture is symmetric about the 0.5 position, so our lights are actually dim near the origin, brightest halfway along the target vector, and then dim to black. I'm trying to fix this using my limited math skills so that Z goes from 0.5 to 1, resulting in maximum brightness near the "light bulb" which dims to 0 as you reach the end of the target vector. But if the game doesn't do this, DR shouldn't either.

The texture does not have to be symmetric, if a particular texture is symmetric, that still does not mean anything. There's no really need or sense to change anything in that regard.

Link to comment
Share on other sites

Could you maybe shed some light why target, right, up aren't orthogonal?

One difference between d3 and bfg is that BFG uses the target vector from spawnargs for Z basis vector and D3 computes a normal between up and right instead.

image.png.eb570f775abf97bdb7ae007c2e7eedbf.png

Link to comment
Share on other sites

2 hours ago, duzenko said:

The texture does not have to be symmetric, if a particular texture is symmetric, that still does not mean anything. There's no really need or sense to change anything in that regard.

It doesn't have to be symmetric, but all of the shaders I've seen so far are symmetric in their Z falloff, including the commonly-used ones like biground1. If the projected Z coordinate goes from 0 to 1 then these textures will give surprising results when applied to projected lights, which (I assume) most users will expect to be brightest near the light origin, not halfway along the target vector.

But of course DR should mimic what the game does, so if the game uses the full 0-1 range of the Z falloff texture then DR should as well, even if it doesn't really make sense for most light shaders (and changing this in game would be a bad idea because it would affect the lighting in existing maps).

2 hours ago, duzenko said:

Could you maybe shed some light why target, right, up aren't orthogonal?

I can't speak to the original game design, but in terms of DR, there is no restriction on where you can drag the right and up points, so it's quite possible to set up a projected light with non-orthogonal vectors (the frustum in this case becomes a sort of weird rhomboid shape). I guess the game is just taking this possibility into account by not assuming that the vectors are orthogonal when calculating the matrix.

Link to comment
Share on other sites

1 hour ago, OrbWeaver said:

I can't speak to the original game design, but in terms of DR, there is no restriction on where you can drag the right and up points, so it's quite possible to set up a projected light with non-orthogonal vectors (the frustum in this case becomes a sort of weird rhomboid shape). I guess the game is just taking this possibility into account by not assuming that the vectors are orthogonal when calculating the matrix.

In this case logically the resulting frustum center point is not what DR shows

I would suggest to fix this in DR then

Link to comment
Share on other sites

17 hours ago, duzenko said:

Could you maybe shed some light why target, right, up aren't orthogonal?

One difference between d3 and bfg is that BFG uses the target vector from spawnargs for Z basis vector and D3 computes a normal between up and right instead.

image.png.eb570f775abf97bdb7ae007c2e7eedbf.png

Yeah, it is natural to assume (target, up, right) is orthogonal basis, like axes of coordinate system.
Somehow, it reminds me the story of rotation hack: everyone assumed orthogonality but nobody restricted it.

I wonder what would happen if we orthogonalize (target, up, right) spawnargs with Gram-Schmidt in game engine when parsing light spawnargs. How much stuff would stop working?

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share


  • Recent Status Updates

    • irg

      Watching warmly for The Black Parade, The Broken Goddess and Blood Death Wish Ep.4. Sometimes the best things in life actually are free.
      · 0 replies
    • STiFU

      We are taking our son on his very first holiday trip to see the sea for the first time. 🙂 Will be back in a week.
      · 2 replies
    • Gilkar

      When I was a young man my father was so ignorant I could hardly stand to have him around. As I grew older I was amazed at how much the old man had learned in such a short time.
      · 2 replies
    • jaxa

      RTX 3090 Super, RTX 3070 Ti 16 GB, RTX 2060 12 GB
      https://wccftech.com/nvidia-launching-rtx-3090-super-rtx-3070-ti-16gb-and-rtx-2060-12gb-by-january-2022/
      · 0 replies
    • duzenko

      CPU benchmark time - compiling DarkRadiant (2nd run)
      i5 8600K 6C/6T@4.4GHz DDR4 2x2133MHz 9MB cache
      Parallel builds: 1. 3:57 Parallel builds: 6 (default). 2:28 r5 1600AF 6C/12T@3.3GHz DDR4 1x2666MHz 16 MB cache, temp folder on HDD
      Parallel builds: 1. 5:05 Parallel builds: 4. 2:47 Parallel builds: 6. 2:55 Parallel builds: 12 (default). 2:57
      · 6 replies
×
×
  • Create New...