diff --git a/Editor/ObjectWindow.cpp b/Editor/ObjectWindow.cpp
index 6724d52fe..2afd77c4e 100644
--- a/Editor/ObjectWindow.cpp
+++ b/Editor/ObjectWindow.cpp
@@ -246,10 +246,19 @@ ObjectWindow::ObjectWindow(wiGUI* gui) : GUI(gui)
y += 30;
- generateAtlasButton = new wiButton("Generate Atlas");
- generateAtlasButton->SetTooltip("Toggle kinematic behaviour.");
- generateAtlasButton->SetPos(XMFLOAT2(x, y += 30));
- generateAtlasButton->OnClick([&](wiEventArgs args) {
+
+ lightmapResolutionSlider = new wiSlider(32, 1024, 128, 1024 - 32, "Lightmap resolution: ");
+ lightmapResolutionSlider->SetTooltip("Set the approximate resolution for this object's lightmap. This will be packed into the larger global lightmap later.");
+ lightmapResolutionSlider->SetSize(XMFLOAT2(100, 30));
+ lightmapResolutionSlider->SetPos(XMFLOAT2(x, y += 30));
+ objectWindow->AddWidget(lightmapResolutionSlider);
+
+
+ generateLightmapButton = new wiButton("Generate Lightmap");
+ generateLightmapButton->SetTooltip("Toggle kinematic behaviour.");
+ generateLightmapButton->SetPos(XMFLOAT2(x, y += 30));
+ generateLightmapButton->SetSize(XMFLOAT2(140,30));
+ generateLightmapButton->OnClick([&](wiEventArgs args) {
Scene& scene = wiRenderer::GetScene();
ObjectComponent* objectcomponent = scene.objects.GetComponent(entity);
@@ -288,7 +297,7 @@ ObjectWindow::ObjectWindow(wiGUI* gui) : GUI(gui)
// Generate atlas:
{
xatlas::PackerOptions packerOptions;
- packerOptions.resolution = 1024;
+ packerOptions.resolution = (uint32_t)lightmapResolutionSlider->GetValue();
packerOptions.conservative = true;
packerOptions.padding = 1;
xatlas::GenerateCharts(atlas, xatlas::CharterOptions(), nullptr, nullptr);
@@ -418,7 +427,7 @@ ObjectWindow::ObjectWindow(wiGUI* gui) : GUI(gui)
});
- objectWindow->AddWidget(generateAtlasButton);
+ objectWindow->AddWidget(generateLightmapButton);
diff --git a/Editor/ObjectWindow.h b/Editor/ObjectWindow.h
index 9e8249415..3dabd809c 100644
--- a/Editor/ObjectWindow.h
+++ b/Editor/ObjectWindow.h
@@ -33,6 +33,7 @@ public:
wiCheckBox* kinematicCheckBox;
wiComboBox* collisionShapeComboBox;
- wiButton* generateAtlasButton;
+ wiSlider* lightmapResolutionSlider;
+ wiButton* generateLightmapButton;
};
diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj b/WickedEngine/WickedEngine_SHADERS.vcxproj
index a0140ea48..8e4f24631 100644
--- a/WickedEngine/WickedEngine_SHADERS.vcxproj
+++ b/WickedEngine/WickedEngine_SHADERS.vcxproj
@@ -35,6 +35,7 @@
+
diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters
index 4e8736ef6..81e7fccc8 100644
--- a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters
+++ b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters
@@ -115,6 +115,9 @@
HF
+
+ HF
+
diff --git a/WickedEngine/globals.hlsli b/WickedEngine/globals.hlsli
index 92bd90dad..d72f5bfde 100644
--- a/WickedEngine/globals.hlsli
+++ b/WickedEngine/globals.hlsli
@@ -106,7 +106,7 @@ struct ComputeShaderInput
// Helpers:
-// returns a random float in range (0, 1). seed must not be >0!
+// returns a random float in range (0, 1). seed must be >0!
inline float rand(inout float seed, in float2 uv)
{
float result = frac(sin(seed * dot(uv, float2(12.9898f, 78.233f))) * 43758.5453f);
diff --git a/WickedEngine/objectInputLayoutHF.hlsli b/WickedEngine/objectInputLayoutHF.hlsli
index 64e57c3a7..69f8b8469 100644
--- a/WickedEngine/objectInputLayoutHF.hlsli
+++ b/WickedEngine/objectInputLayoutHF.hlsli
@@ -14,6 +14,10 @@ struct Input_InstancePrev
float4 wiPrev1 : MATIPREV1;
float4 wiPrev2 : MATIPREV2;
};
+struct Input_InstanceAtlas
+{
+ float4 atlasMulAdd : INSTANCEATLAS;
+};
struct Input_Object_POS
{
@@ -34,6 +38,7 @@ struct Input_Object_ALL
float4 pre : PREVPOS;
Input_Instance instance;
Input_InstancePrev instancePrev;
+ Input_InstanceAtlas instanceAtlas;
};
inline float4x4 MakeWorldMatrixFromInstance(in Input_Instance input)
@@ -108,7 +113,7 @@ inline VertexSurface MakeVertexSurfaceFromInput(Input_Object_ALL input)
surface.uv = input.tex;
- surface.atlas = input.atl;
+ surface.atlas = input.atl * input.instanceAtlas.atlasMulAdd.xy + input.instanceAtlas.atlasMulAdd.zw;
surface.prevPos = float4(input.pre.xyz, 1);
diff --git a/WickedEngine/raySceneIntersectHF.hlsli b/WickedEngine/raySceneIntersectHF.hlsli
new file mode 100644
index 000000000..0616fe6eb
--- /dev/null
+++ b/WickedEngine/raySceneIntersectHF.hlsli
@@ -0,0 +1,278 @@
+#ifndef _RAY_SCENE_INTERSECT_HF_
+#define _RAY_SCENE_INTERSECT_HF_
+#include "globals.hlsli"
+#include "tracedRenderingHF.hlsli"
+
+STRUCTUREDBUFFER(materialBuffer, TracedRenderingMaterial, TEXSLOT_ONDEMAND0);
+STRUCTUREDBUFFER(triangleBuffer, BVHMeshTriangle, TEXSLOT_ONDEMAND1);
+RAWBUFFER(clusterCounterBuffer, TEXSLOT_ONDEMAND2);
+STRUCTUREDBUFFER(clusterIndexBuffer, uint, TEXSLOT_ONDEMAND3);
+STRUCTUREDBUFFER(clusterOffsetBuffer, uint2, TEXSLOT_ONDEMAND4);
+STRUCTUREDBUFFER(clusterConeBuffer, ClusterCone, TEXSLOT_ONDEMAND5);
+STRUCTUREDBUFFER(bvhNodeBuffer, BVHNode, TEXSLOT_ONDEMAND6);
+STRUCTUREDBUFFER(bvhAABBBuffer, BVHAABB, TEXSLOT_ONDEMAND7);
+
+TEXTURE2D(materialTextureAtlas, float4, TEXSLOT_ONDEMAND8);
+
+inline RayHit TraceScene(Ray ray)
+{
+ RayHit bestHit = CreateRayHit();
+
+ // Using BVH acceleration structure:
+
+ // Emulated stack for tree traversal:
+ const uint stacksize = 32;
+ uint stack[stacksize];
+ uint stackpos = 0;
+
+ const uint clusterCount = clusterCounterBuffer.Load(0);
+ const uint leafNodeOffset = clusterCount - 1;
+
+ // push root node
+ stack[stackpos] = 0;
+ stackpos++;
+
+ uint k = 0;
+ do {
+ k++;
+ if (k > 256)
+ return bestHit;
+
+ // pop untraversed node
+ stackpos--;
+ const uint nodeIndex = stack[stackpos];
+
+ BVHNode node = bvhNodeBuffer[nodeIndex];
+ BVHAABB box = bvhAABBBuffer[nodeIndex];
+
+ if (IntersectBox(ray, box))
+ {
+ //if (node.LeftChildIndex == 0 && node.RightChildIndex == 0)
+ if (nodeIndex >= clusterCount - 1)
+ {
+ // Leaf node
+ const uint nodeToClusterID = nodeIndex - leafNodeOffset;
+ const uint clusterIndex = clusterIndexBuffer[nodeToClusterID];
+ bool cullCluster = false;
+
+ //// Compute per cluster visibility:
+ //const ClusterCone cone = clusterConeBuffer[clusterIndex];
+ //if (cone.valid)
+ //{
+ // const float3 testVec = normalize(ray.origin - cone.position);
+ // if (dot(testVec, cone.direction) > cone.angleCos)
+ // {
+ // cullCluster = true;
+ // }
+ //}
+
+ if (!cullCluster)
+ {
+ const uint2 cluster = clusterOffsetBuffer[clusterIndex];
+ const uint triangleOffset = cluster.x;
+ const uint triangleCount = cluster.y;
+
+ for (uint tri = 0; tri < triangleCount; ++tri)
+ {
+ const uint primitiveID = triangleOffset + tri;
+ IntersectTriangle(ray, bestHit, triangleBuffer[primitiveID], primitiveID);
+ }
+ }
+ }
+ else
+ {
+ // Internal node
+ if (stackpos < stacksize - 1)
+ {
+ // push left child
+ stack[stackpos] = node.LeftChildIndex;
+ stackpos++;
+ // push right child
+ stack[stackpos] = node.RightChildIndex;
+ stackpos++;
+ }
+ else
+ {
+ // stack overflow, terminate
+ break;
+ }
+ }
+
+ }
+
+ } while (stackpos > 0);
+
+
+ return bestHit;
+}
+
+inline bool TraceSceneANY(Ray ray, float maxDistance)
+{
+ bool shadow = false;
+
+ // Using BVH acceleration structure:
+
+ // Emulated stack for tree traversal:
+ const uint stacksize = 32;
+ uint stack[stacksize];
+ uint stackpos = 0;
+
+ const uint clusterCount = clusterCounterBuffer.Load(0);
+ const uint leafNodeOffset = clusterCount - 1;
+
+ // push root node
+ stack[stackpos] = 0;
+ stackpos++;
+
+ uint k = 0;
+ do {
+ k++;
+ if (k > 256)
+ return false;
+
+ // pop untraversed node
+ stackpos--;
+ const uint nodeIndex = stack[stackpos];
+
+ BVHNode node = bvhNodeBuffer[nodeIndex];
+ BVHAABB box = bvhAABBBuffer[nodeIndex];
+
+ if (IntersectBox(ray, box))
+ {
+ //if (node.LeftChildIndex == 0 && node.RightChildIndex == 0)
+ if (nodeIndex >= clusterCount - 1)
+ {
+ // Leaf node
+ const uint nodeToClusterID = nodeIndex - leafNodeOffset;
+ const uint clusterIndex = clusterIndexBuffer[nodeToClusterID];
+ bool cullCluster = false;
+
+ //// Compute per cluster visibility:
+ //const ClusterCone cone = clusterConeBuffer[clusterIndex];
+ //if (cone.valid)
+ //{
+ // const float3 testVec = normalize(ray.origin - cone.position);
+ // if (dot(testVec, cone.direction) > cone.angleCos)
+ // {
+ // cullCluster = true;
+ // }
+ //}
+
+ if (!cullCluster)
+ {
+ const uint2 cluster = clusterOffsetBuffer[clusterIndex];
+ const uint triangleOffset = cluster.x;
+ const uint triangleCount = cluster.y;
+
+ for (uint tri = 0; tri < triangleCount; ++tri)
+ {
+ const uint primitiveID = triangleOffset + tri;
+ if (IntersectTriangleANY(ray, maxDistance, triangleBuffer[primitiveID]))
+ {
+ shadow = true;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ // Internal node
+ if (stackpos < stacksize - 1)
+ {
+ // push left child
+ stack[stackpos] = node.LeftChildIndex;
+ stackpos++;
+ // push right child
+ stack[stackpos] = node.RightChildIndex;
+ stackpos++;
+ }
+ else
+ {
+ // stack overflow, terminate
+ break;
+ }
+ }
+
+ }
+
+ } while (!shadow && stackpos > 0);
+
+ return shadow;
+}
+
+inline float3 Shade(inout Ray ray, RayHit hit, inout float seed, in float2 pixel)
+{
+ if (hit.distance < INFINITE_RAYHIT)
+ {
+ BVHMeshTriangle tri = triangleBuffer[hit.primitiveID];
+
+ float u = hit.bary.x;
+ float v = hit.bary.y;
+ float w = 1 - u - v;
+
+ float3 N = normalize(tri.n0 * w + tri.n1 * u + tri.n2 * v);
+ float2 UV = tri.t0 * w + tri.t1 * u + tri.t2 * v;
+
+ uint materialIndex = tri.materialIndex;
+
+ TracedRenderingMaterial mat = materialBuffer[materialIndex];
+
+ UV = frac(UV); // emulate wrap
+ float4 baseColorMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV * mat.baseColorAtlasMulAdd.xy + mat.baseColorAtlasMulAdd.zw, 0);
+ float4 surfaceMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV * mat.surfaceMapAtlasMulAdd.xy + mat.surfaceMapAtlasMulAdd.zw, 0);
+ float4 normalMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV * mat.normalMapAtlasMulAdd.xy + mat.normalMapAtlasMulAdd.zw, 0);
+
+ float4 baseColor = DEGAMMA(mat.baseColor * baseColorMap);
+ float reflectance = mat.reflectance * surfaceMap.r;
+ float metalness = mat.metalness * surfaceMap.g;
+ float3 emissive = baseColor.rgb * mat.emissive * surfaceMap.b;
+ float roughness = mat.roughness /** normalMap.a*/;
+ float sss = mat.subsurfaceScattering;
+
+ float3 albedo = ComputeAlbedo(baseColor, reflectance, metalness);
+ float3 specular = ComputeF0(baseColor, reflectance, metalness);
+
+
+ // Calculate chances of diffuse and specular reflection
+ albedo = min(1.0f - specular, albedo);
+ float specChance = dot(specular, 0.33);
+ float diffChance = dot(albedo, 0.33);
+ float inv = 1.0f / (specChance + diffChance);
+ specChance *= inv;
+ diffChance *= inv;
+
+ // Roulette-select the ray's path
+ float roulette = rand(seed, pixel);
+ if (roulette < specChance)
+ {
+ // Specular reflection
+ //float alpha = 150.0f;
+ float alpha = sqr(1 - roughness) * 1000;
+ ray.direction = SampleHemisphere(reflect(ray.direction, N), alpha, seed, pixel);
+ float f = (alpha + 2) / (alpha + 1);
+ ray.energy *= (1.0f / specChance) * specular * saturate(dot(N, ray.direction) * f);
+ }
+ else
+ {
+ // Diffuse reflection
+ ray.direction = SampleHemisphere(N, 1.0f, seed, pixel);
+ ray.energy *= (1.0f / diffChance) * albedo;
+ }
+
+ ray.origin = hit.position + N * EPSILON;
+ ray.primitiveID = hit.primitiveID;
+ ray.bary = hit.bary;
+
+ return emissive;
+ }
+ else
+ {
+ // Erase the ray's energy - the sky doesn't reflect anything
+ ray.energy = 0.0f;
+
+ return GetDynamicSkyColor(ray.direction);
+ }
+}
+
+#endif // _RAY_SCENE_INTERSECT_HF_
diff --git a/WickedEngine/raytrace_lightsamplingCS.hlsl b/WickedEngine/raytrace_lightsamplingCS.hlsl
index 6c5b6da24..ef24de051 100644
--- a/WickedEngine/raytrace_lightsamplingCS.hlsl
+++ b/WickedEngine/raytrace_lightsamplingCS.hlsl
@@ -2,112 +2,12 @@
#include "ShaderInterop_TracedRendering.h"
#include "ShaderInterop_BVH.h"
#include "tracedRenderingHF.hlsli"
-
-RWTEXTURE2D(resultTexture, float4, 0);
-
-STRUCTUREDBUFFER(materialBuffer, TracedRenderingMaterial, TEXSLOT_ONDEMAND0);
-STRUCTUREDBUFFER(triangleBuffer, BVHMeshTriangle, TEXSLOT_ONDEMAND1);
-RAWBUFFER(clusterCounterBuffer, TEXSLOT_ONDEMAND2);
-STRUCTUREDBUFFER(clusterIndexBuffer, uint, TEXSLOT_ONDEMAND3);
-STRUCTUREDBUFFER(clusterOffsetBuffer, uint2, TEXSLOT_ONDEMAND4);
-STRUCTUREDBUFFER(clusterConeBuffer, ClusterCone, TEXSLOT_ONDEMAND5);
-STRUCTUREDBUFFER(bvhNodeBuffer, BVHNode, TEXSLOT_ONDEMAND6);
-STRUCTUREDBUFFER(bvhAABBBuffer, BVHAABB, TEXSLOT_ONDEMAND7);
-
-TEXTURE2D(materialTextureAtlas, float4, TEXSLOT_ONDEMAND8);
+#include "raySceneIntersectHF.hlsli"
RAWBUFFER(counterBuffer_READ, TEXSLOT_UNIQUE0);
STRUCTUREDBUFFER(rayBuffer_READ, TracedRenderingStoredRay, TEXSLOT_UNIQUE1);
-inline bool TraceSceneANY(Ray ray, float maxDistance)
-{
- bool shadow = false;
-
- // Using BVH acceleration structure:
-
- // Emulated stack for tree traversal:
- const uint stacksize = 32;
- uint stack[stacksize];
- uint stackpos = 0;
-
- const uint clusterCount = clusterCounterBuffer.Load(0);
- const uint leafNodeOffset = clusterCount - 1;
-
- // push root node
- stack[stackpos] = 0;
- stackpos++;
-
- do {
- // pop untraversed node
- stackpos--;
- const uint nodeIndex = stack[stackpos];
-
- BVHNode node = bvhNodeBuffer[nodeIndex];
- BVHAABB box = bvhAABBBuffer[nodeIndex];
-
- if (IntersectBox(ray, box))
- {
- //if (node.LeftChildIndex == 0 && node.RightChildIndex == 0)
- if (nodeIndex >= clusterCount - 1)
- {
- // Leaf node
- const uint nodeToClusterID = nodeIndex - leafNodeOffset;
- const uint clusterIndex = clusterIndexBuffer[nodeToClusterID];
- bool cullCluster = false;
-
- //// Compute per cluster visibility:
- //const ClusterCone cone = clusterConeBuffer[clusterIndex];
- //if (cone.valid)
- //{
- // const float3 testVec = normalize(ray.origin - cone.position);
- // if (dot(testVec, cone.direction) > cone.angleCos)
- // {
- // cullCluster = true;
- // }
- //}
-
- if (!cullCluster)
- {
- const uint2 cluster = clusterOffsetBuffer[clusterIndex];
- const uint triangleOffset = cluster.x;
- const uint triangleCount = cluster.y;
-
- for (uint tri = 0; tri < triangleCount; ++tri)
- {
- const uint primitiveID = triangleOffset + tri;
- if (IntersectTriangleANY(ray, maxDistance, triangleBuffer[primitiveID]))
- {
- shadow = true;
- break;
- }
- }
- }
- }
- else
- {
- // Internal node
- if (stackpos < stacksize - 1)
- {
- // push left child
- stack[stackpos] = node.LeftChildIndex;
- stackpos++;
- // push right child
- stack[stackpos] = node.RightChildIndex;
- stackpos++;
- }
- else
- {
- // stack overflow, terminate
- break;
- }
- }
-
- }
-
- } while (!shadow && stackpos > 0);
-
- return shadow;
-}
+RWTEXTURE2D(resultTexture, float4, 0);
[numthreads(TRACEDRENDERING_TRACE_GROUPSIZE, 1, 1)]
void main( uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex)
diff --git a/WickedEngine/raytrace_primaryCS.hlsl b/WickedEngine/raytrace_primaryCS.hlsl
index 0aa25fadd..5614461f1 100644
--- a/WickedEngine/raytrace_primaryCS.hlsl
+++ b/WickedEngine/raytrace_primaryCS.hlsl
@@ -2,7 +2,10 @@
#include "ShaderInterop_TracedRendering.h"
#include "ShaderInterop_BVH.h"
#include "tracedRenderingHF.hlsli"
+#include "raySceneIntersectHF.hlsli"
+RAWBUFFER(counterBuffer_READ, TEXSLOT_UNIQUE0);
+STRUCTUREDBUFFER(rayBuffer_READ, TracedRenderingStoredRay, TEXSLOT_UNIQUE1);
RWRAWBUFFER(counterBuffer_WRITE, 0);
RWSTRUCTUREDBUFFER(rayBuffer_WRITE, TracedRenderingStoredRay, 1);
@@ -18,180 +21,6 @@ groupshared uint GroupRayCount;
groupshared uint GroupRayWriteOffset;
#endif // ADVANCED_ALLOCATION
-STRUCTUREDBUFFER(materialBuffer, TracedRenderingMaterial, TEXSLOT_ONDEMAND0);
-STRUCTUREDBUFFER(triangleBuffer, BVHMeshTriangle, TEXSLOT_ONDEMAND1);
-RAWBUFFER(clusterCounterBuffer, TEXSLOT_ONDEMAND2);
-STRUCTUREDBUFFER(clusterIndexBuffer, uint, TEXSLOT_ONDEMAND3);
-STRUCTUREDBUFFER(clusterOffsetBuffer, uint2, TEXSLOT_ONDEMAND4);
-STRUCTUREDBUFFER(clusterConeBuffer, ClusterCone, TEXSLOT_ONDEMAND5);
-STRUCTUREDBUFFER(bvhNodeBuffer, BVHNode, TEXSLOT_ONDEMAND6);
-STRUCTUREDBUFFER(bvhAABBBuffer, BVHAABB, TEXSLOT_ONDEMAND7);
-
-TEXTURE2D(materialTextureAtlas, float4, TEXSLOT_ONDEMAND8);
-
-RAWBUFFER(counterBuffer_READ, TEXSLOT_UNIQUE0);
-STRUCTUREDBUFFER(rayBuffer_READ, TracedRenderingStoredRay, TEXSLOT_UNIQUE1);
-
-inline RayHit TraceScene(Ray ray)
-{
- RayHit bestHit = CreateRayHit();
-
- // Using BVH acceleration structure:
-
- // Emulated stack for tree traversal:
- const uint stacksize = 32;
- uint stack[stacksize];
- uint stackpos = 0;
-
- const uint clusterCount = clusterCounterBuffer.Load(0);
- const uint leafNodeOffset = clusterCount - 1;
-
- // push root node
- stack[stackpos] = 0;
- stackpos++;
-
- do {
- // pop untraversed node
- stackpos--;
- const uint nodeIndex = stack[stackpos];
-
- BVHNode node = bvhNodeBuffer[nodeIndex];
- BVHAABB box = bvhAABBBuffer[nodeIndex];
-
- if (IntersectBox(ray, box))
- {
- //if (node.LeftChildIndex == 0 && node.RightChildIndex == 0)
- if (nodeIndex >= clusterCount - 1)
- {
- // Leaf node
- const uint nodeToClusterID = nodeIndex - leafNodeOffset;
- const uint clusterIndex = clusterIndexBuffer[nodeToClusterID];
- bool cullCluster = false;
-
- //// Compute per cluster visibility:
- //const ClusterCone cone = clusterConeBuffer[clusterIndex];
- //if (cone.valid)
- //{
- // const float3 testVec = normalize(ray.origin - cone.position);
- // if (dot(testVec, cone.direction) > cone.angleCos)
- // {
- // cullCluster = true;
- // }
- //}
-
- if (!cullCluster)
- {
- const uint2 cluster = clusterOffsetBuffer[clusterIndex];
- const uint triangleOffset = cluster.x;
- const uint triangleCount = cluster.y;
-
- for (uint tri = 0; tri < triangleCount; ++tri)
- {
- const uint primitiveID = triangleOffset + tri;
- IntersectTriangle(ray, bestHit, triangleBuffer[primitiveID], primitiveID);
- }
- }
- }
- else
- {
- // Internal node
- if (stackpos < stacksize - 1)
- {
- // push left child
- stack[stackpos] = node.LeftChildIndex;
- stackpos++;
- // push right child
- stack[stackpos] = node.RightChildIndex;
- stackpos++;
- }
- else
- {
- // stack overflow, terminate
- break;
- }
- }
-
- }
-
- } while (stackpos > 0);
-
-
- return bestHit;
-}
-
-inline float3 Shade(inout Ray ray, RayHit hit, inout float seed, in float2 pixel)
-{
- if (hit.distance < INFINITE_RAYHIT)
- {
- BVHMeshTriangle tri = triangleBuffer[hit.primitiveID];
-
- float u = hit.bary.x;
- float v = hit.bary.y;
- float w = 1 - u - v;
-
- float3 N = normalize(tri.n0 * w + tri.n1 * u + tri.n2 * v);
- float2 UV = tri.t0 * w + tri.t1 * u + tri.t2 * v;
-
- uint materialIndex = tri.materialIndex;
-
- TracedRenderingMaterial mat = materialBuffer[materialIndex];
-
- UV = frac(UV); // emulate wrap
- float4 baseColorMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV * mat.baseColorAtlasMulAdd.xy + mat.baseColorAtlasMulAdd.zw, 0);
- float4 surfaceMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV * mat.surfaceMapAtlasMulAdd.xy + mat.surfaceMapAtlasMulAdd.zw, 0);
- float4 normalMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV * mat.normalMapAtlasMulAdd.xy + mat.normalMapAtlasMulAdd.zw, 0);
-
- float4 baseColor = DEGAMMA(mat.baseColor * baseColorMap);
- float reflectance = mat.reflectance * surfaceMap.r;
- float metalness = mat.metalness * surfaceMap.g;
- float3 emissive = baseColor.rgb * mat.emissive * surfaceMap.b;
- float roughness = mat.roughness /** normalMap.a*/;
- float sss = mat.subsurfaceScattering;
-
- float3 albedo = ComputeAlbedo(baseColor, reflectance, metalness);
- float3 specular = ComputeF0(baseColor, reflectance, metalness);
-
-
- // Calculate chances of diffuse and specular reflection
- albedo = min(1.0f - specular, albedo);
- float specChance = dot(specular, 0.33);
- float diffChance = dot(albedo, 0.33);
- float inv = 1.0f / (specChance + diffChance);
- specChance *= inv;
- diffChance *= inv;
-
- // Roulette-select the ray's path
- float roulette = rand(seed, pixel);
- if (roulette < specChance)
- {
- // Specular reflection
- //float alpha = 150.0f;
- float alpha = sqr(1 - roughness) * 1000;
- ray.direction = SampleHemisphere(reflect(ray.direction, N), alpha, seed, pixel);
- float f = (alpha + 2) / (alpha + 1);
- ray.energy *= (1.0f / specChance) * specular * saturate(dot(N, ray.direction) * f);
- }
- else
- {
- // Diffuse reflection
- ray.direction = SampleHemisphere(N, 1.0f, seed, pixel);
- ray.energy *= (1.0f / diffChance) * albedo;
- }
-
- ray.origin = hit.position + N * EPSILON;
- ray.primitiveID = hit.primitiveID;
- ray.bary = hit.bary;
-
- return emissive;
- }
- else
- {
- // Erase the ray's energy - the sky doesn't reflect anything
- ray.energy = 0.0f;
-
- return GetDynamicSkyColor(ray.direction);
- }
-}
[numthreads(TRACEDRENDERING_TRACE_GROUPSIZE, 1, 1)]
void main( uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex )
diff --git a/WickedEngine/renderlightmapPS.hlsl b/WickedEngine/renderlightmapPS.hlsl
index 39b4f6dab..f772c8017 100644
--- a/WickedEngine/renderlightmapPS.hlsl
+++ b/WickedEngine/renderlightmapPS.hlsl
@@ -1,122 +1,31 @@
#include "globals.hlsli"
#include "tracedRenderingHF.hlsli"
-
-STRUCTUREDBUFFER(materialBuffer, TracedRenderingMaterial, TEXSLOT_ONDEMAND0);
-STRUCTUREDBUFFER(triangleBuffer, BVHMeshTriangle, TEXSLOT_ONDEMAND1);
-RAWBUFFER(clusterCounterBuffer, TEXSLOT_ONDEMAND2);
-STRUCTUREDBUFFER(clusterIndexBuffer, uint, TEXSLOT_ONDEMAND3);
-STRUCTUREDBUFFER(clusterOffsetBuffer, uint2, TEXSLOT_ONDEMAND4);
-STRUCTUREDBUFFER(clusterConeBuffer, ClusterCone, TEXSLOT_ONDEMAND5);
-STRUCTUREDBUFFER(bvhNodeBuffer, BVHNode, TEXSLOT_ONDEMAND6);
-STRUCTUREDBUFFER(bvhAABBBuffer, BVHAABB, TEXSLOT_ONDEMAND7);
-
-TEXTURE2D(materialTextureAtlas, float4, TEXSLOT_ONDEMAND8);
-
-inline bool TraceSceneANY(Ray ray, float maxDistance)
-{
- bool shadow = false;
-
- // Using BVH acceleration structure:
-
- // Emulated stack for tree traversal:
- const uint stacksize = 32;
- uint stack[stacksize];
- uint stackpos = 0;
-
- const uint clusterCount = clusterCounterBuffer.Load(0);
- const uint leafNodeOffset = clusterCount - 1;
-
- // push root node
- stack[stackpos] = 0;
- stackpos++;
-
- do {
- // pop untraversed node
- stackpos--;
- const uint nodeIndex = stack[stackpos];
-
- BVHNode node = bvhNodeBuffer[nodeIndex];
- BVHAABB box = bvhAABBBuffer[nodeIndex];
-
- if (IntersectBox(ray, box))
- {
- //if (node.LeftChildIndex == 0 && node.RightChildIndex == 0)
- if (nodeIndex >= clusterCount - 1)
- {
- // Leaf node
- const uint nodeToClusterID = nodeIndex - leafNodeOffset;
- const uint clusterIndex = clusterIndexBuffer[nodeToClusterID];
- bool cullCluster = false;
-
- //// Compute per cluster visibility:
- //const ClusterCone cone = clusterConeBuffer[clusterIndex];
- //if (cone.valid)
- //{
- // const float3 testVec = normalize(ray.origin - cone.position);
- // if (dot(testVec, cone.direction) > cone.angleCos)
- // {
- // cullCluster = true;
- // }
- //}
-
- if (!cullCluster)
- {
- const uint2 cluster = clusterOffsetBuffer[clusterIndex];
- const uint triangleOffset = cluster.x;
- const uint triangleCount = cluster.y;
-
- for (uint tri = 0; tri < triangleCount; ++tri)
- {
- const uint primitiveID = triangleOffset + tri;
- if (IntersectTriangleANY(ray, maxDistance, triangleBuffer[primitiveID]))
- {
- shadow = true;
- break;
- }
- }
- }
- }
- else
- {
- // Internal node
- if (stackpos < stacksize - 1)
- {
- // push left child
- stack[stackpos] = node.LeftChildIndex;
- stackpos++;
- // push right child
- stack[stackpos] = node.RightChildIndex;
- stackpos++;
- }
- else
- {
- // stack overflow, terminate
- break;
- }
- }
-
- }
-
- } while (!shadow && stackpos > 0);
-
- return shadow;
-}
-
-
+#include "raySceneIntersectHF.hlsli"
struct Input
{
float4 pos : SV_POSITION;
+ float2 uv : TEXCOORD;
float3 pos3D : WORLDPOSITION;
float3 normal : NORMAL;
};
float4 main(Input input) : SV_TARGET
{
- Ray ray = CreateRay(input.pos3D, input.normal);
+ float3 N = normalize(input.normal);
+ float2 uv = input.uv;
+ float seed = 12345;
+ float3 result = 0;
- bool hit = TraceSceneANY(ray, 100);
+ const uint iterations = 1024;
+ for (uint i = 0; i < iterations; ++i)
+ {
+ float3 direction = SampleHemisphere(N, 1.0f, seed, uv);
+ Ray ray = CreateRay(input.pos3D + direction * EPSILON, direction);
+ RayHit hit = TraceScene(ray);
+ result += ray.energy * Shade(ray, hit, seed, uv);
+ }
+ result /= (float)iterations;
- //return hit ? float4(1,1,0,1) : float4(1,0,0,1);
- return float4(input.pos3D * 0.1f, 1);
+ return float4(result, 1);
}
diff --git a/WickedEngine/renderlightmapVS.hlsl b/WickedEngine/renderlightmapVS.hlsl
index e8d5301ff..f5a9f50ed 100644
--- a/WickedEngine/renderlightmapVS.hlsl
+++ b/WickedEngine/renderlightmapVS.hlsl
@@ -11,6 +11,7 @@ struct Input
struct Output
{
float4 pos : SV_POSITION;
+ float2 uv : TEXCOORD;
float3 pos3D : WORLDPOSITION;
float3 normal : NORMAL;
};
@@ -21,10 +22,12 @@ Output main(Input input)
float4x4 WORLD = MakeWorldMatrixFromInstance(input.instance);
- output.pos = float4(input.atl.xy, 0, 1);
+ output.pos = float4(input.atl, 0, 1);
output.pos.xy = output.pos.xy * 2 - 1;
output.pos.y *= -1;
+ output.uv = input.atl;
+
output.pos3D = mul(float4(input.pos.xyz, 1), WORLD).xyz;
uint normal_wind_matID = asuint(input.pos.w);
diff --git a/WickedEngine/wiGPUBVH.cpp b/WickedEngine/wiGPUBVH.cpp
index be60acd3f..e27dd514d 100644
--- a/WickedEngine/wiGPUBVH.cpp
+++ b/WickedEngine/wiGPUBVH.cpp
@@ -398,7 +398,7 @@ void wiGPUBVH::Build(const Scene& scene, GRAPHICSTHREAD threadID)
wiProfiler::EndRange(threadID); // BVH rebuild
}
-void wiGPUBVH::Bind(GRAPHICSTHREAD threadID)
+void wiGPUBVH::Bind(SHADERSTAGE stage, GRAPHICSTHREAD threadID)
{
GraphicsDevice* device = wiRenderer::GetDevice();
@@ -411,7 +411,7 @@ void wiGPUBVH::Bind(GRAPHICSTHREAD threadID)
bvhNodeBuffer.get(),
bvhAABBBuffer.get(),
};
- device->BindResources(CS, res, TEXSLOT_ONDEMAND1, ARRAYSIZE(res), threadID);
+ device->BindResources(stage, res, TEXSLOT_ONDEMAND1, ARRAYSIZE(res), threadID);
}
diff --git a/WickedEngine/wiGPUBVH.h b/WickedEngine/wiGPUBVH.h
index c45b63f3c..e3f3ee852 100644
--- a/WickedEngine/wiGPUBVH.h
+++ b/WickedEngine/wiGPUBVH.h
@@ -25,7 +25,7 @@ public:
~wiGPUBVH();
void Build(const wiSceneSystem::Scene& scene, GRAPHICSTHREAD threadID);
- void Bind(GRAPHICSTHREAD threadID);
+ void Bind(wiGraphicsTypes::SHADERSTAGE stage, GRAPHICSTHREAD threadID);
static void LoadShaders();
diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp
index cac142d91..c246fd2cc 100644
--- a/WickedEngine/wiRenderer.cpp
+++ b/WickedEngine/wiRenderer.cpp
@@ -327,6 +327,25 @@ GFX_STRUCT InstancePrev
ALIGN_16
};
+GFX_STRUCT InstanceAtlas
+{
+ XMFLOAT4A atlasMulAdd;
+
+ InstanceAtlas(){}
+ InstanceAtlas(const XMFLOAT4& atlasRemap)
+ {
+ Create(atlasRemap);
+ }
+ inline void Create(const XMFLOAT4& atlasRemap) volatile
+ {
+ atlasMulAdd.x = atlasRemap.x;
+ atlasMulAdd.y = atlasRemap.y;
+ atlasMulAdd.z = atlasRemap.z;
+ atlasMulAdd.w = atlasRemap.w;
+ }
+
+ ALIGN_16
+};
Sampler* GetSampler(int slot)
@@ -1303,6 +1322,7 @@ void RenderMeshes(const RenderQueue& renderQueue, SHADERTYPE shaderType, UINT re
{
Instance instance;
InstancePrev instancePrev;
+ InstanceAtlas instanceAtlas;
};
const bool advancedVBRequest =
@@ -1387,6 +1407,7 @@ void RenderMeshes(const RenderQueue& renderQueue, SHADERTYPE shaderType, UINT re
const XMFLOAT4X4& prev_worldMatrix = instance.prev_transform_index >= 0 ? scene.prev_transforms[instance.prev_transform_index].world_prev : IDENTITYMATRIX;
((volatile InstBuf*)instances)[batchID].instancePrev.Create(prev_worldMatrix);
+ ((volatile InstBuf*)instances)[batchID].instanceAtlas.Create(instance.globalLightMapMulAdd);
}
else
{
@@ -1721,6 +1742,7 @@ void LoadShaders()
{ "MATIPREV", 0, FORMAT_R32G32B32A32_FLOAT, 4, APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 },
{ "MATIPREV", 1, FORMAT_R32G32B32A32_FLOAT, 4, APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 },
{ "MATIPREV", 2, FORMAT_R32G32B32A32_FLOAT, 4, APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 },
+ { "INSTANCEATLAS", 0, FORMAT_R32G32B32A32_FLOAT, 4, APPEND_ALIGNED_ELEMENT, INPUT_PER_INSTANCE_DATA, 1 },
};
vertexShaders[VSTYPE_OBJECT_COMMON] = static_cast(wiResourceManager::GetShaderManager().add(SHADERPATH + "objectVS_common.cso", wiResourceManager::VERTEXSHADER));
device->CreateInputLayout(layout, ARRAYSIZE(layout), &vertexShaders[VSTYPE_OBJECT_COMMON]->code, vertexLayouts[VLTYPE_OBJECT_ALL]);
@@ -5991,6 +6013,7 @@ void RefreshImpostors(GRAPHICSTHREAD threadID)
{
Instance instance;
InstancePrev instancePrev;
+ InstanceAtlas instanceAtlas;
};
for (size_t impostorID = 0; impostorID < min(maxImpostorCount, scene.impostors.GetCount()); ++impostorID)
@@ -6007,6 +6030,7 @@ void RefreshImpostors(GRAPHICSTHREAD threadID)
volatile InstBuf* buff = (volatile InstBuf*)device->AllocateFromRingBuffer(&dynamicVertexBufferPools[threadID], sizeof(InstBuf), instancesOffset, threadID);
buff->instance.Create(IDENTITYMATRIX);
buff->instancePrev.Create(IDENTITYMATRIX);
+ buff->instanceAtlas.Create(XMFLOAT4(1, 1, 0, 0));
device->InvalidateBufferAccess(&dynamicVertexBufferPools[threadID], threadID);
state_set = true;
@@ -7188,7 +7212,7 @@ void DrawTracedScene(const CameraComponent& camera, Texture2D* result, GRAPHICST
// Set up tracing resources:
- sceneBVH.Bind(threadID);
+ sceneBVH.Bind(CS, threadID);
GPUResource* res[] = {
tracedSceneParams.materialBuffer,
@@ -7483,20 +7507,117 @@ void ManageGlobalLightmapAtlas(GRAPHICSTHREAD threadID)
GraphicsDevice* device = GetDevice();
static Texture2D* atlasTexture = nullptr;
+ bool repackAtlas = false;
+ const int atlasClampBorder = 1;
+
+ using namespace wiRectPacker;
+ static unordered_map storedTextures;
Scene& scene = GetScene();
- if(!atlasTexture)
+ // Gather all object lightmap textures:
for (size_t i = 0; i < scene.objects.GetCount(); ++i)
{
- const ObjectComponent& object = scene.objects[i];
+ ObjectComponent& object = scene.objects[i];
if (!object.lightmapTextureData.empty())
{
- // TODO: proper packing, etc.
- HRESULT hr = wiTextureHelper::CreateTexture(atlasTexture, object.lightmapTextureData.data(), object.lightmapWidth, object.lightmapHeight, 4, FORMAT_R8G8B8A8_UNORM);
- assert(SUCCEEDED(hr));
- return;
+ if (object.lightmap == nullptr)
+ {
+ // Also create a GPU-side per object lighmap, so that copying into atlas can be done efficiently:
+ wiTextureHelper::CreateTexture(object.lightmap, object.lightmapTextureData.data(), object.lightmapWidth, object.lightmapHeight, 4, FORMAT_R8G8B8A8_UNORM);
+ }
+
+ if (storedTextures.find(object.lightmap) == storedTextures.end())
+ {
+ // we need to pack this lightmap texture into the atlas
+ rect_xywh newRect = rect_xywh(0, 0, object.lightmap->GetDesc().Width + atlasClampBorder * 2, object.lightmap->GetDesc().Height + atlasClampBorder * 2);
+ storedTextures[object.lightmap] = newRect;
+
+ repackAtlas = true;
+ }
+ }
+
+ }
+
+ // Update atlas texture if it is invalidated:
+ if (repackAtlas)
+ {
+ rect_xywh** out_rects = new rect_xywh*[storedTextures.size()];
+ int i = 0;
+ for (auto& it : storedTextures)
+ {
+ out_rects[i] = &it.second;
+ i++;
+ }
+
+ std::vector bins;
+ if (pack(out_rects, (int)storedTextures.size(), 16384, bins))
+ {
+ assert(bins.size() == 1 && "The regions won't fit into the texture!");
+
+ SAFE_DELETE(atlasTexture);
+
+ TextureDesc desc;
+ ZeroMemory(&desc, sizeof(desc));
+ desc.Width = (UINT)bins[0].size.w;
+ desc.Height = (UINT)bins[0].size.h;
+ desc.MipLevels = 0;
+ desc.ArraySize = 1;
+ desc.Format = FORMAT_R8G8B8A8_UNORM;
+ desc.SampleDesc.Count = 1;
+ desc.SampleDesc.Quality = 0;
+ desc.Usage = USAGE_DEFAULT;
+ desc.BindFlags = BIND_SHADER_RESOURCE | BIND_UNORDERED_ACCESS;
+ desc.CPUAccessFlags = 0;
+ desc.MiscFlags = 0;
+
+ atlasTexture = new Texture2D;
+ atlasTexture->RequestIndependentUnorderedAccessResourcesForMIPs(true);
+
+ device->CreateTexture2D(&desc, nullptr, &atlasTexture);
+
+ for (UINT mip = 0; mip < atlasTexture->GetDesc().MipLevels; ++mip)
+ {
+ for (auto& it : storedTextures)
+ {
+ if (mip < it.first->GetDesc().MipLevels)
+ {
+ CopyTexture2D(atlasTexture, mip, (it.second.x >> mip) + atlasClampBorder, (it.second.y >> mip) + atlasClampBorder, it.first, mip, threadID, BORDEREXPAND_CLAMP);
+ }
+ }
+ }
+ }
+ else
+ {
+ wiBackLog::post("Global Lightmap atlas packing failed!");
+ }
+
+ SAFE_DELETE_ARRAY(out_rects);
+ }
+
+ // Assign atlas buckets to objects:
+ for (size_t i = 0; i < scene.objects.GetCount(); ++i)
+ {
+ ObjectComponent& object = scene.objects[i];
+
+ if (object.lightmap != nullptr)
+ {
+ const TextureDesc& desc = atlasTexture->GetDesc();
+
+ rect_xywh rect = storedTextures[object.lightmap];
+
+ // eliminate border expansion:
+ rect.x += atlasClampBorder;
+ rect.y += atlasClampBorder;
+ rect.w -= atlasClampBorder * 2;
+ rect.h -= atlasClampBorder * 2;
+
+ object.globalLightMapMulAdd = XMFLOAT4((float)rect.w / (float)desc.Width, (float)rect.h / (float)desc.Height, (float)rect.x / (float)desc.Width, (float)rect.y / (float)desc.Height);
+ }
+ else
+ {
+ object.globalLightMapMulAdd = XMFLOAT4(0, 0, 0, 0);
}
}
@@ -7523,36 +7644,38 @@ void RenderObjectLightMap(ObjectComponent& object, GRAPHICSTHREAD threadID)
assert(!mesh.vertex_atlas.empty());
assert(mesh.vertexBuffer_ATL != nullptr);
+ UpdatePerFrameData(0);
+ BindPersistentState(threadID);
+
BuildSceneBVH(threadID);
- sceneBVH.Bind(threadID);
TracedSceneParams tracedSceneParams = PrepareTracedSceneResources(threadID);
GPUResource* res[] = {
tracedSceneParams.materialBuffer,
};
- device->BindResources(CS, res, TEXSLOT_ONDEMAND0, ARRAYSIZE(res), threadID);
+ device->BindResources(PS, res, TEXSLOT_ONDEMAND0, ARRAYSIZE(res), threadID);
if (tracedSceneParams.materialAtlas != nullptr)
{
- device->BindResource(CS, tracedSceneParams.materialAtlas, TEXSLOT_ONDEMAND8, threadID);
+ device->BindResource(PS, tracedSceneParams.materialAtlas, TEXSLOT_ONDEMAND8, threadID);
}
else
{
- device->BindResource(CS, wiTextureHelper::getWhite(), TEXSLOT_ONDEMAND8, threadID);
+ device->BindResource(PS, wiTextureHelper::getWhite(), TEXSLOT_ONDEMAND8, threadID);
}
+ sceneBVH.Bind(PS, threadID);
- // Create a temporary render target:
- Texture2D* rt = nullptr;
TextureDesc desc;
desc.Width = object.lightmapWidth;
desc.Height = object.lightmapHeight;
- desc.BindFlags = BIND_RENDER_TARGET;
+ desc.BindFlags = BIND_RENDER_TARGET | BIND_SHADER_RESOURCE;
desc.Format = FORMAT_R8G8B8A8_UNORM;
- device->CreateTexture2D(&desc, nullptr, &rt);
- device->BindRenderTargets(1, &rt, nullptr, threadID);
+ SAFE_DELETE(object.lightmap);
+ device->CreateTexture2D(&desc, nullptr, &object.lightmap);
+ device->BindRenderTargets(1, &object.lightmap, nullptr, threadID);
float clearColor[4] = { 0,0,0,1 };
- device->ClearRenderTarget(rt, clearColor, threadID);
+ device->ClearRenderTarget(object.lightmap, clearColor, threadID);
ViewPort vp;
vp.Width = (float)desc.Width;
@@ -7589,6 +7712,8 @@ void RenderObjectLightMap(ObjectComponent& object, GRAPHICSTHREAD threadID)
device->DrawIndexedInstanced((int)mesh.indices.size(), 1, 0, 0, 0, threadID);
+ device->BindRenderTargets(0, nullptr, nullptr, threadID);
+
// Now download the rendered lightmap from GPU and store it inside the object:
@@ -7610,10 +7735,9 @@ void RenderObjectLightMap(ObjectComponent& object, GRAPHICSTHREAD threadID)
HRESULT hr = device->CreateTexture2D(&staging_desc, nullptr, &stagingTex);
assert(SUCCEEDED(hr));
- bool download_success = device->DownloadResource(rt, stagingTex, object.lightmapTextureData.data(), GRAPHICSTHREAD_IMMEDIATE);
+ bool download_success = device->DownloadResource(object.lightmap, stagingTex, object.lightmapTextureData.data(), GRAPHICSTHREAD_IMMEDIATE);
assert(download_success);
- delete rt;
delete stagingTex;
}
diff --git a/WickedEngine/wiSceneSystem.h b/WickedEngine/wiSceneSystem.h
index 2bcb01d13..f9ec49e85 100644
--- a/WickedEngine/wiSceneSystem.h
+++ b/WickedEngine/wiSceneSystem.h
@@ -409,6 +409,7 @@ namespace wiSceneSystem
// Non-serialized attributes:
XMFLOAT4 globalLightMapMulAdd = XMFLOAT4(0, 0, 0, 0);
+ wiGraphicsTypes::Texture2D* lightmap = nullptr;
XMFLOAT3 center = XMFLOAT3(0, 0, 0);
float impostorFadeThresholdRadius;
diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp
index acd20db99..981376f99 100644
--- a/WickedEngine/wiVersion.cpp
+++ b/WickedEngine/wiVersion.cpp
@@ -9,7 +9,7 @@ namespace wiVersion
// minor features, major updates
const int minor = 24;
// minor bug fixes, alterations, refactors, updates
- const int revision = 0;
+ const int revision = 1;
long GetVersion()