Texture is the perceived surface quality. In CG texture is a 2D multi-channel image overlaid on the surface of a 3D solid to give it color or illusion of relief. Being the property of the geometry, textures play essential role in modeling BRDFs and thus they are managed entirely by the shaders. The quality of a textured surface is determined by the number of pixels per texture element (textel).
in OpenRT, textures are described by class rt::CTexture : public cv::Mat
Image texture is a bitmap image usually stored in a common image file format. The quality of textured surface is determined by the number of pixels per telex. The resolution of image texture play a major role, which affects the overall impression of the quality of graphics in 3D-application.
Procedural texture is created using a mathematical description (i.e. an algorithm) rather than directly stored data. The advantage of this approach is low storage cost, unlimited texture resolution and easy texture mapping. Procedural textures are used to model surface of natural elements such as clouds, marble, granite, metal, stone, and others.
Bump mapping or Normal mapping
Displacement mapping
Environmental mapping or Reflection mapping
Solid is a rigid geometrical construction which consists out of multiple primitives, usually triangles. Primitives are combined into solids in order to make application of geometrical transformations and texturing easier.
in OpenRT, solids are described by class rt::CSolid
The bidirectional reflectance distribution function:
$$ f_r(\vec{p},\omega_o,\omega_i) = \frac{dL_o(\vec{p},\omega_o)}{dE(\vec{p},\omega_i)},$$
where \(\vec{p}\equiv\vec{p}(x, y, z)\) is a 3 dimentional point on solid's surface
Texture is about modeling point - dependent characteristics of BRDF
To map a two-dimensional texture to three-dimensional surface for every 3D point \(\vec{p}(x,y,z)\) a corresponding 2D point \(\vec{q}(u,v)\) in texture must be found.
$$f:(x,y,z)\rightarrow (u,v), $$
where usually
$$u,v\in[0; 1]$$
To map a two-dimensional texture to three-dimensional surface for every 3D point \(\vec{p}(x,y,z)\) a corresponding 2D point \(\vec{q}(u,v)\) in texture must be found.
$$f:(x,y,z)\rightarrow (u,v), $$
where usually
$$u,v\in[0; 1]$$
When ray - primitive intersection is found, the hit-point \(\vec{p} = \vec{o} + t\cdot\vec{d}\) is encoded into the ray structure, thus parametrization can be implemented for every primitive in a method
Vec2f rt::IPrim::getTextureCoords(const Ray& ray) const = 0;
$$r=\sqrt{x^2+y^2+z^2}$$
$$\varphi=\arctan_2\frac{y}{x}$$
$$\theta=\arccos\frac{z}{r}$$
(read more about \(\arctan_2\) function)
Since \(\varphi\in[-\pi; \pi]\) and \(\theta\in[0;\pi]\)
$$u=\frac{\pi+\varphi}{2\pi}$$
$$v=\frac{\theta}{\pi}$$
Vec2f CPrimSphere::getTextureCoords(const Ray& ray) const
{
Vec3f hitPoint = ray.hitPoint() - m_origin;
float theta = acosf(hitPoint.val[1] / m_radius); // [0; Pif]
float phi = atan2(hitPoint.val[2], hitPoint.val[0]); // [-Pif; Pif]
if (isnan(phi)) phi = 0;
return Vec2f((Pif + phi) / (2 * Pif), theta / Pif);
}
in addition to intersection distance \(t\) implicitly calculates the barycentric intersection coordinates \((u,v)\in[0; 1]\), which may be used directly as the texel coordinates
Vec2f CPrimTriangle::getTextureCoords(const Ray& ray) const {
return (1.0f - ray.u - ray.v) * m_ta
+ ray.u * m_tb
+ ray.v * m_tc;
}
struct Ray {
Vec3f org; // Ray origin
Vec3f dir; // Ray direction
double t; // Current/maximum hit distance
const IPrim* hit; // Pointer to currently closest primitive
float u; // Barycentric u coordinate
float v; // Barycentric v coordinate
} ray;
Quadrilateral solid \((a,b,c,d)\) consists out of two triangles \(a,b,c)\) and \((a,c,d)\).
$$r=\sqrt{x^2+y^2}$$
$$\varphi=\arctan_2\frac{y}{x}$$
$$h=h$$
(read more about \(\arctan_2\) function)
Since \(\varphi\in[-\pi; \pi]\) and \(h\in[0;H]\)
$$u=\frac{\pi+\varphi}{2\pi}$$
$$v=\frac{h}{H}$$
If a cylinder has \(n\) sides and every side \(s\) of a cylinder is modeled with a quad \((a,b,c,d)\), it is straightforward to calculate the 3D vertex positions as well as 2D texture coordinates:
$$u_a = \frac{s+1}{n}; v_a = 1$$
$$u_b = \frac{s}{n}; v_b = 1$$
$$u_c = \frac{s}{n}; v_c = 0$$
$$u_d = \frac{s+1}{n}; v_d = 0$$
Inverse mapping for arbitrary 3D surfaces is too complex
is an approximation technique to generate texture coordinates for every vertex of a complex-shaped solid
Inverse mapping for arbitrary 3D surfaces is too complex
Texturing a vase with different intermediate surfaces:
Strong distortion where object surface normal is orthogonal to the intermediate plane normal
Reasonably uniform mapping (symmetry!)
Problems with concave regions
Sphere with diffuse texture
Swirly bump map
Sphere with diffuse texture and bump map
\[\frac{\partial b}{\partial u}=\frac{h_{x+1,y}-h_{x-1,y}}{dx}\]
\[\frac{\partial b}{\partial v}=\frac{h_{x,y+1}-h_{x,y-1}}{dy}\]
perturbed surface
\(\vec{p}\)
\(\vec{n}\)
\(\vec{p}\)
\(\vec{p}'\)
\(\vec{p}\)
\(\vec{n}\)
\(\vec{p}\)
\(\vec{p}'\)
\(\vec{p}'\)
\(\vec{n}'\)
\(\vec{p}\)
\(\vec{n}'\)
height map
normal map
\[(n_x,n_y,n_z)=(r,g,b)\]
Interpret the RGB values per texel as the perturbed normal, not height value
Interpret texel as offset vector to actually displace fragments:
\[\vec{p'} = \vec{p} + h(u,v)\vec{n}\]
Complex geometry at virtually no memory cost
Must be done during modelling stage, e.g. before ray-geometry intersection
Interpret texel as offset vector to actually displace fragments:
\[\vec{p'} = \vec{p} + h(u,v)\vec{n}\]
Complex geometry at virtually no memory cost
Must be done during modelling stage, e.g. before ray-geometry intersection
Base mesh
Repeated subdivision & vertex displacement
Water surface
Fog
…
bump mapping
displacement mapping
\(\vec{n}\)
\(\vec{p}\)
\(\vec{r}\)
primary ray
environmental sphere
omitted intersection point
really visible point
Terminator 2 motion picture 1991
OpenRT mirror balls 2021
1280 x 573 pixels size environmental map
2D mapping
3D carving
Instead of using the texture coordinates as an index, use them to compute a function that defines the texture
\[f(u,v,w)\]
Vec3f CTextureProcedural::getVoxel(const Vec3f& uvw) const
{
float u = uvw.val[0]; // hitpoint.x
float v = uvw.val[1]; // hitpoint.y
float w = uvw.val[2]; // hitpoint.z
float k = 100;
float intencity = (1 + sinf(k * u) * cosf(k * v)) / 2;
Vec3f color = Vec3f::all(intencity);
return color;
}
Color each point one or the other color depending on where \(floor(z)\) (or \(floor(x)\) or \(floor(y)\)) is even or odd
Color each point one or the other color depending on whether the \(floor(\) distancefrom object center along two coordinates\()\) is even or odd
\[f(u,v,w) = (u^2+v^2)\]
Outer rings closer together, which simulates the growth rate of real trees.
woodmap(0)
woodmap(1)
= brown earlywood
= tan latewood
wood(u,v,w)=woodmap((u*u + v*v) % 1)
wood(u,v,w)=woodmap((u*u + v*v) % 1)
Add noise to cylinders to warp wood
wood(u,v,w)=woodmap((u*u + v*v) % 1 + noise(u,v,w))
\[noise(u,v,w)=\alpha\cdot noise(f_xu+\phi_x,f_yv+\phi_y,f_zw+\phi_z)\]
\(noise(u,v,w)\): pseudo-random number generator with the following characteristics:
white noise
Perlin noise
Abs(Perlin noise)
Sum multiple calls to noise:
\[turbulance(u,v,w)=\sum^{octaves}_{i=1}\frac{1}{2^if}noise(2^if\cdot(u,v,w))\]
each additional term adds finer detail, with diminishing return
1-8 octaves of turbulence
\[marble(u,v,w)=sin\left(f\cdot u+\alpha\cdot turbulance(u,v,w)\right)\]