fix: Fix cube map side order and orientation

- Swap +x and -x faces
- Rotate individual cube faces to match what the original engine does
This commit is contained in:
Vsevolod Kremianskii 2021-01-14 21:51:19 +07:00
parent 7e352bb33e
commit 4be0627f2e
7 changed files with 222 additions and 5 deletions

View file

@ -162,6 +162,7 @@ set(RENDER_HEADERS
src/render/shaders.h
src/render/texture.h
src/render/textures.h
src/render/textureutil.h
src/render/types.h
src/render/util.h
src/render/walkmesh.h
@ -194,6 +195,7 @@ set(RENDER_SOURCES
src/render/shaders.cpp
src/render/texture.cpp
src/render/textures.cpp
src/render/textureutil.cpp
src/render/util.cpp
src/render/walkmesh.cpp
src/render/walkmeshes.cpp

View file

@ -17,6 +17,8 @@
#include "tgafile.h"
#include "../textureutil.h"
using namespace std;
namespace reone {
@ -112,9 +114,14 @@ void TgaFile::loadTexture() {
layers.push_back(move(layer));
}
PixelFormat format = _alpha ? PixelFormat::BGRA : PixelFormat::BGR;
if (cubeMap) {
prepareCubeMap(layers, format, format);
}
_texture = make_shared<Texture>(_resRef, _texType, _width, _height);
_texture->init();
_texture->setPixels(move(layers), _alpha ? PixelFormat::BGRA : PixelFormat::BGR);
_texture->setPixels(move(layers), format);
}
shared_ptr<Texture> TgaFile::texture() const {

View file

@ -21,6 +21,8 @@
#include "../../common/streamutil.h"
#include "../textureutil.h"
#include "txifile.h"
using namespace std;
@ -135,11 +137,16 @@ void TpcFile::loadTexture() {
features = txi.features();
}
PixelFormat format = getPixelFormat();
if (_cubeMap) {
prepareCubeMap(layers, format, format);
}
_texture = make_shared<Texture>(_resRef, _type, _width, _height, _headless);
if (!_headless) {
_texture->init();
}
_texture->setPixels(move(layers), getPixelFormat());
_texture->setPixels(move(layers), format);
_texture->setFeatures(move(features));
}

View file

@ -701,9 +701,8 @@ unique_ptr<ModelMesh> MdlFile::readMesh() {
offsets.texCoords1 = mdxTextureOffset != 0xffffffff ? mdxTextureOffset : -1;
offsets.texCoords2 = mdxLightmapOffset != 0xffffffff ? mdxLightmapOffset : -1;
if (mdxTanSpaceOffset != 0xffffffff) {
offsets.bitangents = mdxTanSpaceOffset + 0 * sizeof(float);
offsets.tangents = mdxTanSpaceOffset + 3 * sizeof(float);
offsets.bitangents = mdxTanSpaceOffset + 0;
offsets.normals = mdxTanSpaceOffset + 6 * sizeof(float);
}
offsets.stride = mdxVertexSize;

View file

@ -286,6 +286,7 @@ in vec3 fragNormal;
in vec2 fragTexCoords;
in vec2 fragLightmapCoords;
in mat3 fragTanSpace;
in vec3 fragTanSpaceNormal;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 fragColorBright;
@ -335,7 +336,7 @@ void applyLighting(vec3 normal, float shadow, inout vec3 color) {
vec3 diffuse = uLights[i].color.rgb * diff * color * attenuation;
vec3 specular = uLights[i].color.rgb * spec * color * attenuation;
result += (1.0 - shadow) * (diffuse + specular);
result += (1.0 - 0.5 * shadow) * (diffuse + specular);
}
color = ambient + result;

146
src/render/textureutil.cpp Normal file
View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "textureutil.h"
#include <stdexcept>
#include "s3tc.h"
using namespace std;
namespace reone {
namespace render {
static bool isCompressed(PixelFormat format) {
return format == PixelFormat::DXT1 || format == PixelFormat::DXT5;
}
static int getBitsPerPixel(PixelFormat format) {
switch (format) {
case PixelFormat::Grayscale:
return 1;
case PixelFormat::RGB:
case PixelFormat::BGR:
return 3;
case PixelFormat::RGBA:
case PixelFormat::BGRA:
return 4;
default:
throw invalid_argument("Unsupported pixel format: " + to_string(static_cast<int>(format)));
}
}
void prepareCubeMap(vector<Texture::Layer> &layers, PixelFormat srcFormat, PixelFormat &destFormat) {
static int rotations[] = { 1, 3, 0, 2, 2, 0 };
int layerCount = static_cast<int>(layers.size());
if (layerCount != kNumCubeFaces) {
throw invalid_argument("layer count is invalid");
}
swap(layers[0], layers[1]);
destFormat = srcFormat;
bool compressed = isCompressed(srcFormat);
for (int i = 0; i < kNumCubeFaces; ++i) {
Texture::Layer &layer = layers[i];
if (layer.mipMaps.empty()) {
throw invalid_argument("layer has no mip maps: " + to_string(i));
}
// Cube maps only ever use the base mip map level
Texture::MipMap &mipMap = layer.mipMaps.front();
if (compressed) {
decompressMipMap(mipMap, srcFormat, destFormat);
}
for (int j = 0; j < rotations[i]; ++j) {
rotateMipMap90(mipMap, getBitsPerPixel(destFormat));
}
layer.mipMaps.erase(layer.mipMaps.begin() + 1, layer.mipMaps.end());
}
}
void decompressMipMap(Texture::MipMap &mipMap, PixelFormat srcFormat, PixelFormat &destFormat) {
if (!isCompressed(srcFormat)) {
throw invalid_argument("format must be either DXT1 or DXT5");
}
const uint8_t *srcMipMapData = reinterpret_cast<const uint8_t *>(&mipMap.data[0]);
size_t pixelCount = static_cast<size_t>(mipMap.width) * mipMap.height;
vector<unsigned long> pixels(pixelCount);
unsigned long *pixelsPtr = &pixels[0];
bool alpha;
if (srcFormat == PixelFormat::DXT5) {
BlockDecompressImageDXT5(mipMap.width, mipMap.height, srcMipMapData, pixelsPtr);
alpha = true;
} else {
BlockDecompressImageDXT1(mipMap.width, mipMap.height, srcMipMapData, pixelsPtr);
alpha = false;
}
mipMap.data.resize((alpha ? 4ll : 3ll) * pixelCount);
pixelsPtr = &pixels[0];
uint8_t *destMipMapData = reinterpret_cast<uint8_t *>(&mipMap.data[0]);
for (int i = 0; i < mipMap.width * mipMap.height; ++i) {
unsigned long pixel = *(pixelsPtr++);
*(destMipMapData++) = (pixel >> 24) & 0xff;
*(destMipMapData++) = (pixel >> 16) & 0xff;
*(destMipMapData++) = (pixel >> 8) & 0xff;
if (alpha) {
*(destMipMapData++) = pixel & 0xff;
}
}
destFormat = alpha ? PixelFormat::RGBA : PixelFormat::RGB;
}
void rotateMipMap90(Texture::MipMap &mipMap, int bpp) {
if (mipMap.width != mipMap.height) {
throw invalid_argument("mipMap size is invalid");
}
size_t n = mipMap.width;
size_t w = n / 2;
size_t h = (n + 1) / 2;
uint8_t *mipMapData = reinterpret_cast<uint8_t *>(&mipMap.data[0]);
for (size_t x = 0; x < w; ++x) {
for (size_t y = 0; y < h; ++y) {
const size_t d0 = ( y * n + x ) * bpp;
const size_t d1 = ((n - 1 - x) * n + y ) * bpp;
const size_t d2 = ((n - 1 - y) * n + (n - 1 - x)) * bpp;
const size_t d3 = ( x * n + (n - 1 - y)) * bpp;
for (size_t p = 0; p < static_cast<size_t>(bpp); ++p) {
uint8_t tmp = mipMapData[d0 + p];
mipMapData[d0 + p] = mipMapData[d1 + p];
mipMapData[d1 + p] = mipMapData[d2 + p];
mipMapData[d2 + p] = mipMapData[d3 + p];
mipMapData[d3 + p] = tmp;
}
}
}
}
} // namespace render
} // namespace reone

55
src/render/textureutil.h Normal file
View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "texture.h"
namespace reone {
namespace render {
/**
* KotOR and TSL cube maps require specific transformations before use.
* Allegedly, this is what the original engine does.
*
* @param layers texture layers to process
* @param srcFormat source pixel format
* @param destFormat resulting pixel format (output parameter)
*/
void prepareCubeMap(std::vector<Texture::Layer> &layers, PixelFormat srcFormat, PixelFormat &destFormat);
/**
* Decompresses the mip map.
*
* @param mipMap mip map to decompress
* @param srcFormat source pixel format - must be either DXT1 or DXT5
* @param destFormat pixel format of a decompressed mip map (output parameter)
*/
void decompressMipMap(Texture::MipMap &mipMap, PixelFormat srcFormat, PixelFormat &destFormat);
/**
* Rotates the mip map by 90 degrees.
*
* @param mipMap mip map to rotate - must be uncompressed
* @param bpp number of bytes per pixel
*/
void rotateMipMap90(Texture::MipMap &mipMap, int bpp);
} // namespace render
} // namespace reone