{"id":34,"date":"2021-08-22T14:13:54","date_gmt":"2021-08-22T13:13:54","guid":{"rendered":"https:\/\/mrzebra.co.uk\/code\/?p=34"},"modified":"2021-08-22T14:33:27","modified_gmt":"2021-08-22T13:33:27","slug":"ray-marching-in-infinitely-tiling-space","status":"publish","type":"post","link":"https:\/\/zebra-north.com\/code\/2021\/08\/22\/ray-marching-in-infinitely-tiling-space\/","title":{"rendered":"Ray Marching in Infinitely Tiling Space"},"content":{"rendered":"\n<p>Ray marching is a useful technique for rendering complex scenes, but what if you want that scene repeated throughout space, infinitely in all directions?<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"636\" height=\"355\" src=\"https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/infinite-cubes.png\" alt=\"\" class=\"wp-image-57\" srcset=\"https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/infinite-cubes.png 636w, https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/infinite-cubes-300x167.png 300w\" sizes=\"auto, (max-width: 636px) 100vw, 636px\" \/><\/figure><\/div>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">Ray Marching<\/h2>\n\n\n\n<p>Ray marching is a rendering technique often used implemented in shader demos.  Similar to ray tracing, it projects a ray from the camera to find the geometry that it intersects.  The difference is that instead of casting an infinite ray through the scene and finding the closest intersection, the ray is advanced by the smallest distance that will <em>not<\/em> intersect any geometry.  When that distance drops below a certain threshold, the ray is considered to have intersected the geometry.  In this way, the ray is stepped (&#8220;marched&#8221;) forward through space until an intersection is found.<\/p>\n\n\n\n<p>Because ray marching only requires calculation of the distance between a point and a surface, not the intersection of a line and a surface, it may be easier to calculate.<\/p>\n\n\n\n<p>For an in-depth explanation of ray marching, see the excellent video tutorial series by <a href=\"https:\/\/www.youtube.com\/watch?v=PGtv-dBi2wE\">Art of Code<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Infinitely Tiling Space &#8211; The Cheap Way<\/h2>\n\n\n\n<p>Given that your geometry is confined to a cube (0, 0, 0) to (1, 1, 1), the easiest way to have your geometry repeat infinitely is to simply take the current ray position modulo 1 before testing it against the geometry.<\/p>\n\n\n\n<p>This works if your scene is small compared to the cube.  If your scene is up to about a third of the size of the cube then it will appear okay, however you will start getting artefacts beyond that.  This is because for large objects, the ray may start inside an object.<\/p>\n\n\n\n<p>If having the scene small is an acceptable compromise, this way is much faster to compute.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Infinitely Tiling Space &#8211; The Proper Way<\/h2>\n\n\n\n<p>The solution to correctly tiling in space is quite simple &#8211; test for intersection with the bounding volume as well as the scene geometry.  If the ray intersects the bounding volume then simply wrap it using the modulus operator and continue marching.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"220\" src=\"https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/wrapping-in-unit-space-1.png\" alt=\"\" class=\"wp-image-54\"\/><figcaption>Wrapping in Unit Space<\/figcaption><\/figure><\/div>\n\n\n\n<p>Testing for intersection with the unit bounding cube is simple: because it is axis-aligned, it can be decomposed into six intersections with axis-aligned infinite planes, meaning that each axis can be considered independently.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"169\" height=\"149\" src=\"https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/axis-aligned-distance.png\" alt=\"\" class=\"wp-image-50\"\/><figcaption>Distance from Axis Aligned Plane<\/figcaption><\/figure><\/div>\n\n\n\n<p>It is important that intersections are only registered when exiting the space, not when entering it, so the direction of the ray must also be considered. Otherwise, when the point was wrapped from the distal side, it would immediately collide with the proximal side.<\/p>\n\n\n\n<p>GLSL code for finding the distance between the ray and the closest face on the unit cube is given below.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-clike\">\/**\n * Find the shortest distance to the inside\n * of an axis aligned unit cube.\n *\n * @param vec3 position The position of the ray to test.\n * @param vec3 rayDirection The direction of the ray.\n *\n * @return float Returns the distance between the point and the cube.\n *\/\nfloat unitSpaceDistance(vec3 position, vec3 rayDirection)\n{\n    float dx, dy, dz;\n    \n    if (rayDirection.x &lt; 0.0)\n        dx = position.x;\n    else\n        dx = 1.0 - position.x;\n        \n    if (rayDirection.y &lt; 0.0)\n        dy = position.y;\n    else\n        dy = 1.0 - position.y;\n        \n    if (rayDirection.z &lt; 0.0)\n        dz = position.z;\n    else\n        dz = 1.0 - position.z;\n        \n    return min(min(dx, dy), dz);        \n}<\/code><\/pre>\n\n\n\n<p>The ray marching function must then be modified to consider intersections with both the scene geometry and the bounding volume:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-clike\">\/**\n * March rays through an infinitely tiled unit space.\n * \n * @param vec3 rayOrigin    The starting point of the ray (camera position).\n * @param vec3 rayDirection The direction of the ray, in world space.\n *\n * @return vec3 Returns the distance from the ray origin at which the ray\n *              intersects the scene.\n *\/\nfloat rayMarchUnitSpace(vec3 rayOrigin, vec3 rayDirection)\n{\n    const int MAX_STEPS = 300;\n    const float RAY_INTERSECTION_DISTANCE = 0.001;\n    float distanceFromOrigin = 0.0;\n    float totalDistance = 0.0;\n    rayOrigin = mod(rayOrigin, 1.0);\n    \n    int i;\n    \n    for (i = 0; i &lt; MAX_STEPS; ++i)\n    {\n        vec3 rayPosition = rayOrigin + distanceFromOrigin * rayDirection;\n        float surfaceDistance = getSurfaceDistance(rayPosition);\n        float unitSpaceDistance = unitSpaceDistance(rayPosition, rayDirection);\n        \n        if (unitSpaceDistance &lt; surfaceDistance)\n        {\n            distanceFromOrigin += unitSpaceDistance;\n            totalDistance += unitSpaceDistance;\n            \n            if (unitSpaceDistance &lt; RAY_INTERSECTION_DISTANCE || totalDistance &gt; MAXIMUM_RAY_LENGTH)\n            {\n                rayOrigin = mod(rayOrigin + rayDirection * (distanceFromOrigin + RAY_INTERSECTION_DISTANCE), 1.0);\n                distanceFromOrigin = 0.0;\n            }\n        }\n        else\n        {\n            distanceFromOrigin += surfaceDistance;\n            totalDistance += surfaceDistance;\n\n            if (surfaceDistance &lt; RAY_INTERSECTION_DISTANCE || totalDistance &gt; MAXIMUM_RAY_LENGTH)\n                break;\n        }\n    }\n    \n    if (i == MAX_STEPS)\n        return MAXIMUM_RAY_LENGTH;\n    \n    return min(MAXIMUM_RAY_LENGTH, totalDistance);\n}<\/code><\/pre>\n\n\n\n<p>This code functions as follows:<\/p>\n\n\n\n<p>Firstly, the ray origin is wrapped into modulo-1 space.  This has the effect of bringing the camera into the unit space, if it is outside it.<\/p>\n\n\n\n<p>Ray marching then proceeds as normal, however two distances are calculated: the distance to the geometry as usual, and the distance to the bounding volume.<\/p>\n\n\n\n<p>If the ray intersects with the bounding volume, then it is moved forward by the intersection distance <code>di<\/code> to ensure that it passes outside the bounding volume, then wrapped back into unit space with the modulus operation.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"241\" height=\"201\" src=\"https:\/\/zebra-north.com\/code\/wp-content\/uploads\/2021\/08\/ray-march.png\" alt=\"\" class=\"wp-image-51\"\/><figcaption>Advancing and Wrapping<\/figcaption><\/figure><\/div>\n\n\n\n<p>Notice in the figure how the ray has advanced to within the intersection distance <code>di<\/code> of the right-hand edge of the bounding area, but it has not actually passed the edge.  If the modulus operation were to be applied now, the point would remain unmoved.  To compensate for this, the ray is advanced by an additional <code>di<\/code> to ensure that it passes out of the bounding volume, and the modulus operation wraps it back to the inside.<\/p>\n\n\n\n<p>Two distances are tracked, the current length of the ray, and the total length.  The current length is used to keep track of the position while marching, and the total length is the final distance between the camera and the intersected surface.<\/p>\n\n\n\n<p>This is all that is required.  As long as the geometry fits within the unit cube, all the standard ray marching functions can be used.<\/p>\n\n\n\n<p>The resulting shader is shown below.  The source code can be found at <a href=\"https:\/\/www.shadertoy.com\/view\/3l3czB\">https:\/\/www.shadertoy.com\/view\/3l3czB<\/a>.<\/p>\n\n\n\n<script class=\"wp-block-zebra-north-webgl-shader\" type=\"text\/javascript\">webglShader(\"\/\/ The maximum distance to cast a ray when ray marching.\\nconst float MAXIMUM_RAY_LENGTH = 100.0;\\n\\n\/**\\n * Calculate the distance from a position to the surface of a sphere.\\n *\\n * @param vec3 position The position to test.\\n * @param vec3 centre   The centre of the sphere.\\n * @param vec3 radius   The radius of the sphere.\\n *\\n * @return float Returns the distance from the position to the sphere's surface.\\n *\/\\nfloat sphereDistance(vec3 position, vec3 centre, float radius)\\n{\\n    return length(position - centre) - radius;\\n}\\n\\n\/**\\n * Calculate the distance to a horizontal plane at z = 0.\\n *\\n * @param vec3 position The position to test.\\n *\\n * @return float Returns the distance between the position and the ground plane.\\n *\/\\nfloat groundDistance(vec3 position)\\n{\\n    return position.y;\\n}\\n\\n\/**\\n * Calculate the distance to an infinite plane at centre\\n * with a given normal.\\n *\\n * @param vec3 position The position to test.\\n * @param vec3 centre   A point that the plane passes through.\\n * @param vec3 normal   The surface normal of the plane.\\n *\\n * @return float Returns the distance between the position and the plane.\\n *\/\\nfloat planeDistance(vec3 position, vec3 centre, vec3 normal)\\n{\\n    \/\/ Take a vector from the plane to the point.\\n    vec3 w = position - centre;\\n    \\n    \/\/ Project the vector onto the normal.\\n    return dot(normal, w);    \\n}\\n\\n\/**\\n * Find the shortest distance to the inside\\n * of an axis aligned unit cube.\\n *\\n * @param vec3 position The position of the ray to test.\\n * @param vec3 rayDirection The direction of the ray.\\n *\\n * @return float Returns the distance between the point and the cube.\\n *\/\\nfloat unitSpaceDistance(vec3 position, vec3 rayDirection)\\n{\\n    float dx, dy, dz;\\n    \\n    if (rayDirection.x < 0.0)\\n        dx = position.x;\\n    else\\n        dx = 1.0 - position.x;\\n        \\n    if (rayDirection.y < 0.0)\\n        dy = position.y;\\n    else\\n        dy = 1.0 - position.y;\\n        \\n    if (rayDirection.z < 0.0)\\n        dz = position.z;\\n    else\\n        dz = 1.0 - position.z;\\n        \\n    return min(min(dx, dy), dz);        \\n}\\n\\n\/**\\n * Calculate the distance to a capsule.\\n *\\n * @param vec3  position The position to test.\\n * @param vec3  a        The position of the first end sphere.\\n * @param vec3  b        The position of the second end sphere.\\n * @param float radius   The radius of the end spheres.\\n *\\n * @return float Returns the distance between the position and the capsule.\\n *\/\\nfloat capsuleDistance(vec3 position, vec3 a, vec3 b, float radius)\\n{\\n    vec3 ap = position - a;\\n    vec3 bp = position - b;\\n    vec3 ab = b - a;\\n    \\n    \/\/ Find how far along the line a-b the closest point to p is.\\n    float t = dot(ap, ab) \/ dot(ab, ab);\\n    t = clamp(t, 0.0, 1.0);\\n    vec3 c = a + ab * t;\\n    \\n    return length(position - c) - radius;\\n}\\n\\n\/**\\n * Calculate the distance to a torus in the XZ plane.\\n *\\n * @param vec3  position    The position to test.\\n * @param vec3  centre      The centre of the torus.\\n * @param float majorRadius The main radius of the torus.\\n * @param flota minorRadius The radius of the tube.\\n *\/\\nfloat torusDistance(vec3 position, vec3 centre, float majorRadius, float minorRadius)\\n{\\n    \/\/ The torus is at the origin on the zx plane.\\n    \\n    \/\/ Move the position relative to the centre of the torus.\\n    position -= centre;\\n    \\n    \/\/ Get the distance from the position to the major radius on the xz plane.\\n    float x = length(position.xz) - majorRadius;\\n    \\n    \\n    return length(vec2(x, position.y)) - minorRadius;\\n}\\n\\n\/**\\n * Calculate the distance to a cube.\\n *\\n * @param vec3 position The position to test.\\n * @param vec3 centre   The centre of the cube.\\n * @param vec3 size     The length, width, and height.\\n *\\n * @return Returns the distance from the position to the closest point on the object.\\n *\/\\nfloat boxDistance(vec3 position, vec3 centre, vec3 size)\\n{\\n    position -= centre;\\n    \\n    \/\/ Rounded corners.\\n    float radius = 0.1;\\n    \\n    return length(max(abs(position) - size, 0.0)) - radius;\\n    \\n}\\n\\n\/**\\n * Calculate the distance to a cylinder.\\n *\\n * @param vec3  position The position to test.\\n * @param vec3  a        The first end point of the cylinder.\\n * @param vec3  b        The second end point of the cylinder.\\n * @param float radius   The radius of the cylinder.\\n *\\n * @return float Returns the distance from the position to the closest point on the object.\\n *\/\\nfloat cylinderDistance(vec3 position, vec3 a, vec3 b, float radius)\\n{\\n    vec3 ap = position - a;\\n    vec3 bp = position - b;\\n    vec3 ab = b - a;\\n    \\n    \/\/ Find how far along the line a-b the closest point to p is.\\n    float t = dot(ap, ab) \/ dot(ab, ab);\\n\\n    vec3 c = a + ab * t;\\n    float d = length(position - c) - radius;\\n    \\n    float y = (abs(t - 0.5) - 0.5) * length(ab);\\n    float e = length(max(vec2(d, y), 0.0));\\n    \\n    float i = min(max(d, y), 0.0);\\n    \\n    return e + i;\\n}\\n\\n\\n\/**\\n * Get the distance from the given position to the nearest\\n * point on a surface of an object in the scene.\\n *\\n * @param vec3 position The position to check.\\n *\\n * @return float Returns the minium distance from the position to a surface.\\n *\/\\nfloat getSurfaceDistance(vec3 position)\\n{\\n    \/\/ Uncomment one of these to select which object to render.\\n\\n    \/\/return torusDistance(position, vec3(0.5, 0.5, 0.5), 0.2, 0.05);\\n    \/\/return sphereDistance(position, vec3(0.5, 0.5, 0.5), 0.4);\\n    return boxDistance(position, vec3(0.5, 0.5, 0.5), vec3(0.1, 0.1, 0.1));\\n}\\n\\n\/**\\n * Get the surface normal at the given point in world space.\\n * This works by finding surface points a small distance away\\n * from the position.\\n *\\n * @param vec3 position The position at which to find the normal.\\n *\\n * @return vec3 Returns a normal.\\n *\/\\nvec3 getSurfaceNormal(vec3 position)\\n{\\n    vec2 epsilon = vec2(0.01, 0.0);\\n    float dist = getSurfaceDistance(position);\\n    vec3 normal = dist - vec3(\\n        getSurfaceDistance(position - epsilon.xyy),\\n        getSurfaceDistance(position - epsilon.yxy),\\n        getSurfaceDistance(position - epsilon.yyx));\\n        \\n    return normalize(normal);\\n}\\n\\n\/**\\n * Wrap a position so it lies within\\n * a unit cube from (0, 0, 0) to (1, 1, 1).\\n *\\n * @param vec3 position The position to wrap.\\n *\\n * @return vec3 Returns the position within a unit cube.\\n *\/\\nvec3 unitSpace(vec3 position)\\n{\\n    return mod(position, 1.0);\\n}\\n\\n\/**\\n * Perform ray marching in the scene.\\n * \\n * @param vec3 rayOrigin    The starting point of the ray (camera position).\\n * @param vec3 rayDirection The direction of the ray, in world space.\\n *\\n * @return vec3 Returns the distance from the ray origin at which the ray\\n *              intersects the scene.\\n *\/\\nfloat rayMarch(vec3 rayOrigin, vec3 rayDirection)\\n{\\n    const int MAX_STEPS = 100;\\n    const float RAY_INTERSECTION_DISTANCE = 0.01;\\n    \\n    float distanceFromOrigin = 0.0;\\n    \\n    for (int i = 0; i < MAX_STEPS; ++i)\\n    {\\n        vec3 rayPosition = rayOrigin + distanceFromOrigin * rayDirection;\\n        float surfaceDistance = getSurfaceDistance(rayPosition);\\n        distanceFromOrigin += surfaceDistance;\\n        \\n        if (surfaceDistance < RAY_INTERSECTION_DISTANCE || distanceFromOrigin > MAXIMUM_RAY_LENGTH)\\n            break;\\n    }\\n    \\n    return min(MAXIMUM_RAY_LENGTH, distanceFromOrigin);\\n}\\n\\n\/**\\n * March rays through an infinitely tiled unit space.\\n * \\n * @param vec3 rayOrigin    The starting point of the ray (camera position).\\n * @param vec3 rayDirection The direction of the ray, in world space.\\n *\\n * @return vec3 Returns the distance from the ray origin at which the ray\\n *              intersects the scene.\\n *\/\\nfloat rayMarchUnitSpace(vec3 rayOrigin, vec3 rayDirection)\\n{\\n    const int MAX_STEPS = 300;\\n    const float RAY_INTERSECTION_DISTANCE = 0.001;\\n    float distanceFromOrigin = 0.0;\\n    float totalDistance = 0.0;\\n    rayOrigin = unitSpace(rayOrigin);\\n    \\n    int i;\\n    \\n    for (i = 0; i < MAX_STEPS; ++i)\\n    {\\n        vec3 rayPosition = rayOrigin + distanceFromOrigin * rayDirection;\\n        float surfaceDistance = getSurfaceDistance(rayPosition);\\n        float unitSpaceDistance = unitSpaceDistance(rayPosition, rayDirection);\\n        \\n        if (unitSpaceDistance < surfaceDistance)\\n        {\\n            distanceFromOrigin += unitSpaceDistance;\\n            totalDistance += unitSpaceDistance;\\n            \\n            if (unitSpaceDistance < RAY_INTERSECTION_DISTANCE || totalDistance > MAXIMUM_RAY_LENGTH)\\n            {\\n                rayOrigin = unitSpace(rayOrigin + rayDirection * (distanceFromOrigin + RAY_INTERSECTION_DISTANCE));\\n                distanceFromOrigin = 0.0;\\n            }\\n        }\\n        else\\n        {\\n            distanceFromOrigin += surfaceDistance;\\n            totalDistance += surfaceDistance;\\n\\n            if (surfaceDistance < RAY_INTERSECTION_DISTANCE || totalDistance > MAXIMUM_RAY_LENGTH)\\n                break;\\n        }\\n    }\\n    \\n    if (i == MAX_STEPS)\\n        return MAXIMUM_RAY_LENGTH;\\n    \\n    return min(MAXIMUM_RAY_LENGTH, totalDistance);\\n}\\n\\n\/**\\n * Calculate Phong shading.\\n *\\n * @param vec3  surfacePoint    The point being shaded.\\n * @param vec3  normal          The surface normal.\\n * @param vec3  lightPosition   The location of the light.\\n * @param vec3  viewerDirection A unit vector from the surface point in the direction of the camera.\\n * @param float ambient         The ambient lighting, 0..1.\\n * @param float diffuse         The diffuse lighting, 0..1.\\n * @param float specular        The specular highlight strength, 0..1.\\n * @param float shininess       The highlight sharpness, 0.. higher gives a smaller reflection.\\n *\/\\nfloat phong(vec3 surfacePoint, vec3 normal, vec3 lightPosition, vec3 viewerDirection, float ambient, float diffuse, float specular, float shininess)\\n{\\n    vec3 lightDirection = normalize(lightPosition - surfacePoint);\\n    \\n    vec3 reflection = normalize(2.0 * dot(lightDirection, normal) * normal - lightDirection);\\n    return ambient + diffuse * clamp(dot(lightDirection, normal), 0.0, 1.0) + specular * pow(clamp(dot(reflection, viewerDirection), 0.0, 1.0), shininess);\\n}\\n\\n\/**\\n * Calculate the surface shading at the intersection point.\\n *\\n * @param vec3  position      The point on the surface.\\n * @param vec3  lightPosition The position of the light source.\\n * @param float ambient       The amount of ambient illumination.\\n * @param float diffuse       The brightness of the diffuse lighting, 0..1.\\n * @param float specular      The brightness of the specular highlight, 0..1.\\n * @param float shininess     The sharpness of the specular highlight, higher being sharper.\\n *\\n * @return float Returns the brightness at the given position.\\n *\/\\nfloat shade(vec3 position, vec3 lightPosition, vec3 cameraPosition, float ambient, float diffuse, float specular, float shininess)\\n{\\n    vec3 surfaceNormal = getSurfaceNormal(position);\\n    vec3 lightDirection = normalize(lightPosition - position);\\n    vec3 pointToCamera = normalize(cameraPosition - position);\\n\\n    float brightness = phong(position, surfaceNormal, lightPosition, pointToCamera, ambient, diffuse, specular, shininess);\\n    clamp(dot(lightDirection, surfaceNormal), 0.0, 1.0);\\n    \\n    \/*\\n    float distanceTowardsLight = rayMarch(position + surfaceNormal * 0.02, lightDirection);\\n    \\n    if (distanceTowardsLight < max(MAXIMUM_RAY_LENGTH, length(lightPosition - position) + 0.02))\\n      return ambient;\\n    *\/\\n    \\n    float distance = length(position - cameraPosition);\\n    \\n    \/\/ Apply fog.\\n    brightness \/= max(distance - 2.0, 0.001);\\n    \\n    return brightness;\\n}\\n\\n\/**\\n * Build a rotation matrix.\\n *\\n * @param float roll  Rotation around the x axis in radians.\\n * @param float pitch Rotation around the y axis in radians.\\n * @param float yaw   Rotation around the z axis in radians.\\n *\\n * @return mat3 Returns a rotation matrix with the given Euler angles.\\n *\/\\nmat3 rotationMatrix3d(float roll, float pitch, float yaw)\\n{\\n    float sinRoll = sin(roll);\\n    float cosRoll = cos(roll);\\n    float sinPitch = sin(pitch);\\n    float cosPitch = cos(pitch);\\n    float sinYaw = sin(yaw);\\n    float cosYaw = cos(yaw);\\n      \\n    return mat3(\\n        vec3(cosRoll * cosPitch, cosRoll * sinPitch * sinYaw - sinRoll * cosYaw, cosRoll * sinPitch * cosYaw + sinRoll * sinYaw),\\n        vec3(sinRoll * cosPitch, sinRoll * sinPitch * sinYaw + cosRoll * cosYaw, sinRoll * sinPitch * cosYaw - cosRoll * sinYaw),\\n        vec3(-sinPitch, cosPitch * sinYaw, cosPitch * cosYaw));\\n}\\n\\n\/**\\n * The fragment shader function.\\n *\\n * @param vec4 fragColor The output fragment colour.\\n * @param vec2 fragCoord The UV coordinates of the fragment.\\n *\/\\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\\n{\\n    \/\/ Normalized pixel coordinates (from 0 to 1).\\n    vec2 uv = ((fragCoord - iResolution.xy \/ 2.0) \/ iResolution.y);\\n    \\n    \/\/ Set up the camera.\\n    vec3 cameraPosition = vec3(5.0, 0.0, 5.0) * sin(iTime * 0.5);\\n    mat3 cameraRotation = rotationMatrix3d(iTime * 0.1, iTime, iTime * 0.3);\\n    \\n    \/\/ Set up the ray from the camera to the scene.\\n    vec3 rayDirection = vec3(uv.x, uv.y, 1.0);\\n    rayDirection = normalize(rayDirection);\\n    rayDirection *= cameraRotation;\\n    \\n    \/\/ Perform ray marching to find what the ray intersects.\\n    float surfaceDistance = rayMarchUnitSpace(cameraPosition, rayDirection);\\n    \\n    vec3 surfacePoint = cameraPosition + surfaceDistance * rayDirection;\\n    \\n    \/\/ Set up the light source.\\n    vec3 lightPosition = vec3(0.0, 5.0, 6.0);\\n    lightPosition.xz += vec2(sin(iTime), cos(iTime)) * 2.0;\\n  \\n    \/\/ Find the colour at the point of intersection.\\n    vec3 col = vec3(1.0 - shade(surfacePoint, lightPosition, cameraPosition, 1.0, 0.0, 0.0, 10.0));\\n\\n    \/\/ Set the output colour.\\n    fragColor = vec4(col, 1.0);\\n}\\n\");<\/script>\n","protected":false},"excerpt":{"rendered":"<p>Ray marching is a useful technique for rendering complex scenes, but what if you want that scene repeated throughout space, infinitely in all directions?<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[],"class_list":["post-34","post","type-post","status-publish","format-standard","hentry","category-shader"],"_links":{"self":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/34","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/comments?post=34"}],"version-history":[{"count":10,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/34\/revisions"}],"predecessor-version":[{"id":65,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/34\/revisions\/65"}],"wp:attachment":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/media?parent=34"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/categories?post=34"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/tags?post=34"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}