Skip to content

Commit

Permalink
Shading, static lights, lightmaps
Browse files Browse the repository at this point in the history
- rename SpatialCache to VertLookupTree
- pass colored light (float3) to pixel shader instead of greyscale light (float)
- use colored per-vertex light for shading instead of averaging to greyscale

Lightmap Shading:
- enable primitive lightmap GPU shading
- fix texture arrray SRGB flag not working
- fix lightmaps not using SRGB flag
- fix lightmap mipmap generation
- manually set lightmap sampling mipmap level to get smoother light
- fix solid shading mode not showing lightmaps

VOB static light:
- split getLightStaticAtPos into getGroundFaceAtPos and interpolateColor
- use groundFace existance to find lightmaps of groundface to implement static light from vobLights for VOBs in lightmapped rooms:
  - load static lights from ZEN
  - use spatial lookup tree to find static lights near VOBs und use them for static light shading
  - new debug flag for vob positions
  • Loading branch information
Katharsas committed Apr 11, 2024
1 parent d3cdeab commit 6cdd6eb
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 67 deletions.
53 changes: 33 additions & 20 deletions ZenRen/Shaders/mainPass.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct VS_OUT
float2 uvBaseColor : TEXCOORD0;
float3 uvLightmap : TEXCOORD1;
float4 color : COLOR;
float light : LIGHT_INTENSITY;
float3 light : LIGHT_INTENSITY;
//float4 normal : NORMAL;
float4 position : SV_POSITION;
};
Expand Down Expand Up @@ -74,17 +74,17 @@ VS_OUT VS_Main(VS_IN input)
lightReceivedRatio = (lightNormalDotProduct + lightRatioAt90) / (1 + lightRatioAt90);
}

float ambientLight2 = 0.14f; // TODO why is ambientLight even customizable outside of shader? needs to be balanced with lightRatioAt90 anyway
lightReceivedRatio = (lightReceivedRatio + ambientLight2) / (1 + ambientLight2);
float ambientLight = 0.14f; // TODO why is ambientLight even customizable outside of shader? needs to be balanced with lightRatioAt90 anyway
lightReceivedRatio = (lightReceivedRatio + ambientLight) / (1 + ambientLight);

//float lightReceivedRatio = max(0, lightNormalDotProduct + 0.3f) + ambientLight;
float sunStrength = 1.7f;
float staticStrength = 2.f;
float staticLightAverage = (input.colLight.r + input.colLight.g + input.colLight.b) / 3.0f;
output.light = ((lightReceivedRatio * sunStrength) * 0.5f) + ((staticLightAverage * staticStrength) * 0.5f);
//float staticLightAverage = (input.colLight.r + input.colLight.g + input.colLight.b) / 3.0f;
output.light = (((float3) lightReceivedRatio * sunStrength) * 0.5f) + ((input.colLight * staticStrength) * 0.5f);
}
else {
output.light = 1;
output.light = (float3) 1;
}

output.position = mul(viewPosition, projectionMatrix);
Expand All @@ -104,15 +104,6 @@ VS_OUT VS_Main(VS_IN input)
// Pixel Shader
//--------------------------------------------------------------------------------------

float CalcMipLevel(float2 texCoord)
{
float2 dx = ddx(texCoord);
float2 dy = ddy(texCoord);
float delta_max_sqr = max(dot(dx, dx), dot(dy, dy));

return max(0.0, 0.5 * log2(delta_max_sqr));
}

Texture2D baseColor : register(s0);
Texture2DArray lightmaps : register(s1);
SamplerState SampleType : register(s0);
Expand All @@ -122,19 +113,41 @@ struct PS_IN
float2 uvBaseColor : TEXCOORD0;
float3 uvLightmap : TEXCOORD1;
float4 color : COLOR;
float light : LIGHT_INTENSITY;
float3 light : LIGHT_INTENSITY;
//int indexLightmap : INDEX_LIGHTMAP;
//float4 normal : NORMAL;
};

float CalcMipLevel(float2 texCoord)
{
float2 dx = ddx(texCoord);
float2 dy = ddy(texCoord);
float delta_max_sqr = max(dot(dx, dx), dot(dy, dy));

return max(0.0, 0.5 * log2(delta_max_sqr));
}

float4 SampleLightmap(float3 uvLightmap)
{
// TODO
// - We should probably use a LOD/mipmap bias, not a fixed mimap level, does SampleLevel do that?
// - How does Gothic get the smoothed look? any mipmap multisampling trickery?

//return lightmaps.Sample(SampleType, uvLightmap);
return lightmaps.SampleLevel(SampleType, uvLightmap, 0.5f);
}

