Compare commits

...

2 Commits

Author SHA1 Message Date
Jarrod Doyle 4a80ef547b
Support toggling lightmap layers 2024-08-28 07:44:07 +01:00
Jarrod Doyle 092896aa52
Use Texture2dArray for lightmap texture 2024-08-27 21:18:31 +01:00
3 changed files with 156 additions and 55 deletions

View File

@ -149,57 +149,45 @@ public class WorldRep : IChunk
} }
} }
public readonly byte[] AsBytesRgba() public readonly byte[] AsBytesRgba(int layer)
{ {
var length = Width * Height * 4; if (layer >= Layers)
var bytes = new byte[length];
for (var i = 0; i < length; i++)
{ {
bytes[i] = 0; throw new ArgumentOutOfRangeException(nameof(layer));
} }
// TODO: might be faster to do these fors in the switch? var length = Width * Height * 4;
for (var z = 0; z < Layers; z++) var layerOffset = layer * Bpp * Width * Height;
{ var bytes = new byte[length];
for (var y = 0; y < Height; y++) for (var y = 0; y < Height; y++)
{ {
for (var x = 0; x < Width; x++) for (var x = 0; x < Width; x++)
{ {
var pIdx = 0 + x * Bpp + y * Bpp * Width + z * Bpp * Width * Height; var bIdx = x * 4 + y * 4 * Width;
var r = 0; var pIdx = x * Bpp + y * Bpp * Width + layerOffset;
var g = 0;
var b = 0;
var a = 0;
switch (Bpp) switch (Bpp)
{ {
case 1: case 1:
var raw1 = Pixels[pIdx]; var raw1 = Pixels[pIdx];
r = raw1; bytes[bIdx] = raw1;
g = raw1; bytes[bIdx + 1] = raw1;
b = raw1; bytes[bIdx + 2] = raw1;
a = 255; bytes[bIdx + 3] = 255;
break; break;
case 2: case 2:
var raw2 = Pixels[pIdx] + (Pixels[pIdx + 1] << 8); var raw2 = Pixels[pIdx] + (Pixels[pIdx + 1] << 8);
r = (int)(255 * (raw2 & 31) / 31.0f); bytes[bIdx] = (byte)(255 * (raw2 & 31) / 31.0f);
g = (int)(255 * ((raw2 >> 5) & 31) / 31.0f); bytes[bIdx + 1] = (byte)(255 * ((raw2 >> 5) & 31) / 31.0f);
b = (int)(255 * ((raw2 >> 10) & 31) / 31.0f); bytes[bIdx + 2] = (byte)(255 * ((raw2 >> 10) & 31) / 31.0f);
a = 255; bytes[bIdx + 3] = 255;
break; break;
case 4: case 4:
r = Pixels[pIdx + 2]; bytes[bIdx] = Pixels[pIdx + 2];
g = Pixels[pIdx + 1]; bytes[bIdx + 1] = Pixels[pIdx + 1];
b = Pixels[pIdx]; bytes[bIdx + 2] = Pixels[pIdx];
a = Pixels[pIdx + 3]; bytes[bIdx + 3] = Pixels[pIdx + 3];
break; break;
} }
var bIdx = x * 4 + y * 4 * Width;
bytes[bIdx] = (byte)Math.Min(255, bytes[bIdx] + r);
bytes[bIdx + 1] = (byte)Math.Min(255, bytes[bIdx + 1] + g);
bytes[bIdx + 2] = (byte)Math.Min(255, bytes[bIdx + 2] + b);
bytes[bIdx + 3] = (byte)Math.Min(255, bytes[bIdx + 3] + a);
}
} }
} }

View File

@ -45,12 +45,16 @@ public partial class Mission : Node3D
DbFile _file; DbFile _file;
TextureLoader _textureLoader; TextureLoader _textureLoader;
ModelLoader _modelLoader; ModelLoader _modelLoader;
List<ShaderMaterial> _materials;
Vector2I _lmLayerMask;
public override void _Ready() public override void _Ready()
{ {
var extractPath = ProjectSettings.GlobalizePath($"user://extracted/tmp"); var extractPath = ProjectSettings.GlobalizePath($"user://extracted/tmp");
_installPaths = new ResourcePathManager(extractPath); _installPaths = new ResourcePathManager(extractPath);
_modelLoader = new ModelLoader(_installPaths); _modelLoader = new ModelLoader(_installPaths);
_materials = new List<ShaderMaterial>();
_lmLayerMask = new Vector2I(~0, ~0);
var missionSelector = GetNode<Control>("%MissionSelector") as MissionSelector; var missionSelector = GetNode<Control>("%MissionSelector") as MissionSelector;
missionSelector.pathManager = _installPaths; missionSelector.pathManager = _installPaths;
missionSelector.MissionSelected += (string campaign, string mission) => missionSelector.MissionSelected += (string campaign, string mission) =>
@ -84,6 +88,10 @@ public partial class Mission : Node3D
{ {
Build = true; Build = true;
} }
if (keyEvent.Keycode == Key.O)
{
ToggleLightmap();
}
} }
} }
@ -93,6 +101,8 @@ public partial class Mission : Node3D
{ {
node.QueueFree(); node.QueueFree();
} }
_materials.Clear();
} }
public void RebuildMap() public void RebuildMap()
@ -111,6 +121,60 @@ public partial class Mission : Node3D
} }
} }
public void ToggleLightmap()
{
if (_lmLayerMask == Vector2I.Zero)
{
_lmLayerMask = new Vector2I(~0, ~0);
}
else
{
_lmLayerMask = Vector2I.Zero;
}
foreach (var mat in _materials)
{
mat.SetShaderParameter("lightmap_bitmask", _lmLayerMask);
}
}
public void ToggleLmLayer(uint layer)
{
if (layer >= 64)
{
throw new ArgumentOutOfRangeException(nameof(layer));
}
static int ToggleBit(int bitIdx, int val)
{
var mask = 1 << bitIdx;
var isEnabled = (val & mask) != 0;
if (isEnabled)
{
val &= ~mask;
}
else
{
val |= mask;
}
return val;
}
if (layer < 32)
{
_lmLayerMask.X = ToggleBit((int)layer, _lmLayerMask.X);
}
else
{
_lmLayerMask.Y = ToggleBit((int)layer - 32, _lmLayerMask.Y);
}
foreach (var mat in _materials)
{
mat.SetShaderParameter("lightmap_bitmask", _lmLayerMask);
}
}
// TODO: Make this less of a mess // TODO: Make this less of a mess
private ObjectHierarchy BuildHierarchy() private ObjectHierarchy BuildHierarchy()
{ {
@ -239,10 +303,12 @@ public partial class Mission : Node3D
} }
var albedoTexture = _textureLoader.Get(textureId); var albedoTexture = _textureLoader.Get(textureId);
var mat = BuildMaterial(albedoTexture, lightmapTexture, lmHdr, _lmLayerMask);
_materials.Add(mat);
var mesh = new ArrayMesh(); var mesh = new ArrayMesh();
mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surface.BuildSurfaceArray()); mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surface.BuildSurfaceArray());
mesh.SurfaceSetMaterial(0, BuildMaterial(albedoTexture, lightmapTexture, lmHdr)); mesh.SurfaceSetMaterial(0, mat);
var meshInstance = new MeshInstance3D { Mesh = mesh }; var meshInstance = new MeshInstance3D { Mesh = mesh };
AddChild(meshInstance); AddChild(meshInstance);
@ -282,7 +348,11 @@ public partial class Mission : Node3D
return new LightmapRectData(cellIdx, polyIdx, textureId, start, end); return new LightmapRectData(cellIdx, polyIdx, textureId, start, end);
} }
private static Texture BuildLightmapTexture(WorldRep.Cell[] cells, PackingRectangle[] packingRects, Dictionary<int, LightmapRectData> rectDataMap, Dictionary<int, MeshSurfaceData> surfaceDataMap) private static Texture2DArray BuildLightmapTexture(
WorldRep.Cell[] cells,
PackingRectangle[] packingRects,
Dictionary<int, LightmapRectData> rectDataMap,
Dictionary<int, MeshSurfaceData> surfaceDataMap)
{ {
var bounds = Timing.TimeStage("RectPack", () => var bounds = Timing.TimeStage("RectPack", () =>
{ {
@ -290,7 +360,16 @@ public partial class Mission : Node3D
return bounds; return bounds;
}); });
GD.Print($"Creating lightmap with bounds: ({bounds.Width}, {bounds.Height})"); GD.Print($"Creating lightmap with bounds: ({bounds.Width}, {bounds.Height})");
var image = Image.CreateEmpty((int)bounds.Width, (int)bounds.Height, false, Image.Format.Rgba8);
var lightmapFormat = Image.Format.Rgba8;
var lmLayerCount = 33;
var lmImages = new Godot.Collections.Array<Image>();
lmImages.Resize(lmLayerCount);
for (var i = 0; i < lmLayerCount; i++)
{
lmImages[i] = Image.CreateEmpty((int)bounds.Width, (int)bounds.Height, false, lightmapFormat);
}
foreach (var rect in packingRects) foreach (var rect in packingRects)
{ {
if (!rectDataMap.ContainsKey(rect.Id)) GD.Print("Invalid rectDataMap key"); if (!rectDataMap.ContainsKey(rect.Id)) GD.Print("Invalid rectDataMap key");
@ -298,9 +377,18 @@ public partial class Mission : Node3D
if (info.cellIndex >= cells.Length) GD.Print($"CellIndex too big: {info.cellIndex}/{cells.Length}"); if (info.cellIndex >= cells.Length) GD.Print($"CellIndex too big: {info.cellIndex}/{cells.Length}");
if (info.lightmapIndex >= cells[info.cellIndex].Lightmaps.Length) GD.Print($"LightmapIndex too big: {info.lightmapIndex}/{cells[info.cellIndex].Lightmaps.Length}"); if (info.lightmapIndex >= cells[info.cellIndex].Lightmaps.Length) GD.Print($"LightmapIndex too big: {info.lightmapIndex}/{cells[info.cellIndex].Lightmaps.Length}");
var lightmap = cells[info.cellIndex].Lightmaps[info.lightmapIndex]; var lightmap = cells[info.cellIndex].Lightmaps[info.lightmapIndex];
var cellLm = Image.CreateFromData(lightmap.Width, lightmap.Height, false, Image.Format.Rgba8, lightmap.AsBytesRgba()); var width = lightmap.Width;
image.BlitRect(cellLm, new Rect2I(0, 0, lightmap.Width, lightmap.Height), new Vector2I((int)rect.X, (int)rect.Y)); var height = lightmap.Height;
var layerCount = lightmap.Layers;
var srcRect = new Rect2I(0, 0, width, height);
var dst = new Vector2I((int)rect.X, (int)rect.Y);
for (var i = 0; i < layerCount; i++)
{
var cellLm = Image.CreateFromData(width, height, false, lightmapFormat, lightmap.AsBytesRgba(i));
lmImages[i].BlitRect(cellLm, srcRect, dst);
}
if (!surfaceDataMap.ContainsKey(info.textureId)) GD.Print("Invalid SurfaceDataMap key"); if (!surfaceDataMap.ContainsKey(info.textureId)) GD.Print("Invalid SurfaceDataMap key");
surfaceDataMap[info.textureId].TransformUv2s(info.uvStart, info.uvEnd, (uv) => surfaceDataMap[info.textureId].TransformUv2s(info.uvStart, info.uvEnd, (uv) =>
@ -321,7 +409,9 @@ public partial class Mission : Node3D
}); });
} }
return ImageTexture.CreateFromImage(image); var lightmapTexture = new Texture2DArray();
lightmapTexture.CreateFromImages(lmImages);
return lightmapTexture;
} }
private int CalcBaseUV( private int CalcBaseUV(
@ -448,12 +538,17 @@ public partial class Mission : Node3D
} }
const string MATERIAL_PATH = "res://project/materials/base.tres"; const string MATERIAL_PATH = "res://project/materials/base.tres";
private static Material BuildMaterial(Texture albedoTexture, Texture lightmapTexture, bool lmHdr) private static ShaderMaterial BuildMaterial(
Texture2D albedoTexture,
Texture2DArray lightmapTexture,
bool lmHdr,
Vector2I layerMask)
{ {
var material = ResourceLoader.Load<ShaderMaterial>(MATERIAL_PATH).Duplicate() as ShaderMaterial; var material = ResourceLoader.Load<ShaderMaterial>(MATERIAL_PATH).Duplicate() as ShaderMaterial;
material.SetShaderParameter("texture_albedo", albedoTexture); material.SetShaderParameter("texture_albedo", albedoTexture);
material.SetShaderParameter("lightmap_albedo", lightmapTexture); material.SetShaderParameter("lightmap_albedo", lightmapTexture);
material.SetShaderParameter("lightmap_2x", lmHdr); material.SetShaderParameter("lightmap_2x", lmHdr);
material.SetShaderParameter("lightmap_bitmask", layerMask);
return material; return material;
} }
} }

