2.4 Normals

A surface normal (or just normal) is a vector that is perpendicular to a surface at a particular position. It can be defined as the cross product of any two nonparallel vectors that are tangent to the surface at a point. Although normals are superficially similar to vectors, it is important to distinguish between the two of them: because normals are defined in terms of their relationship to a particular surface, they behave differently than vectors in some situations, particularly when applying transformations. This difference is discussed in Section 2.8.

<<Normal Declarations>>= 
template <typename T> class Normal3 { public: <<Normal3 Public Methods>> 
Normal3() { x = y = z = 0; } Normal3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) { } Normal3<T> operator-() const { return Normal3(-x, -y, -z); } Normal3<T> operator+(const Normal3<T> &n) const { return Normal3<T>(x + n.x, y + n.y, z + n.z); } Normal3<T>& operator+=(const Normal3<T> &n) { x += n.x; y += n.y; z += n.z; return *this; } Normal3<T> operator- (const Normal3<T> &n) const { return Normal3<T>(x - n.x, y - n.y, z - n.z); } Normal3<T>& operator-=(const Normal3<T> &n) { x -= n.x; y -= n.y; z -= n.z; return *this; } bool HasNaNs() const { return std::isnan(x) || std::isnan(y) || std::isnan(z); } Normal3<T> operator*(T f) const { return Normal3<T>(f*x, f*y, f*z); } Normal3<T> &operator*=(T f) { x *= f; y *= f; z *= f; return *this; } Normal3<T> operator/(T f) const { Assert(f != 0); Float inv = (Float)1 / f; return Normal3<T>(x * inv, y * inv, z * inv); } Normal3<T> &operator/=(T f) { Assert(f != 0); Float inv = (Float)1 / f; x *= inv; y *= inv; z *= inv; return *this; } Float LengthSquared() const { return x*x + y*y + z*z; } Float Length() const { return std::sqrt(LengthSquared()); } <<Geometry Inline Functions>> 
template <typename T> inline Vector3<T>::Vector3(const Point3<T> &p) : x(p.x), y(p.y), z(p.z) { } template <typename T> inline Vector3<T> operator*(T s, const Vector3<T> &v) { return v * s; } template <typename T> Vector3<T> Abs(const Vector3<T> &v) { return Vector3<T>(std::abs(v.x), std::abs(v.y), std::abs(v.z)); } template <typename T> inline T Dot(const Vector3<T> &v1, const Vector3<T> &v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; } template <typename T> inline T AbsDot(const Vector3<T> &v1, const Vector3<T> &v2) { return std::abs(Dot(v1, v2)); } template <typename T> inline Vector3<T> Cross(const Vector3<T> &v1, const Vector3<T> &v2) { double v1x = v1.x, v1y = v1.y, v1z = v1.z; double v2x = v2.x, v2y = v2.y, v2z = v2.z; return Vector3<T>((v1y * v2z) - (v1z * v2y), (v1z * v2x) - (v1x * v2z), (v1x * v2y) - (v1y * v2x)); } template <typename T> inline Vector3<T> Cross(const Vector3<T> &v1, const Normal3<T> &v2) { double v1x = v1.x, v1y = v1.y, v1z = v1.z; double v2x = v2.x, v2y = v2.y, v2z = v2.z; return Vector3<T>((v1y * v2z) - (v1z * v2y), (v1z * v2x) - (v1x * v2z), (v1x * v2y) - (v1y * v2x)); } template <typename T> inline Vector3<T> Cross(const Normal3<T> &v1, const Vector3<T> &v2) { double v1x = v1.x, v1y = v1.y, v1z = v1.z; double v2x = v2.x, v2y = v2.y, v2z = v2.z; return Vector3<T>((v1y * v2z) - (v1z * v2y), (v1z * v2x) - (v1x * v2z), (v1x * v2y) - (v1y * v2x)); } template <typename T> inline Vector3<T> Normalize(const Vector3<T> &v) { return v / v.Length(); } template <typename T> T MinComponent(const Vector3<T> &v) { return std::min(v.x, std::min(v.y, v.z)); } template <typename T> T MaxComponent(const Vector3<T> &v) { return std::max(v.x, std::max(v.y, v.z)); } template <typename T> int MaxDimension(const Vector3<T> &v) { return (v.x > v.y) ? ((v.x > v.z) ? 0 : 2) : ((v.y > v.z) ? 1 : 2); } template <typename T> Vector3<T> Min(const Vector3<T> &p1, const Vector3<T> &p2) { return Vector3<T>(std::min(p1.x, p2.x), std::min(p1.y, p2.y), std::min(p1.z, p2.z)); } template <typename T> Vector3<T> Max(const Vector3<T> &p1, const Vector3<T> &p2) { return Vector3<T>(std::max(p1.x, p2.x), std::max(p1.y, p2.y), std::max(p1.z, p2.z)); } template <typename T> Vector3<T> Permute(const Vector3<T> &v, int x, int y, int z) { return Vector3<T>(v[x], v[y], v[z]); } template <typename T> inline void CoordinateSystem(const Vector3<T> &v1, Vector3<T> *v2, Vector3<T> *v3) { if (std::abs(v1.x) > std::abs(v1.y)) *v2 = Vector3<T>(-v1.z, 0, v1.x) / std::sqrt(v1.x * v1.x + v1.z * v1.z); else *v2 = Vector3<T>(0, v1.z, -v1.y) / std::sqrt(v1.y * v1.y + v1.z * v1.z); *v3 = Cross(v1, *v2); } template <typename T> Vector2<T>::Vector2(const Point2<T> &p) : x(p.x), y(p.y) { Assert(!HasNaNs()); } template <typename T> Vector2<T>::Vector2(const Point3<T> &p) : x(p.x), y(p.y) { Assert(!HasNaNs()); } template <typename T> inline Vector2<T> operator*(T f, const Vector2<T> &v) { return v*f; } template <typename T> inline Float Dot(const Vector2<T> &v1, const Vector2<T> &v2) { return v1.x * v2.x + v1.y * v2.y; } template <typename T> inline Float AbsDot(const Vector2<T> &v1, const Vector2<T> &v2) { return std::abs(Dot(v1, v2)); } template <typename T> inline Vector2<T> Normalize(const Vector2<T> &v) { return v / v.Length(); } template <typename T> Vector2<T> Abs(const Vector2<T> &v) { return Vector2<T>(std::abs(v.x), std::abs(v.y)); } template <typename T> inline Float Distance(const Point3<T> &p1, const Point3<T> &p2) { return (p1 - p2).Length(); } template <typename T> inline Float DistanceSquared(const Point3<T> &p1, const Point3<T> &p2) { return (p1 - p2).LengthSquared(); } template <typename T> inline Point3<T> operator*(Float f, const Point3<T> &p) { Assert(!p.HasNaNs()); return p * f; } template <typename T> Point3<T> Lerp(Float t, const Point3<T> &p0, const Point3<T> &p1) { return (1 - t) * p0 + t * p1; } template <typename T> Point3<T> Min(const Point3<T> &p1, const Point3<T> &p2) { return Point3<T>(std::min(p1.x, p2.x), std::min(p1.y, p2.y), std::min(p1.z, p2.z)); } template <typename T> Point3<T> Max(const Point3<T> &p1, const Point3<T> &p2) { return Point3<T>(std::max(p1.x, p2.x), std::max(p1.y, p2.y), std::max(p1.z, p2.z)); } template <typename T> Point3<T> Floor(const Point3<T> &p) { return Point3<T>(std::floor(p.x), std::floor(p.y), std::floor(p.z)); } template <typename T> Point3<T> Ceil(const Point3<T> &p) { return Point3<T>(std::ceil(p.x), std::ceil(p.y), std::ceil(p.z)); } template <typename T> Point3<T> Abs(const Point3<T> &p) { return Point3<T>(std::abs(p.x), std::abs(p.y), std::abs(p.z)); } template <typename T> inline Float Distance(const Point2<T> &p1, const Point2<T> &p2) { return (p1 - p2).Length(); } template <typename T> inline Float DistanceSquared(const Point2<T> &p1, const Point2<T> &p2) { return (p1 - p2).LengthSquared(); } template <typename T> inline Point2<T> operator*(T f, const Point2<T> &p) { Assert(!p.HasNaNs()); return p*f; } template <typename T> Point2<T> Floor(const Point2<T> &p) { return Point2<T>(std::floor(p.x), std::floor(p.y)); } template <typename T> Point2<T> Ceil(const Point2<T> &p) { return Point2<T>(std::ceil(p.x), std::ceil(p.y)); } template <typename T> Point2<T> Lerp(Float t, const Point2<T> &v0, const Point2<T> &v1) { return (1 - t) * v0 + t * v1; } template <typename T> Point2<T> Min(const Point2<T> &pa, const Point2<T> &pb) { return Point2<T>(std::min(pa.x, pb.x), std::min(pa.y, pb.y)); } template <typename T> Point2<T> Max(const Point2<T> &pa, const Point2<T> &pb) { return Point2<T>(std::max(pa.x, pb.x), std::max(pa.y, pb.y)); } template <typename T> Point3<T> Permute(const Point3<T> &p, int x, int y, int z) { return Point3<T>(p[x], p[y], p[z]); } template <typename T> inline Normal3<T> operator*(T f, const Normal3<T> &n) { return Normal3<T>(f*n.x, f*n.y, f*n.z); } template <typename T> inline Vector3<T>::Vector3(const Normal3<T> &n) : x(n.x), y(n.y), z(n.z) { Assert(!n.HasNaNs()); } template <typename T> inline T Dot(const Normal3<T> &n1, const Vector3<T> &v2) { return n1.x * v2.x + n1.y * v2.y + n1.z * v2.z; } template <typename T> inline T Dot(const Vector3<T> &v1, const Normal3<T> &n2) { return v1.x * n2.x + v1.y * n2.y + v1.z * n2.z; } template <typename T> inline T Dot(const Normal3<T> &n1, const Normal3<T> &n2) { return n1.x * n2.x + n1.y * n2.y + n1.z * n2.z; } template <typename T> inline T AbsDot(const Normal3<T> &n1, const Vector3<T> &v2) { return std::abs(n1.x * v2.x + n1.y * v2.y + n1.z * v2.z); } template <typename T> inline T AbsDot(const Vector3<T> &v1, const Normal3<T> &n2) { return std::abs(v1.x * n2.x + v1.y * n2.y + v1.z * n2.z); } template <typename T> inline T AbsDot(const Normal3<T> &n1, const Normal3<T> &n2) { return std::abs(n1.x * n2.x + n1.y * n2.y + n1.z * n2.z); } template <typename T> inline Normal3<T> Faceforward(const Normal3<T> &n, const Vector3<T> &v) { return (Dot(n, v) < 0.f) ? -n : n; } template <typename T> inline Normal3<T> Faceforward(const Normal3<T> &n, const Normal3<T> &n2) { return (Dot(n, n2) < 0.f) ? -n : n; } template <typename T> inline Vector3<T> Faceforward(const Vector3<T> &v, const Vector3<T> &v2) { return (Dot(v, v2) < 0.f) ? -v : v; } template <typename T> inline Vector3<T> Faceforward(const Vector3<T> &v, const Normal3<T> &n2) { return (Dot(v, n2) < 0.f) ? -v : v; } template <typename T> Normal3<T> Abs(const Normal3<T> &v) { return Normal3<T>(std::abs(v.x), std::abs(v.y), std::abs(v.z)); } template <typename T> inline const Point3<T> & Bounds3<T>::operator[](int i) const { Assert(i == 0 || i == 1); return (i == 0) ? pMin : pMax; } template <typename T> inline Point3<T> & Bounds3<T>::operator[](int i) { Assert(i == 0 || i == 1); return (i == 0) ? pMin : pMax; } template <typename T> Bounds3 <T> Union(const Bounds3<T> &b, const Point3<T> &p) { return Bounds3<T>(Point3<T>(std::min(b.pMin.x, p.x), std::min(b.pMin.y, p.y), std::min(b.pMin.z, p.z)), Point3<T>(std::max(b.pMax.x, p.x), std::max(b.pMax.y, p.y), std::max(b.pMax.z, p.z))); } template <typename T> Bounds3<T> Union(const Bounds3<T> &b1, const Bounds3<T> &b2) { return Bounds3<T>(Point3<T>(std::min(b1.pMin.x, b2.pMin.x), std::min(b1.pMin.y, b2.pMin.y), std::min(b1.pMin.z, b2.pMin.z)), Point3<T>(std::max(b1.pMax.x, b2.pMax.x), std::max(b1.pMax.y, b2.pMax.y), std::max(b1.pMax.z, b2.pMax.z))); } template <typename T> Bounds3<T> Intersect(const Bounds3<T> &b1, const Bounds3<T> &b2) { return Bounds3<T>(Point3<T>(std::max(b1.pMin.x, b2.pMin.x), std::max(b1.pMin.y, b2.pMin.y), std::max(b1.pMin.z, b2.pMin.z)), Point3<T>(std::min(b1.pMax.x, b2.pMax.x), std::min(b1.pMax.y, b2.pMax.y), std::min(b1.pMax.z, b2.pMax.z))); } template <typename T> bool Overlaps(const Bounds3<T> &b1, const Bounds3<T> &b2) { bool x = (b1.pMax.x >= b2.pMin.x) && (b1.pMin.x <= b2.pMax.x); bool y = (b1.pMax.y >= b2.pMin.y) && (b1.pMin.y <= b2.pMax.y); bool z = (b1.pMax.z >= b2.pMin.z) && (b1.pMin.z <= b2.pMax.z); return (x && y && z); } template <typename T> bool Inside(const Point3<T> &p, const Bounds3<T> &b) { return (p.x >= b.pMin.x && p.x <= b.pMax.x && p.y >= b.pMin.y && p.y <= b.pMax.y && p.z >= b.pMin.z && p.z <= b.pMax.z); } template <typename T> bool InsideExclusive(const Point3<T> &p, const Bounds3<T> &b) { return (p.x >= b.pMin.x && p.x < b.pMax.x && p.y >= b.pMin.y && p.y < b.pMax.y && p.z >= b.pMin.z && p.z < b.pMax.z); } template <typename T, typename U> inline Bounds3<T> Expand(const Bounds3<T> &b, U delta) { return Bounds3<T>(b.pMin - Vector3<T>(delta, delta, delta), b.pMax + Vector3<T>(delta, delta, delta)); } inline Bounds2iIterator begin(const Bounds2i &b) { return Bounds2iIterator(b, b.pMin); } inline Bounds2iIterator end(const Bounds2i &b) { return Bounds2iIterator(b, Point2i(b.pMin.x, b.pMax.y)); } template <typename T> Bounds2<T> Union(const Bounds2<T> &b, const Point2<T> &p) { Bounds2<T> ret(Point2<T>(std::min(b.pMin.x, p.x), std::min(b.pMin.y, p.y)), Point2<T>(std::max(b.pMax.x, p.x), std::max(b.pMax.y, p.y))); return ret; } template <typename T> Bounds2<T> Union(const Bounds2<T> &b, const Bounds2<T> &b2) { Bounds2<T> ret(Point2<T>(std::min(b.pMin.x, b2.pMin.x), std::min(b.pMin.y, b2.pMin.y)), Point2<T>(std::max(b.pMax.x, b2.pMax.x), std::max(b.pMax.y, b2.pMax.y))); return ret; } template <typename T> Bounds2<T> Intersect(const Bounds2<T> &b, const Bounds2<T> &b2) { Bounds2<T> ret(Point2<T>(std::max(b.pMin.x, b2.pMin.x), std::max(b.pMin.y, b2.pMin.y)), Point2<T>(std::min(b.pMax.x, b2.pMax.x), std::min(b.pMax.y, b2.pMax.y))); return ret; } template <typename T> bool Overlaps(const Bounds2<T> &ba, const Bounds2<T> &bb) { bool x = (ba.pMax.x >= bb.pMin.x) && (ba.pMin.x <= bb.pMax.x); bool y = (ba.pMax.y >= bb.pMin.y) && (ba.pMin.y <= bb.pMax.y); return (x && y); } template <typename T> bool Inside(const Point2<T> &pt, const Bounds2<T> &b) { return (pt.x >= b.pMin.x && pt.x <= b.pMax.x && pt.y >= b.pMin.y && pt.y <= b.pMax.y); } template <typename T> bool InsideExclusive(const Point2<T> &pt, const Bounds2<T> &b) { return (pt.x >= b.pMin.x && pt.x < b.pMax.x && pt.y >= b.pMin.y && pt.y < b.pMax.y); } template <typename T, typename U> Bounds2<T> Expand(const Bounds2<T> &b, U delta) { return Bounds2<T>(b.pMin - Vector2<T>(delta, delta), b.pMax + Vector2<T>(delta, delta)); } template <typename T> inline bool Bounds3<T>::IntersectP(const Ray &ray, Float *hitt0, Float *hitt1) const { Float t0 = 0, t1 = ray.tMax; for (int i = 0; i < 3; ++i) { <<Update interval for ith bounding box slab>> 
Float invRayDir = 1 / ray.d[i]; Float tNear = (pMin[i] - ray.o[i]) * invRayDir; Float tFar = (pMax[i] - ray.o[i]) * invRayDir; <<Update parametric interval from slab intersection t values>> 
if (tNear > tFar) std::swap(tNear, tFar); <<Update tFar to ensure robust ray–bounds intersection>> 
tFar *= 1 + 2 * gamma(3);
t0 = tNear > t0 ? tNear : t0; t1 = tFar < t1 ? tFar : t1; if (t0 > t1) return false;
} if (hitt0) *hitt0 = t0; if (hitt1) *hitt1 = t1; return true; } template <typename T> inline bool Bounds3<T>::IntersectP(const Ray &ray, const Vector3f &invDir, const int dirIsNeg[3]) const { const Bounds3f &bounds = *this; <<Check for ray intersection against x and y slabs>> 
Float tMin = (bounds[ dirIsNeg[0]].x - ray.o.x) * invDir.x; Float tMax = (bounds[1-dirIsNeg[0]].x - ray.o.x) * invDir.x; Float tyMin = (bounds[ dirIsNeg[1]].y - ray.o.y) * invDir.y; Float tyMax = (bounds[1-dirIsNeg[1]].y - ray.o.y) * invDir.y; <<Update tMax and tyMax to ensure robust bounds intersection>> 
tMax *= 1 + 2 * gamma(3); tyMax *= 1 + 2 * gamma(3);
if (tMin > tyMax || tyMin > tMax) return false; if (tyMin > tMin) tMin = tyMin; if (tyMax < tMax) tMax = tyMax;
<<Check for ray intersection against z slab>> 
Float tzMin = (bounds[ dirIsNeg[2]].z - ray.o.z) * invDir.z; Float tzMax = (bounds[1-dirIsNeg[2]].z - ray.o.z) * invDir.z; <<Update tzMax to ensure robust bounds intersection>> 
tzMax *= 1 + 2 * gamma(3);
if (tMin > tzMax || tzMin > tMax) return false; if (tzMin > tMin) tMin = tzMin; if (tzMax < tMax) tMax = tzMax;
return (tMin < ray.tMax) && (tMax > 0); } inline Point3f OffsetRayOrigin(const Point3f &p, const Vector3f &pError, const Normal3f &n, const Vector3f &w) { Float d = Dot(Abs(n), pError); Vector3f offset = d * Vector3f(n); if (Dot(w, n) < 0) offset = -offset; Point3f po = p + offset; <<Round offset point po away from p>> 
for (int i = 0; i < 3; ++i) { if (offset[i] > 0) po[i] = NextFloatUp(po[i]); else if (offset[i] < 0) po[i] = NextFloatDown(po[i]); }
return po; } inline Vector3f SphericalDirection(Float sinTheta, Float cosTheta, Float phi) { return Vector3f(sinTheta * std::cos(phi), sinTheta * std::sin(phi), cosTheta); } inline Vector3f SphericalDirection(Float sinTheta, Float cosTheta, Float phi, const Vector3f &x, const Vector3f &y, const Vector3f &z) { return sinTheta * std::cos(phi) * x + sinTheta * std::sin(phi) * y + cosTheta * z; } inline Float SphericalTheta(const Vector3f &v) { return std::acos(Clamp(v.z, -1, 1)); } inline Float SphericalPhi(const Vector3f &v) { Float p = std::atan2(v.y, v.x); return (p < 0) ? (p + 2 * Pi) : p; }
= template <typename T> inline Normal3<T> Normalize(const Normal3<T> &n) { return n / n.Length(); } #ifndef NDEBUG Normal3<T>(const Normal3<T> &n) { Assert(!n.HasNaNs()); x = n.x; y = n.y; z = n.z; } Normal3<T> &operator=(const Normal3<T> &n) { Assert(!n.HasNaNs()); x = n.x; y = n.y; z = n.z; return *this; } #endif // !NDEBUG friend std::ostream& operator<<(std::ostream& os, const Normal3<T> &v) { os << "[" << v.x << ", " << v.y << ", " << v.z << "]"; return os; } explicit Normal3<T>(const Vector3<T> &v) : x(v.x), y(v.y), z(v.z) { Assert(!v.HasNaNs()); } bool operator==(const Normal3<T> &n) const { return x == n.x && y == n.y && z == n.z; } bool operator!=(const Normal3<T> &n) const { return x != n.x || y != n.y || z != n.z; } T operator[](int i) const { Assert(i >= 0 && i <= 2); if (i == 0) return x; if (i == 1) return y; return z; } T &operator[](int i) { Assert(i >= 0 && i <= 2); if (i == 0) return x; if (i == 1) return y; return z; }
<<Normal3 Public Data>> 
T x, y, z;
};

