# CindyGL Tutorial - Rendering 3D Scenes

In this tutorial, we want to render a three-dimensional scene with `colorplot`

in CindyJS. We will demonstrate the basic indigents that can be used to build complex three-dimensional scenes or to investigate various rendering approaches.

We will build an applet 3d.html, which renders a 3D scene that can be rotated by dragging the mouse:

## Prerequisites

We assume that you already have seen some CindyScript and the core functionality of the `colorplot`

-command, for example in the CindyGL live coding tutorial. For the last part, it is good to know how to create your CindyJS-applets or having some experience with the CindyJS editor.

## Basic concepts of Raytracing

Let us assume that a camera is located at a particular position, for instance at `origin = [0,1,-3]`

and we are looking along the $z$-axis. For the beginning, let us try to render the plane $y=0$.

How can this be archived through a `colorplot`

on the GPU?

The key idea is that with each pixel a direction `dir`

is associated, which varies for each pixel. The pixel in the middle of the rendered screen should "look" along the z-axis, hence for it, we have `dir = [0,0,1]`

. For pixels that are slightly right of the pixel in the middle, this variable could take the value `dir = [0,0.1,1]`

, meaning that the associated direction points slightly more to the right.

More general, we can write `dir = [#.x, #.y, 1]`

, where `#`

is the coordinate of the current pixel.

We will say that behind this pixel lies a ray

```
ray(t) := origin + t*dir
```

This ray will eventually intersect the scene for some $t>0$. Now, if we want to render the intersection of each ray with the plane $y=0$. We can solve $(\mathrm{origin} + t \cdot \mathrm{dir}).y = 0$ for $t$ and obtain

```
tfloor = -origin.y/dir.y;
```

If the computed `tfloor>0`

, then the ray behind the current pixel eventually will intersect the plane $y=0$, otherwise it wont intersect it (and we will display the color of a sky, for instance). Let us put this together:

```
origin = [0,1,-3];
colorplot(
dir = [#.x,#.y,1]; //ray(t):=origin+t*dir
//intersection with floor:
//ray(floort).y=0 <=> origin.y+floort*dir.y=0
floort = -origin.y/dir.y;
if(floort>0,
[1,1,1]*exp(-|floort|*.2), //ray hits floor
[.8,.8,1]*exp(-.3*dir.y) //sky
);
);
```

## Rendering a sphere

Now, instead of the floor, let us render a sphere. For simplicity, let us display the unit sphere located at (0,0,0).

When does `ray(t):=origin+t*dir`

intersect this sphere? This can be reformulated into the solution a quadratic polynomial:

$$
\begin{eqnarray}
|| \mathrm{ray}(t) || = 1 &\Leftrightarrow& || \mathrm{ray}(t) ||^2 = 1 \Leftrightarrow \langle \mathrm{ray}(t), \mathrm{ray}(t) \rangle = 1 \\
&\Leftrightarrow &
\langle \mathrm{origin}, \mathrm{origin} \rangle + 2 t \langle \mathrm{origin}, \mathrm{dir} \rangle + t^2 \langle \mathrm{dir}, \mathrm{dir} \rangle= 1 \\
&\Leftrightarrow& a t^2 + b t + c = 0 \text{ for $a=\langle \mathrm{dir}, \mathrm{dir} \rangle$, $b=2 \langle \mathrm{origin}, \mathrm{dir} \rangle$ and $c=\langle \mathrm{origin}, \mathrm{origin} \rangle$}\
\end{eqnarray}
$$
Hence, whenever the discriminant $D=b^2-4 a c$ of the polynomial $a t^2 + b t + c$ is non-negative, the ray intersects the sphere. Scalar products can be computed in CindyScript via `*`

. The two intersection points can be computed as $t_{1,2} = \frac{-b \pm \sqrt{b^2-4 a c}}{2 a}$. This can be used for the following program that "renders" a sphere:

```
origin = [0,1,-3];
colorplot(
dir = [#.x,#.y,1];
a = (dir*dir);
b = 2*(origin*dir);
c = origin*origin-1;
D = b^2-4*a*c; //discriminant
if(D>0, //there is some intersection
[1,.3,.3], //some reddish color
[.8,.8,1]*exp(-.3*dir.y) //sky
);
);
```

