From c9a3d0430cd8ee167cc1919a3f27c72126bc746a Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Tue, 27 Jul 2021 14:03:22 +0200 Subject: [PATCH] First implementation of dynamically updated lightmap textures. These are uploaded from the Quake engine to Unity on map load, and whenever a lightmap texture changes. It has been tested and found (mostly) working by manipulating the UVs on surface meshes, applying the lightmap as main texture, and using an unlit shader. This is not part of the commit. --- Assets/Scripts/Game/GameAssets.cs | 41 +++++++++++++++++++ Assets/Scripts/Game/GameState.cs | 3 +- .../Scripts/Modules/RenderModule.Interop.cs | 14 +++++++ Assets/Scripts/Modules/RenderModule.cs | 9 ++++ engine/Quake/gl_texmgr.h | 1 + engine/Quake/r_brush.c | 4 ++ engine/UniQuake/gl_uniquake.c | 6 +++ 7 files changed, 77 insertions(+), 1 deletion(-) diff --git a/Assets/Scripts/Game/GameAssets.cs b/Assets/Scripts/Game/GameAssets.cs index d43523e..c82e983 100644 --- a/Assets/Scripts/Game/GameAssets.cs +++ b/Assets/Scripts/Game/GameAssets.cs @@ -7,6 +7,7 @@ public class GameAssets private readonly UniQuake uq; private readonly Dictionary textures = new Dictionary(); + private readonly Dictionary lightmaps = new Dictionary(); private BrushModel worldModel; private readonly List brushModels = new List(); @@ -46,6 +47,39 @@ public class GameAssets return texNum > 0 && textures.TryGetValue(texNum, out texture); } + public bool TryGetTexture(string texName, out Texture2D texture) + { + foreach (var tex in textures.Values) + { + if (tex.name == texName) + { + texture = tex; + return true; + } + } + + texture = null; + return false; + } + + public void UploadLightmap(int lightmapNum, int width, int height, byte[] data) + { + if (!lightmaps.TryGetValue(lightmapNum, out var lightmap)) + { + lightmap = new Texture2D(width, height, TextureFormat.RGBA32, true) { name = $"Lightmap_{lightmapNum}" }; + lightmaps.Add(lightmapNum, lightmap); + } + + lightmap.SetPixelData(data, 0); + lightmap.Apply(); + } + + public bool TryGetLightmap(int lightmapNum, out Texture2D lightmap) + { + lightmap = null; + return lightmapNum >= 0 && lightmaps.TryGetValue(lightmapNum, out lightmap); + } + public void AddAliasModel(AliasModel aliasModel) { aliasModels.Add(aliasModel); @@ -126,6 +160,13 @@ public class GameAssets aliasModels.Clear(); + foreach (var lightmap in lightmaps.Values) + { + Object.Destroy(lightmap); + } + + lightmaps.Clear(); + foreach (var texture in textures.Values) { Object.Destroy(texture); diff --git a/Assets/Scripts/Game/GameState.cs b/Assets/Scripts/Game/GameState.cs index 1795981..64225e4 100644 --- a/Assets/Scripts/Game/GameState.cs +++ b/Assets/Scripts/Game/GameState.cs @@ -57,8 +57,9 @@ public class GameState { uint fbNum = surfaceMesh.FullBrightNum; uq.GameAssets.TryGetTexture(fbNum, out var fullBright); + uq.GameAssets.TryGetLightmap(surfaceMesh.Lightmap, out var lightmap); - uq.CurrentStyle.SetWorldTextures(mr.material, texture, fullBright, null); + uq.CurrentStyle.SetWorldTextures(mr.material, texture, fullBright, lightmap); } } diff --git a/Assets/Scripts/Modules/RenderModule.Interop.cs b/Assets/Scripts/Modules/RenderModule.Interop.cs index 2af570a..cc5b9ca 100644 --- a/Assets/Scripts/Modules/RenderModule.Interop.cs +++ b/Assets/Scripts/Modules/RenderModule.Interop.cs @@ -18,6 +18,7 @@ public partial class RenderModule: CallbackHandler UploadWorldModel = CreateCallback(Callback_UploadWorldModel), UploadTexture = CreateCallback(Callback_UploadTexture), + UploadLightmap = CreateCallback(Callback_UploadLightmap), SetupView = CreateCallback(Callback_SetupView), }; @@ -36,6 +37,7 @@ public partial class RenderModule: CallbackHandler public IntPtr UploadBrushModel; public IntPtr UploadWorldModel; public IntPtr UploadTexture; + public IntPtr UploadLightmap; public IntPtr SetupView; } @@ -123,6 +125,18 @@ public partial class RenderModule: CallbackHandler return GetSelf(target).UploadTexture(texture, dataBytes, ref texNum); } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool UploadLightmapCallback(IntPtr target, int lmap, int width, int height, IntPtr data); + + [MonoPInvokeCallback(typeof(UploadLightmapCallback))] + private static bool Callback_UploadLightmap(IntPtr target, int lmap, int width, int height, IntPtr data) + { + // TODO: this is a fairly pointless additional data copy step; we could probably make this faster by wrapping the IntPtr directly into a NativeArray + byte[] dataBytes = new byte[width * height * 4]; // 32 bits per pixel (RGBA) + Marshal.Copy(data, dataBytes, 0, dataBytes.Length); + return GetSelf(target).UploadLightmap(lmap, width, height, dataBytes); + } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetupViewCallback(IntPtr target, ref QVec3 origin, ref QVec3 angles, ref QLeaf viewLeaf); diff --git a/Assets/Scripts/Modules/RenderModule.cs b/Assets/Scripts/Modules/RenderModule.cs index da350b9..bf75e35 100644 --- a/Assets/Scripts/Modules/RenderModule.cs +++ b/Assets/Scripts/Modules/RenderModule.cs @@ -67,6 +67,15 @@ public partial class RenderModule texNum = uq.GameAssets.SetTexture(texNum, tex); return true; } + + private bool UploadLightmap(int lightmapNum, int width, int height, byte[] data) + { + if (width == 0 || height == 0) + return false; + + uq.GameAssets.UploadLightmap(lightmapNum, width, height, data); + return true; + } private void SetupView(QVec3 origin, QVec3 angles, QLeaf viewLeaf) { diff --git a/engine/Quake/gl_texmgr.h b/engine/Quake/gl_texmgr.h index b39b09c..d982b3e 100644 --- a/engine/Quake/gl_texmgr.h +++ b/engine/Quake/gl_texmgr.h @@ -107,6 +107,7 @@ void GL_Bind (gltexture_t *texture); void GL_ClearBindings (void); extern qboolean UQ_GL_UploadTexture(gltexture_t *texture, unsigned *data); +extern qboolean UQ_GL_UploadLightmap(int lmap, int width, int height, byte *data); #endif /* _GL_TEXMAN_H */ diff --git a/engine/Quake/r_brush.c b/engine/Quake/r_brush.c index 796ae4c..2ca839d 100644 --- a/engine/Quake/r_brush.c +++ b/engine/Quake/r_brush.c @@ -940,6 +940,8 @@ void GL_BuildLightmaps (void) lm->texture = TexMgr_LoadImage (cl.worldmodel, name, LMBLOCK_WIDTH, LMBLOCK_HEIGHT, SRC_LIGHTMAP, lm->data, "", (src_offset_t)lm->data, TEXPREF_LINEAR | TEXPREF_NOPICMIP); //johnfitz + + UQ_GL_UploadLightmap(i, LMBLOCK_WIDTH, LMBLOCK_HEIGHT, lm->data); } //johnfitz -- warn about exceeding old limits @@ -1270,6 +1272,8 @@ static void R_UploadLightmap(int lmap) lm->rectchange.h = 0; lm->rectchange.w = 0; + UQ_GL_UploadLightmap(lmap, LMBLOCK_WIDTH, LMBLOCK_HEIGHT, lm->data); + rs_dynamiclightmaps++; } diff --git a/engine/UniQuake/gl_uniquake.c b/engine/UniQuake/gl_uniquake.c index 41e8215..3ac445c 100644 --- a/engine/UniQuake/gl_uniquake.c +++ b/engine/UniQuake/gl_uniquake.c @@ -11,6 +11,7 @@ typedef struct unity_glcalls_s int(*UploadBrushModel)(void *target, qmodel_t *model); int(*UploadWorldModel)(void *target, qmodel_t *model); qboolean(*UploadTexture)(void *target, gltexture_t *texture, unsigned *data, GLuint *texnum); + qboolean(*UploadLightmap)(void *target, int lmap, int width, int height, byte *data); void(*SetupView)(void *target, vec3_t origin, vec3_t angles, mleaf_t *viewLeaf); } unity_glcalls_t; @@ -39,6 +40,11 @@ qboolean UQ_GL_UploadTexture(gltexture_t *texture, unsigned *data) return unity_glcalls->UploadTexture(unity_glcalls->target, texture, data, &texture->texnum); } +qboolean UQ_GL_UploadLightmap(int lmap, int width, int height, byte *data) +{ + return unity_glcalls->UploadLightmap(unity_glcalls->target, lmap, width, height, data); +} + void UQ_GL_SetupView(vec3_t origin, vec3_t angles, mleaf_t *viewLeaf) { unity_glcalls->SetupView(unity_glcalls->target, origin, angles, viewLeaf);