Files
filament/libs/ibl/src/Cubemap.cpp
2019-06-28 14:49:40 -07:00

220 lines
8.0 KiB
C++

/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <ibl/Cubemap.h>
using namespace filament::math;
namespace filament {
namespace ibl {
Cubemap::Cubemap(size_t dim) {
resetDimensions(dim);
}
Cubemap::~Cubemap() = default;
size_t Cubemap::getDimensions() const {
return mDimensions;
}
void Cubemap::resetDimensions(size_t dim) {
mDimensions = dim;
mScale = 2.0f / dim;
mUpperBound = std::nextafter((float) mDimensions, 0.0f);
for (auto& mFace : mFaces) {
mFace.reset();
}
}
void Cubemap::setImageForFace(Face face, const Image& image) {
mFaces[size_t(face)].set(image);
}
Cubemap::Address Cubemap::getAddressFor(const float3& r) {
Cubemap::Address addr;
float sc, tc, ma;
const float rx = std::abs(r.x);
const float ry = std::abs(r.y);
const float rz = std::abs(r.z);
if (rx >= ry && rx >= rz) {
ma = 1.0f / rx;
if (r.x >= 0) {
addr.face = Face::PX;
sc = -r.z;
tc = -r.y;
} else {
addr.face = Face::NX;
sc = r.z;
tc = -r.y;
}
} else if (ry >= rx && ry >= rz) {
ma = 1.0f / ry;
if (r.y >= 0) {
addr.face = Face::PY;
sc = r.x;
tc = r.z;
} else {
addr.face = Face::NY;
sc = r.x;
tc = -r.z;
}
} else {
ma = 1.0f / rz;
if (r.z >= 0) {
addr.face = Face::PZ;
sc = r.x;
tc = -r.y;
} else {
addr.face = Face::NZ;
sc = -r.x;
tc = -r.y;
}
}
// ma is guaranteed to be >= sc and tc
addr.s = (sc * ma + 1.0f) * 0.5f;
addr.t = (tc * ma + 1.0f) * 0.5f;
return addr;
}
void Cubemap::makeSeamless() {
size_t dim = getDimensions();
size_t D = dim;
// here we assume that all faces share the same underlying image
const size_t bpr = getImageForFace(Face::NX).getBytesPerRow();
const size_t bpp = getImageForFace(Face::NX).getBytesPerPixel();
auto getTexel = [](Image& image, ssize_t x, ssize_t y) -> Texel* {
return (Texel*)((uint8_t*)image.getData() + x * image.getBytesPerPixel() + y * image.getBytesPerRow());
};
auto stitch = [ & ](
Face faceDst, ssize_t xdst, ssize_t ydst, size_t incDst,
Face faceSrc, size_t xsrc, size_t ysrc, ssize_t incSrc) {
Image& imageDst = getImageForFace(faceDst);
Image& imageSrc = getImageForFace(faceSrc);
Texel* dst = getTexel(imageDst, xdst, ydst);
Texel* src = getTexel(imageSrc, xsrc, ysrc);
for (size_t i = 0; i < dim; ++i) {
*dst = *src;
dst = (Texel*)((uint8_t*)dst + incDst);
src = (Texel*)((uint8_t*)src + incSrc);
}
};
auto corners = [ & ](Face face) {
size_t L = D - 1;
Image& image = getImageForFace(face);
*getTexel(image, -1, -1) = (*getTexel(image, 0, 0) + *getTexel(image, -1, 0) + *getTexel(image, 0, -1)) / 3;
*getTexel(image, L+1, -1) = (*getTexel(image, L, 0) + *getTexel(image, L, -1) + *getTexel(image, L+1, 0)) / 3;
*getTexel(image, -1, L+1) = (*getTexel(image, 0, L) + *getTexel(image, -1, L) + *getTexel(image, 0, L+1)) / 3;
*getTexel(image, L+1, L+1) = (*getTexel(image, L, L) + *getTexel(image, L+1, L) + *getTexel(image, L+1, L)) / 3;
};
// +Y / Top
stitch( Face::PY, -1, 0, bpr, Face::NX, 0, 0, bpp); // left
stitch( Face::PY, 0, -1, bpp, Face::NZ, D-1, 0, -bpp); // top
stitch( Face::PY, D, 0, bpr, Face::PX, D-1, 0, -bpp); // right
stitch( Face::PY, 0, D, bpp, Face::PZ, 0, 0, bpp); // bottom
corners(Face::PY);
// -X / Left
stitch( Face::NX, -1, 0, bpr, Face::NZ, D-1, 0, bpr); // left
stitch( Face::NX, 0, -1, bpp, Face::PY, 0, 0, bpr); // top
stitch( Face::NX, D, 0, bpr, Face::PZ, 0, 0, bpr); // right
stitch( Face::NX, 0, D, bpp, Face::NY, 0, D-1, -bpr); // bottom
corners(Face::NX);
// +Z / Front
stitch( Face::PZ, -1, 0, bpr, Face::NX, D-1, 0, bpr); // left
stitch( Face::PZ, 0, -1, bpp, Face::PY, 0, D-1, bpp); // top
stitch( Face::PZ, D, 0, bpr, Face::PX, 0, 0, bpr); // right
stitch( Face::PZ, 0, D, bpp, Face::NY, 0, 0, bpp); // bottom
corners(Face::PZ);
// +X / Right
stitch( Face::PX, -1, 0, bpr, Face::PZ, D-1, 0, bpr); // left
stitch( Face::PX, 0, -1, bpp, Face::PY, D-1, D-1, -bpr); // top
stitch( Face::PX, D, 0, bpr, Face::NZ, 0, 0, bpr); // right
stitch( Face::PX, 0, D, bpp, Face::NY, D-1, 0, bpr); // bottom
corners(Face::PX);
// -Z / Back
stitch( Face::NZ, -1, 0, bpr, Face::PX, D-1, 0, bpr); // left
stitch( Face::NZ, 0, -1, bpp, Face::PY, D-1, 0, -bpp); // top
stitch( Face::NZ, D, 0, bpr, Face::NX, 0, 0, bpr); // right
stitch( Face::NZ, 0, D, bpp, Face::NY, D-1, D-1, -bpp); // bottom
corners(Face::NZ);
// -Y / Bottom
stitch( Face::NY, -1, 0, bpr, Face::NX, D-1, D-1, -bpp); // left
stitch( Face::NY, 0, -1, bpp, Face::PZ, 0, D-1, bpp); // top
stitch( Face::NY, D, 0, bpr, Face::PX, 0, D-1, bpp); // right
stitch( Face::NY, 0, D, bpp, Face::NZ, D-1, D-1, -bpp); // bottom
corners(Face::NY);
}
Cubemap::Texel Cubemap::filterAt(const Image& image, float x, float y) {
const size_t x0 = size_t(x);
const size_t y0 = size_t(y);
// we allow ourselves to read past the width/height of the Image because the data is valid
// and contain the "seamless" data.
size_t x1 = x0 + 1;
size_t y1 = y0 + 1;
const float u = float(x - x0);
const float v = float(y - y0);
const float one_minus_u = 1 - u;
const float one_minus_v = 1 - v;
const Texel& c0 = sampleAt(image.getPixelRef(x0, y0));
const Texel& c1 = sampleAt(image.getPixelRef(x1, y0));
const Texel& c2 = sampleAt(image.getPixelRef(x0, y1));
const Texel& c3 = sampleAt(image.getPixelRef(x1, y1));
return (one_minus_u*one_minus_v)*c0 + (u*one_minus_v)*c1 + (one_minus_u*v)*c2 + (u*v)*c3;
}
Cubemap::Texel Cubemap::filterAtCenter(const Image& image, size_t x0, size_t y0) {
// we allow ourselves to read past the width/height of the Image because the data is valid
// and contain the "seamless" data.
size_t x1 = x0 + 1;
size_t y1 = y0 + 1;
const Texel& c0 = sampleAt(image.getPixelRef(x0, y0));
const Texel& c1 = sampleAt(image.getPixelRef(x1, y0));
const Texel& c2 = sampleAt(image.getPixelRef(x0, y1));
const Texel& c3 = sampleAt(image.getPixelRef(x1, y1));
return (c0 + c1 + c2 + c3) * 0.25f;
}
Cubemap::Texel Cubemap::trilinearFilterAt(const Cubemap& l0, const Cubemap& l1, float lerp,
const float3& L)
{
Cubemap::Address addr(getAddressFor(L));
const Image& i0 = l0.getImageForFace(addr.face);
const Image& i1 = l1.getImageForFace(addr.face);
float x0 = std::min(addr.s * l0.mDimensions, l0.mUpperBound);
float y0 = std::min(addr.t * l0.mDimensions, l0.mUpperBound);
float x1 = std::min(addr.s * l1.mDimensions, l1.mUpperBound);
float y1 = std::min(addr.t * l1.mDimensions, l1.mUpperBound);
float3 c0 = filterAt(i0, x0, y0);
c0 += lerp * (filterAt(i1, x1, y1) - c0);
return c0;
}
} // namespace ibl
} // namespace filament