How can we colorize the sphere, which now shown as a reddish circle only, properly? One trick is that the coordinate on the unit sphere itself is already the normal of the sphere at this position. The normal can be used for approximating some diffuse reflection.

```
ray(t):= origin+t*dir;
origin = [0,1,-3];
colorplot(
dir = [#.x,#.y,1];
a = (dir*dir);
b = 2*(origin*dir);
c = origin*origin-1;
D = b^2-4*a*c; //discriminant
if(D>0, //there is some intersection
normal = ray((-b-re(sqrt(D)))/(2*a));
(normal*[1,1,-1])*[1,.3,.3],
[.8,.8,1]*exp(-.3*dir.y) //sky
);
);
```

## Combining multiple elements in a scene

Now, let us combine multiple elements within one scene. For each object $i$ we compute an intersection value $t_i$, i.e. the smallest value such that $\mathrm{ray}(t_i)$ intersects the object. For the corresponding pixel, we display the object $i$ which has the smallest non-negative value $t_i$. We might also say that each object has some different color and also the normal of each object should be taken into consideration. This could be programmed in CindyScript with the following helper function that is executed for each element:

```
updatehit(t, normal, color) := if(t>0 & t < hitt,
hitt = t;
hitpos = ray(t);
hitnormal = normal;
hitcolor = color;
);
```

If `updatehit(t, normal, color)`

is called, it will update the variables `hitt`

, `hitpos`

, `hitnormal`

and `hitcolor`

whenever the current parameters correspond to the so far closest object. The variable `hitt`

has to be initialized to $\infty$, within colorplot. Since there is no Infinity in CindyScript, we just choose a large float-value such as `1e8`

=$1\cdot 10^8$. Alltogether we can use the following code to render a scene consisting of a floor and a wall:

```
light = [cos(seconds()),2, sin(seconds())-1];
origin = [0,1,-3];
colorplot(
dir = [#.x,#.y,1];
//default values (if no hit)
hitt = 1e8; hitpos = hitnormal = hitcolor = [0,0,0];
//intersect with floor
updatehit((-origin.y)/dir.y, [0,1,0], [1,1,1]);
//intersect with wall at z=4:
updatehit((4-origin.z)/dir.z, [0,0,-1], [1,1,0.6]);
lightdir = (light-hitpos)/|light-hitpos|;
max(0,lightdir*hitnormal)*hitcolor;
);
```

Furthermore, we introduced a vector `light`

, which indicates the (moving) source of the light. Click "Enter Fullscreen" if the code covers your entire screen.

We can use the same scheme to futher add the unit-sphere with center $(0,0,0)$ as follows:

```
light = [cos(seconds()),2, sin(seconds())-1];
origin = [0,1,-3];
colorplot(
dir = [#.x,#.y,1];
//default values (if no hit)
hitt = 1e8; hitpos = hitnormal = hitcolor = [0,0,0];
updatehit((-origin.y)/dir.y, [0,1,0], [1,1,1]);
updatehit((4-origin.z)/dir.z, [0,0,-1], [1,1,0.6]);
//polynomial for |ray(t)|^2=1: sphere
a = (dir*dir); b = 2*(origin*dir); c = origin*origin-1;
D = b^2-4*a*c; //discriminant of polynomial a t^2 + b t + c
if(D>0,
spheret = (-b-re(sqrt(D)))/(2*a);
updatehit(spheret, ray(spheret), [1,0,0]);
);
lightdir = (light-hitpos)/|light-hitpos|;
max(0,lightdir*hitnormal)*hitcolor;
);
```

## Moving the camera

In order to avoid a lot of distracting code, we have encapsulated the code above into a helper-function `hitray()`

that is defined as follows and returns the color based on the set variables `dir`

and `origin`