<<Normal Declarations>>+= 
typedef Normal3<Float> Normal3f;

The implementations of Normal3s and Vector3s are very similar. Like vectors, normals are represented by three components xy, and z; they can be added and subtracted to compute new normals; and they can be scaled and normalized. However, a normal cannot be added to a point, and one cannot take the cross product of two normals. Note that, in an unfortunate turn of terminology, normals are not necessarily normalized.

Normal3 provides an extra constructor that initializes a Normal3 from a Vector3. Because Normal3s and Vector3s are different in subtle ways, we want to make sure that this conversion doesn’t happen when we don’t intend it to, so the C++ explicit keyword is again used here. Vector3 also provides a constructor that converts the other way. Thus, given the declarations Vector3f v; and Normal3f n;, then the assignment n = v is illegal, so it is necessary to explicitly convert the vector, as in n = Normal3f(v).

<<Normal3 Public Methods>>= 
explicit Normal3<T>(const Vector3<T> &v) : x(v.x), y(v.y), z(v.z) { Assert(!v.HasNaNs()); }

<<Geometry Inline Functions>>+=  
template <typename T> inline Vector3<T>::Vector3(const Normal3<T> &n) : x(n.x), y(n.y), z(n.z) { Assert(!n.HasNaNs()); }

The Dot() and AbsDot() functions are also overloaded to compute dot products between the various possible combinations of normals and vectors. This code won’t be included in the text here. We also won’t include implementations of all of the various other Normal3 methods here, since they are similar to those for vectors.

One new operation to implement comes from the fact it’s often necessary to flip a surface normal so that it lies in the same hemisphere as a given vector—for example, the surface normal that lies in the same hemisphere as an outgoing ray is frequently needed. The Faceforward() utility function encapsulates this small computation. (pbrt also provides variants of this function for the other three combinations of Vector3s and Normal3s as parameters.) Be careful when using the other instances, though: when using the version that takes two Vector3s, for example, ensure that the first parameter is the one that should be returned (possibly flipped) and the second is the one to test against. Reversing the two parameters will give unexpected results.

<<Geometry Inline Functions>>+=  
template <typename T> inline Normal3<T> Faceforward(const Normal3<T> &n, const Vector3<T> &v) { return (Dot(n, v) < 0.f) ? -n : n; }