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:
parent
7e352bb33e
commit
4be0627f2e
7 changed files with 222 additions and 5 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
146
src/render/textureutil.cpp
Normal 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
55
src/render/textureutil.h
Normal 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
|
Loading…
Reference in a new issue