```
hitray() := (
hitt = 1e8; hitpos = hitnormal = hitcolor = [0,0,0];
//floor
updatehit((-origin.y)/dir.y, [0,1,0], [1,1,1]);
//wall
updatehit((4-origin.z)/dir.z, [0,0,-1], [1,1,0.6]);
//sphere; polynomial for |ray(t)|^2=1
a = (dir*dir); b = 2*(origin*dir); c = origin*origin-1;
D = b^2-4*a*c; //discriminant of polynomial a t^2 + b t + c
if(D>0,
spheret = (-b-re(sqrt(D)))/(2*a);
updatehit(spheret, ray(spheret), [1,0,0]);
);
lightdir = (light-hitpos)/|light-hitpos|;
max(0,lightdir*hitnormal)*hitcolor;
);
```

and the entire scene can be now rendered as

```
origin = [0,1,-3];
colorplot(
dir = [#.x,#.y,1];
hitray() //computes the color of the intersection of ray(t):=origin+t*dir with the scene
);
```

Let us move the camera! A simple way of moving the is to change the variable `origin`

. In the examples above, you could replace `origin = [0,1,-3];`

with `origin = [0,2+sin(seconds()),-3]`

for instance:

```
origin = [0,2+sin(seconds()),-3];
colorplot(
dir = [#.x,#.y,1];
hitray() //computes the color of the intersection of ray(t):=origin+t*dir with the scene
);
```

## Pointing the camera to a target

How can point the camera to a certain target coordinate? Let us introduce a second variable
`target = [0,0,0];`

and demand that `dir`

for the pixel in the middle of the screen should be the vector pointing from `origin`

to `target`

. Let us compute this vector as `mdir = (target-origin)/|(target-origin)|;`

. Further, we can compute a orthogonal basis `(v,w,mdir)`

and and set `dir = mdir + #.x*v + #.y*w;`

. In order to build such a orthogonal basis, we have a freedom in rotating `v`

and `w`

along `mdir`

.

In this context, a good orthogonal basis has the vector `w`

pointing upwards such that the vertical lines on the screen align with the vertical lines in the image (no "Dutch angle"). Such a orthogonal basis can be computed with cross-products as follows:

```
target = [0,0,0];
origin = [cos(seconds())*2, sin(seconds()/3)+2, sin(seconds())*2];
mdir = (target-origin)/|(target-origin)|;
v = cross([0,1,0], mdir);
w = cross(mdir, v);
v = v/|v|;
w = w/|w|;
colorplot(
dir = mdir + #.x*v + #.y*w;
hitray();
);
```

Once this code is executed, the target at $(0,0,0)$ remains at the center of the screen.

## Interaction with the mouse

For many visualizations, it is good to let the user how to watch something. Suppose, we want to study a particular object at the `target = [0,0,0]`

and modify `origin`

with the mouse (by dragging). How can we do this?

The aim is to build an applet such as 3d.html, which renders a 3D scene that can be rotated by dragging the mouse.

In order to build this applet we set origin to be a point on a sphere specified by the coordinates `(lambda, phi)`

:

```
target = [0,0,0];
origin = 2*[cos(lambda)*cos(phi),
sin(phi),
sin(lambda)*cos(phi)
];
mdir = (target-origin)/|(target-origin)|;
v = cross([0,1,0], mdir);
w = cross(mdir, v);
v = v/|v|;
w = w/|w|;
colorplot(
dir = mdir + #.x*v + #.y*w;
hitray();
);
```

The values of `lambda`

and `phi`

are controlled essentially via the `mousedrag`

-script. In the live-editor of the tutorial you cannot modify these special scripts.
For this purpose, you can use the CindyJS online editor available at https://cindyjs.org/editor/.
Alternatively, if you are building your own applet based on a file such as boilerplate.html, you can either add some HTML-code

```
<script id="csmousedrag" type="text/x-cindyscript">
your code
</script>
```

The `mousedrag`

script is always executed if the browser notices that the mouse is moved while the button is pressed.

In this example we have set the `mousedrag`

-script to the following:

```
d = mouse()-lastmouse;
lambda = lambda-d.x;
phi = phi-d.y;
lastmouse = mouse();
```

Which means that whenever the mouse is dragged, the distance traveled by the mouse is also subtracted to the coordinates `(lambda, phi)`

. The script should always be well defined. So, in the `mousedown`

-script we should also set

```
lastmouse = mouse();
```

and in the `init`

-script we should assign some starting values to `lambda`

and `phi`

.