new branch to implement displacements based on work by Milos. Step 1: heightfields!
parent
f1f91c2ebd
commit
06b0562154
|
@ -12,5 +12,6 @@ plugins += env.SharedLibrary('hair', ['hair.cpp'])
|
||||||
plugins += env.SharedLibrary('shapegroup', ['shapegroup.cpp'])
|
plugins += env.SharedLibrary('shapegroup', ['shapegroup.cpp'])
|
||||||
plugins += env.SharedLibrary('instance', ['instance.cpp'])
|
plugins += env.SharedLibrary('instance', ['instance.cpp'])
|
||||||
plugins += env.SharedLibrary('animatedinstance', ['animatedinstance.cpp'])
|
plugins += env.SharedLibrary('animatedinstance', ['animatedinstance.cpp'])
|
||||||
|
plugins += env.SharedLibrary('heightfield', ['heightfield.cpp'])
|
||||||
|
|
||||||
Export('plugins')
|
Export('plugins')
|
||||||
|
|
|
@ -0,0 +1,371 @@
|
||||||
|
/*
|
||||||
|
This file is part of Mitsuba, a physically based rendering system.
|
||||||
|
|
||||||
|
Copyright (c) 2007-2011 by Wenzel Jakob and others.
|
||||||
|
|
||||||
|
Mitsuba is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License Version 3
|
||||||
|
as published by the Free Software Foundation.
|
||||||
|
|
||||||
|
Mitsuba 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <mitsuba/render/shape.h>
|
||||||
|
#include <mitsuba/render/bsdf.h>
|
||||||
|
#include <mitsuba/render/trimesh.h>
|
||||||
|
#include <mitsuba/core/properties.h>
|
||||||
|
#include <mitsuba/core/bitmap.h>
|
||||||
|
#include <mitsuba/core/fstream.h>
|
||||||
|
|
||||||
|
MTS_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/*!\plugin{heightfield}{Height field}
|
||||||
|
* \order{12}
|
||||||
|
*
|
||||||
|
* Developed by Milo\^{s} Ha\^{s}an
|
||||||
|
*/
|
||||||
|
class Heightfield : public Shape {
|
||||||
|
private:
|
||||||
|
inline Float getIJ(int i, int j) const {
|
||||||
|
i = std::max(0, std::min(m_rows-1, i));
|
||||||
|
j = std::max(0, std::min(m_cols-1, j));
|
||||||
|
return m_data[i*m_cols + j];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Normal getNormalIJ(int i, int j) const {
|
||||||
|
i = std::max(0, std::min(m_rows-1, i));
|
||||||
|
j = std::max(0, std::min(m_cols-1, j));
|
||||||
|
return m_normals[i*m_cols + j];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Float getXY(int x, int y) const {
|
||||||
|
return getIJ(m_rows - y - 1, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Normal getNormalXY(int x, int y) const {
|
||||||
|
return getNormalIJ(m_rows - y - 1, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Float getBilinear(Float x, Float y) const {
|
||||||
|
// cout << "getBilinear: " << x << ' ' << y << ' ';
|
||||||
|
int xi = (int) std::floor(x);
|
||||||
|
int yi = (int) std::floor(y);
|
||||||
|
Float u = x - xi, v = y - yi;
|
||||||
|
Float z00 = getXY(xi, yi);
|
||||||
|
Float z01 = getXY(xi, yi+1);
|
||||||
|
Float z10 = getXY(xi+1, yi);
|
||||||
|
Float z11 = getXY(xi+1, yi+1);
|
||||||
|
Float result = (1-u)*(1-v)*z00 + (1-u)*v*z01 + u*(1-v)*z10 + u*v*z11;
|
||||||
|
// cout << result << '\n';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vector2 getBilinearGradient(Float x, Float y) const {
|
||||||
|
int xi = (int) std::floor(x);
|
||||||
|
int yi = (int) std::floor(y);
|
||||||
|
Float u = x - xi, v = y - yi;
|
||||||
|
Float z00 = getXY(xi, yi);
|
||||||
|
Float z01 = getXY(xi, yi+1);
|
||||||
|
Float z10 = getXY(xi+1, yi);
|
||||||
|
Float z11 = getXY(xi+1, yi+1);
|
||||||
|
Float du = (1-v) * (z10 - z00) + v * (z11 - z01);
|
||||||
|
Float dv = (1-u) * (z01 - z00) + u * (z11 - z10);
|
||||||
|
return Vector2(du, dv);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Normal getInterpolatedNormal(Float x, Float y) const {
|
||||||
|
int xi = (int) std::floor(x);
|
||||||
|
int yi = (int) std::floor(y);
|
||||||
|
Float u = x - xi, v = y - yi;
|
||||||
|
Normal z00 = getNormalXY(xi, yi);
|
||||||
|
Normal z01 = getNormalXY(xi, yi+1);
|
||||||
|
Normal z10 = getNormalXY(xi+1, yi);
|
||||||
|
Normal z11 = getNormalXY(xi+1, yi+1);
|
||||||
|
Normal result = (1-u)*(1-v)*z00 + (1-u)*v*z01 + u*(1-v)*z10 + u*v*z11;
|
||||||
|
return normalize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Heightfield(const Properties &props) : Shape(props) {
|
||||||
|
m_pixelSize = props.getFloat("pixelSize", 0.01f);
|
||||||
|
Float multiplier = props.getFloat("multiplier", 1);
|
||||||
|
m_flipNormal = props.getBoolean("flipNormal", false);
|
||||||
|
|
||||||
|
// read bitmap; use only red channel
|
||||||
|
fs::path hfPath = props.getString("hf");
|
||||||
|
ref<FileStream> stream = new FileStream(hfPath);
|
||||||
|
ref<Bitmap> hf = new Bitmap(Bitmap::EOpenEXR, stream);
|
||||||
|
m_rows = hf->getHeight();
|
||||||
|
m_cols = hf->getWidth();
|
||||||
|
Vector4* hfData = (Vector4*) hf->getFloatData();
|
||||||
|
|
||||||
|
m_data = new float[m_rows * m_cols];
|
||||||
|
for (int i = 0; i < m_rows * m_cols; i++)
|
||||||
|
m_data[i] = hfData[i].x * multiplier;
|
||||||
|
|
||||||
|
// compute bounding box
|
||||||
|
Float xMax = (m_cols-1) * m_pixelSize / 2;
|
||||||
|
Float yMax = (m_rows-1) * m_pixelSize / 2;
|
||||||
|
Float zMin = std::numeric_limits<Float>::infinity(), zMax = -zMin;
|
||||||
|
for (int i = 0; i < m_rows * m_cols; i++) {
|
||||||
|
Float v = m_data[i];
|
||||||
|
zMin = std::min(zMin, v);
|
||||||
|
zMax = std::max(zMax, v);
|
||||||
|
}
|
||||||
|
zMin -= Epsilon; zMax += Epsilon;
|
||||||
|
m_aabb = AABB(Point(-xMax, -yMax, zMin), Point(xMax, yMax, zMax));
|
||||||
|
// cout << m_aabb.toString() << '\n';
|
||||||
|
|
||||||
|
// compute normals
|
||||||
|
m_normals = 0;
|
||||||
|
if (props.getBoolean("interpolateNormals", false)) {
|
||||||
|
m_normals = new Normal[m_rows * m_cols];
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_rows; i++)
|
||||||
|
for (int j = 0; j < m_cols; j++) {
|
||||||
|
Float dx = getIJ(i, j+1) - getIJ(i, j-1);
|
||||||
|
Float dy = getIJ(i-1, j) - getIJ(i+1, j); // reversal between i and y
|
||||||
|
Normal n(-dx / m_pixelSize, -dy / m_pixelSize, 2);
|
||||||
|
m_normals[index++] = normalize(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Heightfield(Stream *stream, InstanceManager *manager)
|
||||||
|
: Shape(stream, manager) {
|
||||||
|
Assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Heightfield() {
|
||||||
|
delete[] m_data;
|
||||||
|
if (m_normals) delete[] m_normals;
|
||||||
|
}
|
||||||
|
|
||||||
|
void serialize(Stream *stream, InstanceManager *manager) const {
|
||||||
|
Shape::serialize(stream, manager);
|
||||||
|
Assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
AABB getAABB() const {
|
||||||
|
return m_aabb;
|
||||||
|
}
|
||||||
|
|
||||||
|
Float getSurfaceArea() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// find smallest t >= 0 such that a*t + b is integer
|
||||||
|
inline static Float nextInt(Float a, Float b) {
|
||||||
|
if (a == 0) return std::numeric_limits<Float>::infinity();
|
||||||
|
else if (a > 0) return (std::ceil(b) - b) / a;
|
||||||
|
else return (std::floor(b) - b) / a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// clip ray against bounding box
|
||||||
|
bool clipRay(const Ray& ray, Float& mint, Float& maxt) const {
|
||||||
|
Float tmp1, tmp2;
|
||||||
|
if (!m_aabb.rayIntersect(ray, tmp1, tmp2)) return false;
|
||||||
|
mint = std::max(mint, tmp1);
|
||||||
|
maxt = std::min(maxt, tmp2);
|
||||||
|
if (maxt <= mint) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// transform from world to grid space
|
||||||
|
Point world2grid(Point p) const {
|
||||||
|
p.x /= m_pixelSize;
|
||||||
|
p.y /= m_pixelSize;
|
||||||
|
p.x += (m_cols - 1) / 2.0f;
|
||||||
|
p.y += (m_rows - 1) / 2.0f;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtract ray, fit quadratic function and solve.
|
||||||
|
/// x0, xMid and x1 assumed to be 0, 0.5 and 1.
|
||||||
|
/// Return -1 if no solution in [0,1].
|
||||||
|
inline Float fitAndSolve(Float f0, Float fMid, Float f1, Float z0, Float z1) const {
|
||||||
|
// subtract the "z" line of the ray
|
||||||
|
f0 -= z0; f1 -= z1; fMid -= (z0 + z1) / 2;
|
||||||
|
|
||||||
|
// fit quadratic function to values
|
||||||
|
Float a = 2*f0 - 4*fMid + 2*f1;
|
||||||
|
Float b = -3*f0 + 4*fMid - f1;
|
||||||
|
Float c = f0;
|
||||||
|
|
||||||
|
// solve and return the smaller root within [0,1] (if any)
|
||||||
|
Float t0, t1;
|
||||||
|
if (!solveQuadratic(a, b, c, t0, t1)) return -1;
|
||||||
|
if (t0 >= 0 && t0 <= 1) return t0;
|
||||||
|
if (t1 >= 0 && t1 <= 1) return t1;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// find nearest intersection of ray with bilinear interpolant
|
||||||
|
Float intersectPatch(Float x0, Float y0, Float z0,
|
||||||
|
Float x1, Float y1, Float z1) const {
|
||||||
|
Float f0 = getBilinear(x0, y0);
|
||||||
|
Float fMid = getBilinear((x0 + x1) / 2, (y0 + y1) / 2);
|
||||||
|
Float f1 = getBilinear(x1, y1);
|
||||||
|
return fitAndSolve(f0, fMid, f1, z0, z1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rayIntersect(const Ray &ray, Float mint, Float maxt, Float &tResult, void *tmp) const {
|
||||||
|
if (!clipRay(ray, mint, maxt)) return false;
|
||||||
|
|
||||||
|
// ray segment endpoints in grid space
|
||||||
|
Point start = world2grid(ray(mint));
|
||||||
|
Point end = world2grid(ray(maxt));
|
||||||
|
|
||||||
|
// the 2D vector from start to end of ray
|
||||||
|
Vector d = end - start;
|
||||||
|
if (d.lengthSquared() == 0) return false;
|
||||||
|
|
||||||
|
// handle trivial case of perpendicular ray
|
||||||
|
if (d.x == 0 && d.y == 0) {
|
||||||
|
Assert(d.z != 0);
|
||||||
|
Float z = getBilinear(start.x, start.y);
|
||||||
|
|
||||||
|
if ((z - start.z) * (z - end.z) > 0) return false;
|
||||||
|
|
||||||
|
Float t = (z - start.z) / (end.z - start.z);
|
||||||
|
tResult = (1-t) * mint + t * maxt;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the 2D ray parameter and its deltas (can be infinite)
|
||||||
|
Float t = 0;
|
||||||
|
Float tMax = std::sqrt(d.x*d.x + d.y*d.y);
|
||||||
|
Float tDeltaX = std::abs(tMax / d.x);
|
||||||
|
Float tDeltaY = std::abs(tMax / d.y);
|
||||||
|
d /= tMax;
|
||||||
|
Float tMaxX = nextInt(d.x, start.x);
|
||||||
|
Float tMaxY = nextInt(d.y, start.y);
|
||||||
|
Float tNext;
|
||||||
|
|
||||||
|
while (t < tMax) {
|
||||||
|
if (tMaxX < tMaxY) {
|
||||||
|
// advance in x
|
||||||
|
tNext = std::min(tMaxX, tMax);
|
||||||
|
tMaxX += tDeltaX;
|
||||||
|
} else {
|
||||||
|
// advance in y
|
||||||
|
tNext = std::min(tMaxY, tMax);
|
||||||
|
tMaxY += tDeltaY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get intersection point (shifted into [0,1])
|
||||||
|
Point p = start + t * d;
|
||||||
|
Point q = start + tNext * d;
|
||||||
|
Float tPatch = intersectPatch(p.x, p.y, p.z, q.x, q.y, q.z);
|
||||||
|
|
||||||
|
if (tPatch >= 0) {
|
||||||
|
// found intersection, shift back from [0,1]
|
||||||
|
tPatch = t + tPatch * (tNext - t);
|
||||||
|
tResult = mint + tPatch * (maxt - mint) / tMax;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
t = tNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rayIntersect(const Ray &r, Float mint, Float maxt) const {
|
||||||
|
Float t;
|
||||||
|
return rayIntersect(r, mint, maxt, t, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillIntersectionRecord(const Ray &ray,
|
||||||
|
const void *temp, Intersection &its) const {
|
||||||
|
Float t = its.t;
|
||||||
|
memset(&its, 0, sizeof(Intersection));
|
||||||
|
|
||||||
|
// position
|
||||||
|
its.t = t;
|
||||||
|
its.p = ray(t);
|
||||||
|
|
||||||
|
// normals
|
||||||
|
Point pGrid = world2grid(its.p);
|
||||||
|
Vector2 grad = getBilinearGradient(pGrid.x, pGrid.y);
|
||||||
|
Normal gn = normalize(Vector(-grad.x, -grad.y, m_pixelSize));
|
||||||
|
Normal sn = m_normals ? getInterpolatedNormal(pGrid.x, pGrid.y) : gn;
|
||||||
|
|
||||||
|
// primitive uv-s
|
||||||
|
its.uv = Point2(pGrid.x, pGrid.y);
|
||||||
|
its.dpdu = Vector(m_pixelSize, 0, grad.x);
|
||||||
|
its.dpdv = Vector(0, m_pixelSize, grad.y);
|
||||||
|
|
||||||
|
if (m_flipNormal) { gn *= -1; sn *= -1; }
|
||||||
|
its.geoFrame = Frame(gn);
|
||||||
|
its.shFrame = Frame(sn);
|
||||||
|
|
||||||
|
its.wi = its.toLocal(-ray.d);
|
||||||
|
its.shape = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getNormalDerivative(const Intersection &its,
|
||||||
|
Vector &dndu, Vector &dndv, bool shadingFrame) const {
|
||||||
|
if (!m_normals) {
|
||||||
|
dndu = Vector(0.0f);
|
||||||
|
dndv = Vector(0.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Float x = its.uv.x, y = its.uv.y;
|
||||||
|
int xi = (int) std::floor(x);
|
||||||
|
int yi = (int) std::floor(y);
|
||||||
|
Float u = x - xi, v = y - yi;
|
||||||
|
Normal n00 = getNormalXY(xi, yi);
|
||||||
|
Normal n01 = getNormalXY(xi, yi+1);
|
||||||
|
Normal n10 = getNormalXY(xi+1, yi);
|
||||||
|
Normal n11 = getNormalXY(xi+1, yi+1);
|
||||||
|
|
||||||
|
Normal N((1-u)*(1-v)*n00 + (1-u)*v*n01 + u*(1-v)*n10 + u*v*n11);
|
||||||
|
Float il = 1.0f / N.length(); N *= il;
|
||||||
|
|
||||||
|
dndu = (1-v) * (n10 - n00) + v * (n11 - n01);
|
||||||
|
dndu *= il; dndu -= N * dot(N, dndu);
|
||||||
|
dndv = (1-u) * (n01 - n00) + u * (n11 - n10);
|
||||||
|
dndv *= il; dndv -= N * dot(N, dndv);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getPrimitiveCount() const {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getEffectivePrimitiveCount() const {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toString() const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Heightfield[" << endl
|
||||||
|
<< " pixelSize = " << m_pixelSize << ", " << endl
|
||||||
|
<< " rows = " << m_rows << ", " << endl
|
||||||
|
<< " cols = " << m_cols << ", " << endl
|
||||||
|
<< "]";
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
MTS_DECLARE_CLASS()
|
||||||
|
private:
|
||||||
|
AABB m_aabb;
|
||||||
|
Float m_pixelSize;
|
||||||
|
int m_rows, m_cols;
|
||||||
|
float* m_data;
|
||||||
|
Normal* m_normals;
|
||||||
|
bool m_flipNormal;
|
||||||
|
};
|
||||||
|
|
||||||
|
MTS_IMPLEMENT_CLASS_S(Heightfield, false, Shape)
|
||||||
|
MTS_EXPORT_PLUGIN(Heightfield, "Height field intersection primitive");
|
||||||
|
MTS_NAMESPACE_END
|
||||||
|
|
Loading…
Reference in New Issue