float4 PS_Main(PS_IN input) : SV_TARGET
{
// light color
float4 lightColor = float4(float3(1, 1, 1) * input.light, 1);
float4 lightColor = float4(input.light, 1);

// albedo color
float4 albedoColor;
if (!outputDirectEnabled || outputDirectType == FLAG_OUTPUT_DIRECT_DIFFUSE) {
if (input.uvLightmap.z >= 0) /* dynamic branch */ {
lightColor = SampleLightmap(input.uvLightmap);
}
albedoColor = baseColor.Sample(SampleType, input.uvBaseColor);

// Alpha Cutoff
Expand All @@ -146,7 +159,7 @@ float4 PS_Main(PS_IN input) : SV_TARGET
// since non-blended transparency fades out on higher mips more than it should (potentially leading to disappearing vegetation), correct it
float texWidth;
float texHeight;
baseColor.GetDimensions(texWidth, texHeight);
baseColor.GetDimensions(texWidth, texHeight);// TODO this info could be passed with cb
float distAlphaMipScale = 0.25f;
albedoColor.a *= 1 + CalcMipLevel(input.uvBaseColor * float2(texWidth, texHeight)) * distAlphaMipScale;
}
Expand All @@ -161,9 +174,9 @@ float4 PS_Main(PS_IN input) : SV_TARGET
// if sharpening is used, the alphaCutoff its pretty much irrelevant here (we could just use 0.5)
clip(albedoColor.a < alphaCutoff ? -1 : 1);
}
else if (outputDirectType == FLAG_OUTPUT_DIRECT_LIGHTMAP) {
else if (outputDirectType == FLAG_OUTPUT_DIRECT_SOLID || outputDirectType == FLAG_OUTPUT_DIRECT_LIGHTMAP) {
if (input.uvLightmap.z >= 0) /* dynamic branch */ {
albedoColor = lightmaps.Sample(SampleType, input.uvLightmap);
albedoColor = SampleLightmap(input.uvLightmap);
}
else {
albedoColor = input.color;
Expand Down
3 changes: 1 addition & 2 deletions ZenRen/src/renderer/PipelineWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,7 @@ namespace renderer::world {
Texture* texture = new Texture(d3d, lightmap.ddsRaw, false, "lightmap_xxx");
debugTextures.push_back(texture);
}
// TODO it is totally unclear if these should be loaded as sRGB or linear!
lightmapTexArray = createShaderTexArray(d3d, ddsRaws, 256, 256, false);
lightmapTexArray = createShaderTexArray(d3d, ddsRaws, 256, 256, true);
}

world.meshes.clear();
Expand Down
8 changes: 8 additions & 0 deletions ZenRen/src/renderer/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ namespace renderer
D3DXCOLOR colLightStatic;
};

struct Light {
// TODO falloff and type surely play a role
VEC3 pos;
bool isStatic;
D3DXCOLOR color;
float range;
};

struct InMemoryTexFile {
int32_t width;
int32_t height;
Expand Down
9 changes: 7 additions & 2 deletions ZenRen/src/renderer/RendererCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,13 @@ namespace renderer
const VEC_VERTEX_DATA& get(const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData) const {
return meshData.find(*this->mat)->second;
}
const VERTEX_POS& getPos(const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData) const {
return meshData.find(*this->mat)->second.vecPos[this->vertIndex];
const std::array<VERTEX_POS, 3> getPos(const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData) const {
auto& vertData = meshData.find(*this->mat)->second.vecPos;
return { vertData[this->vertIndex], vertData[this->vertIndex + 1], vertData[this->vertIndex + 2] };
}
const std::array<VERTEX_OTHER, 3> getOther(const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData) const {
auto& vertData = meshData.find(*this->mat)->second.vecNormalUv;
return { vertData[this->vertIndex], vertData[this->vertIndex + 1], vertData[this->vertIndex + 2] };
}
};

Expand Down
25 changes: 14 additions & 11 deletions ZenRen/src/renderer/Texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace renderer {
const TexMetadata& metadata = image->GetMetadata();
if (metadata.width != width || metadata.height != height) {

// resize dicards any images after the first (resulting file will have no mipmaps)!
// resize discards any images after the first (resulting file will have no mipmaps)!
ScratchImage imageResized;
auto hr = Resize(*image->GetImage(0, 0, 0), width, height, DirectX::TEX_FILTER_DEFAULT, imageResized);
throwOnError(hr, name);
Expand Down Expand Up @@ -81,6 +81,11 @@ namespace renderer {
}

ID3D11ShaderResourceView* createShaderTexArray(D3d d3d, std::vector<std::vector<uint8_t>>& ddsRaws, int32_t width, int32_t height, bool sRgb, bool noMip) {
// SRGB:
// Lightmaps come in the format DXGI_FORMAT_B5G6R5_UNORM (85), which is a 16bpp format.
// There is not SRGB variant for this format, so if we use it as is, the CREATETEX_FORCE_SRGB flag will fail silently.
// To fix that, we convert all 16BPP formats to more standard formats on DDS conversion to make sRGB flag work in all cases.

std::vector<DirectX::ScratchImage*> imageOwners;
std::vector<DirectX::Image> images;

Expand All @@ -90,27 +95,24 @@ namespace renderer {
for (auto& ddsRaw : ddsRaws) {
std::string name = "lightmap_" + std::format("{:03}", i);
DirectX::ScratchImage* image = new DirectX::ScratchImage();
auto hr = DirectX::LoadFromDDSMemory(ddsRaw.data(), ddsRaw.size(), DirectX::DDS_FLAGS::DDS_FLAGS_NONE, &metadata, *image);
auto hr = DirectX::LoadFromDDSMemory(ddsRaw.data(), ddsRaw.size(), DirectX::DDS_FLAGS::DDS_FLAGS_NO_16BPP, &metadata, *image);
throwOnError(hr, name);

resizeIfOtherSize(image, width, height, name);
if (!noMip) {
// TODO it is unclear if Mimaps are needed since the lightmaps details are extremely low frequency and much likely will never noticably alias unless running at extremely low resolution
createMipmapsIfMissing(image, name);
metadata = image->GetMetadata();// update metadata.mipLevels
}

imageOwners.push_back(image);
images.push_back(*image->GetImages());
for (int m = 0; m < metadata.mipLevels; m++) {
images.push_back(*image->GetImage(m, 0, 0));
}
i++;
}
metadata.arraySize = i;

metadata.arraySize = images.size();

// TODO
// It is unclear if MipMaps work here.
// We should instead write Image to DDS raw memory (see https://stackoverflow.com/questions/76144325/convert-any-input-dds-into-another-dds-format-in-directxtex)
// and then load raw memory with CreateDDSTextureFromMemory (see https://github.com/Microsoft/DirectXTK/wiki/DDSTextureLoader)

// TODO Maybe we should just create a DX Texture object and do the SRV ourselves.
ID3D11ShaderResourceView* resourceView;
auto sRgbFlag = sRgb ? DirectX::CREATETEX_FORCE_SRGB : DirectX::CREATETEX_IGNORE_SRGB;
auto hr = DirectX::CreateShaderResourceViewEx(
Expand All @@ -127,6 +129,7 @@ namespace renderer {
}

void createSetSrv(D3d d3d, const ScratchImage& image, const std::string& name, bool sRgb, ID3D11ShaderResourceView** ppSrv) {
// TODO Maybe we should just create a DX Texture object and do the SRV ourselves.
auto sRgbFlag = sRgb ? DirectX::CREATETEX_FORCE_SRGB : DirectX::CREATETEX_IGNORE_SRGB;
auto hr = DirectX::CreateShaderResourceViewEx(
d3d.device, image.GetImages(), image.GetImageCount(), image.GetMetadata(),
Expand Down
10 changes: 3 additions & 7 deletions ZenRen/src/renderer/loader/StaticLightFromGroundFace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,15 @@ namespace renderer::loader
return colorAverage;
}

std::optional<D3DXCOLOR> getLightStaticAtPos(const XMVECTOR pos, const unordered_map<Material, VEC_VERTEX_DATA>& meshData, const SpatialCache& spatialCache)
std::optional<VertKey> getGroundFaceAtPos(const XMVECTOR pos, const unordered_map<Material, VEC_VERTEX_DATA>& meshData, const VertLookupTree& vertLookup)
{
VEC3 pos3 = toVec3(pos);
vector<VertKey> vertKeys;
if (useNaiveSlowGroundFaceSearch) {
vertKeys = rayDownIntersectedNaive(meshData, pos3, 100);
}
else {
vertKeys = rayDownIntersected(spatialCache, pos3, 100);
vertKeys = rayDownIntersected(vertLookup, pos3, 100);
}
vector<VERTEX_POS> belowVerts;
for (auto& vertKey : vertKeys) {
Expand All @@ -162,11 +162,7 @@ namespace renderer::loader
return std::nullopt;
}
else {
D3DXCOLOR staticLight = interpolateColor(pos3, meshData, vertKeys[closestIndex]);
if (tintVobStaticLight) {
staticLight = D3DXCOLOR((staticLight.r / 3.f) * 2.f, staticLight.g, staticLight.b, staticLight.a);
}
return staticLight;
return vertKeys[closestIndex];
}
}
}
3 changes: 2 additions & 1 deletion ZenRen/src/renderer/loader/StaticLightFromGroundFace.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace renderer::loader
{
std::optional<D3DXCOLOR> getLightStaticAtPos(const DirectX::XMVECTOR pos, const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData, const SpatialCache& spatialCache);
D3DXCOLOR interpolateColor(const VEC3& pos, const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData, const VertKey& vertKey);
std::optional<VertKey> getGroundFaceAtPos(const DirectX::XMVECTOR pos, const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData, const VertLookupTree& vertLookup);
}

12 changes: 6 additions & 6 deletions ZenRen/src/renderer/loader/VertPosLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ namespace renderer::loader
maxY = std::max(maxY, vert.y);
maxZ = std::max(maxZ, vert.z);
}
return OrthoTree::BoundingBoxND<3, float>{ {minX, minY, minZ}, {maxX, maxY, maxZ} };
return OrthoTree::BoundingBoxND<3, float>{ {minX, minY, minZ}, {maxX, maxY, maxZ} };
}

SpatialCache createSpatialCache(const unordered_map<Material, VEC_VERTEX_DATA>& meshData)
VertLookupTree createVertLookup(const unordered_map<Material, VEC_VERTEX_DATA>& meshData)
{
vector<OrthoBoundingBox3D> bboxes;

SpatialCache result;
VertLookupTree result;
uint32_t bbIndex = 0;
for (auto& it : meshData) {
auto& mat = it.first;
Expand All @@ -49,22 +49,22 @@ namespace renderer::loader
return result;
}

vector<VertKey> rayDownIntersected(const SpatialCache& cache, const VEC3& pos, float searchSizeY)
vector<VertKey> rayDownIntersected(const VertLookupTree& lookup, const VEC3& pos, float searchSizeY)
{
auto searchBox = OrthoBoundingBox3D{
{ pos.x - rayIntersectTolerance, pos.y - searchSizeY, pos.z - rayIntersectTolerance },
{ pos.x + rayIntersectTolerance, pos.y, pos.z + rayIntersectTolerance }
};

constexpr bool shouldFullyContain = false;
auto intersectedBoxesSorted = cache.tree.RangeSearch<shouldFullyContain>(searchBox);
auto intersectedBoxesSorted = lookup.tree.RangeSearch<shouldFullyContain>(searchBox);

// TODO while RayIntersectedAll should be equivalent from what i understand, it is missing most bboxes that RangeSearch finds
//auto intersectedBoxesSorted = cache.tree.RayIntersectedAll({ pos.x, pos.y, pos.z }, { 0, -1, 0 }, rayIntersectTolerance, searchSizeY);

vector<VertKey> result;
for (auto id : intersectedBoxesSorted) {
const auto& vertKey = cache.bboxIndexToVert.find(id)->second;
const auto& vertKey = lookup.bboxIndexToVert.find(id)->second;
result.push_back(vertKey);
}
return result;
Expand Down
6 changes: 3 additions & 3 deletions ZenRen/src/renderer/loader/VertPosLookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ namespace renderer::loader
// we don't have to provide them separately on each search by storing them in SpatialCache.
using OrthoOctree = OrthoTree::TreeBoxContainerND<3, 2, float>;

struct SpatialCache {
struct VertLookupTree {
std::unordered_map<uint32_t, VertKey> bboxIndexToVert;
OrthoOctree tree;
};

SpatialCache createSpatialCache(const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData);
std::vector<VertKey> rayDownIntersected(const SpatialCache& cache, const VEC3& pos, float searchSizeY);
VertLookupTree createVertLookup(const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData);
std::vector<VertKey> rayDownIntersected(const VertLookupTree& lookup, const VEC3& pos, float searchSizeY);
std::vector<VertKey> rayDownIntersectedNaive(const std::unordered_map<Material, VEC_VERTEX_DATA>& meshData, const VEC3& pos, float searchSizeY);
}

Loading

0 comments on commit 6cdd6eb

Please sign in to comment.