By Harry Chen (harry7557558) - I couldn't find a satisfying tool on the internet, so I made one. I was inspired by raymarching demos on Shadertoy.
This tool implements the raymarching algorithm to render 3D implicit surfaces. Type equations in the input
box, or look at some examples. Drag the canvas to rotate the surface, and scroll to zoom in/out. Drag while
holding Shift
to move the graph on the screen. Reset the viewport by switching to an example
and switching back (you may want to backup your input). Try to play with different selectors, checkboxes,
and sliders.
You need a device/browser that supports WebGL 2 to
run this tool. An FPS counter will be available if your browser supports the
EXT_disjoint_timer_query_webgl2
extension. If the graph takes too long to update, uncheck the
"auto-update" checkbox and apply your change by clicking the "update" button or pressing
Alt+Enter
.
A preview of the equation is available via MathJax. You can drag and move it, or turn it off/on by unchecking/checking the "equation preview" checkbox.
Use $x, y, z$ as independent variables. Write your equation in the form $f(x,y,z)=0$ or $f(x,y,z)=g(x,y,z)$.
Use ^
for power/exponentiation, *
for multiplication and /
for
division. You can use built-in functions like abs()
, sin()
, and
sqrt()
. Note that log(x)
calculates the natural logarithm by default. For the
common logarithm, type log(10,x)
instead.
Defining variables: A variable name starts with a letter, followed by an (optional) underscore and a
string of letters or numbers. Example variable names are k
, x0
($x_0$, equivalent
to x_0
), x_t
($x_t$) and A_11
($A_{11}$). For example, you can define
a=x+y
and enter z=a*sin(a)
as the main equation.
Defining functions: A function name is similar to a variable name. A function may be defined as
f(t)=t*sin(t)
and called like z=f(x)*f(y)
, or defined as
g(a,b)=sin(a)*cos(b)
and called like z=g(x+y,x-y)
.
Comments: A comment can be a single line or after a line of expression, starting with the character
#
. (Check the "Atan2 Spiral" example)
Quality: A higher quality means a smaller raymarching step, which is usually slower but produces a more accurate image.
Y-up: A majority of math textbooks use the z-axis as the vertical axis. Check this checkbox if you prefer y as the vertical axis.
Grid: When checked, this tool will display an adaptive grid on the surface, making it easier to see the size of the object and read the coordinates of a point.
Transparency: Check this if you want the surface to be semi-transparent so you can look through it. (Try the "A5 Star" example.) Warn that this may decrease the accuracy of the rendering.
Analytical gradient: When this is checked, this tool evaluates the analytical gradient of the function used in ray-surface intersection and shading. Otherwise, it approximates the line derivative from previous raymarching samples, which is typically faster but less accurate. Raymarching step size is reduced to balance speed and quality when this is unchecked.
Discontinuity: In this tool, the surface is defined by a set of points with changes of sign, which is either a zero or a discontinuity. Check this to detect and red highlight discontinuity. (Try the "Sin Terrace" example.)
Lighting angles: As the θlight slider is dragged from left to right, the light moves from bottom to bottom counter-clockwise. As the φlight slider is dragged from left to right, the light moves from front to back. The light rotates to fit this description as the viewport rotates.
Default: (not really the default mode) This mode displays a light gray, glazed surface. You may or may not see a slight tint depending on your device's display setting.
Normal: This mode calculates the albedo of the surface based on the surface normal (normalized gradient). Red corresponds the x-direction, green corresponds the y-direction, blue corresponds the z-direction. When the component of the normal is more positive along a direction, the corresponding color component is stronger. Visually, the green part has the most positive y normal.
Gradient: This mode colors the surface based on the magnitude of the gradient. The surface appears bluer when the magnitude of the gradient is closer to an integer power of 100, like 0.01, 1, 100, and more orange as it departs. For a perfect SDF, you should see a clean dark blue color. For where the gradient approaches zero or infinity, there may be alternating blue and orange "stripes." (check the "A6 heart" example)
This tool implements the raymarching algorithm in WebGL fragment shaders. It casts rays from the camera and numerically finds its intersections with the surface. The raymarching step size is calculated by dividing the value of the scalar field by the magnitude of the directional derivative along the ray (in screen space) and clamped based on a given step size, which can be changed through the "quality" selector.
In the first pass, it marches along the ray to determine an interval where intersections may exist. Then, the result is pooled using min/max functions with neighboring pixels to avoid missing intersections. These two passes are done in 0.25x of the screen resolution.
The main raymarching function checks intersections within the calculated intervals. For opaque surfaces, a bisection search is performed when the first sign change is detected, and the color is calculated and returned. For a semi-transparent surface, it approximates intersections using linear interpolation and calculates and accumulates the color each time a sign change is detected.
The rendered image goes through an anti-aliasing pass. This pass uses a filter based on linear regression to anti-alias the image. A description and implementation of the algorithm can be found here.
The input entered is parsed in JavaScript. After preprocessing (ex. adding multiplication signs), it is parsed to the postfix notation using the shunting-yard algorithm. When generating GLSL code, the expression is evaluated while being divided into steps, and its analytical gradient expression is generated via automatic differentiation. Generated GLSL code is logged to the console when the input is updated, which can be found under the "Console" tab of the F12 developer tool.
The source code of this tool can be found on GitHub.