View File

@ -4,8 +4,9 @@
code = "shader_type spatial; code = "shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,unshaded; render_mode blend_mix,depth_draw_opaque,cull_back,unshaded;
uniform sampler2D texture_albedo : filter_linear_mipmap_anisotropic,repeat_enable; uniform sampler2D texture_albedo : filter_linear_mipmap_anisotropic,repeat_enable;
uniform sampler2D lightmap_albedo : filter_linear_mipmap_anisotropic,repeat_enable; uniform sampler2DArray lightmap_albedo : filter_linear_mipmap_anisotropic,repeat_enable;
uniform bool lightmap_2x; uniform bool lightmap_2x;
uniform ivec2 lightmap_bitmask;
float srgb_to_linear_e(float input) { float srgb_to_linear_e(float input) {
float output; float output;
@ -28,11 +29,27 @@ vec3 srgb_to_linear(vec3 input) {
void fragment() { void fragment() {
vec4 albedo_tex = texture(texture_albedo,UV); vec4 albedo_tex = texture(texture_albedo,UV);
vec4 lightmap_tex = texture(lightmap_albedo,UV2); if (lightmap_bitmask.x == 0 && lightmap_bitmask.y == 0) {
ALBEDO = srgb_to_linear(albedo_tex.rgb);
} else {
vec4 lightmap_tex = vec4(0.0);
int mask = lightmap_bitmask.x;
for (int i = 0; i < 33; i++) {
if (i == 32) {
mask = lightmap_bitmask.y;
}
bool layer_enabled = (mask & (1 << (i % 32))) != 0;
if (layer_enabled) {
lightmap_tex += texture(lightmap_albedo, vec3(UV2, float(i)));
}
}
lightmap_tex = min(vec4(1.0), lightmap_tex);
if (lightmap_2x) { if (lightmap_2x) {
lightmap_tex *= 2.0f; lightmap_tex *= 2.0f;
} }
ALBEDO = srgb_to_linear((albedo_tex * lightmap_tex).rgb); ALBEDO = srgb_to_linear((albedo_tex * lightmap_tex).rgb);
}
} }
" "
@ -40,3 +57,4 @@ void fragment() {
render_priority = 0 render_priority = 0
shader = SubResource("Shader_eumy4") shader = SubResource("Shader_eumy4")
shader_parameter/lightmap_2x = null shader_parameter/lightmap_2x = null
shader_parameter/lightmap_bitmask = null