# Rendering: The Easy Case

In this chapter, I will explain the easiest case of non-Euclidean rendering: spaces that are homogeneous and isotropic.
This includes the most familiar types of non-Euclidean geometry: spherical and hyperbolic.

Why do I say this is easy? It turns out that OpenGL is just as happy to process these spaces as it is Euclidean geometry.
You don't need to switch to raytracing, or use complicated hacks, like you would for weirder spaces.

I plan to show you everything you need to know to render a simple scene using WebGL.
As a prerequisite, I will assume you understand how to do this in Euclidean geometry.

## Euclidean Example

Let's begin by looking at an example in Euclidean geometry. I'll just go over the interesting parts, but the full example is available [here]example_euclidean.html.

Here's my vertex shader.
```js
{{#include ../examples/euclidean.html:97:118}}
```
It's fairly standard. But notice that I chose the `vec4` representation:
- A point is stored as a `vec4` whose last coordinate is 1. 
- A vector is stored as a `vec4` whose last coordinate is 0.
- A transformation is stored as a `mat4`s whose last row is `0 0 0 1`.

This will ease the comparison to the non-Euclidean case later.

Some other minor notes:
- The shader outputs are in a player-centric coordinate system. This is just because I didn't want to deal with matrix inverses.
- `instance_transform` exists because I am using instanced rendering.

Now here's the fragment shader.
```js
{{#include ../examples/euclidean.html:120:163}}
```
Again, fairly standard. Ambient lighting, plus three sources of diffuse lighting.

Finally, the motion-handling code is fairly simple.
To move or rotate, just multiply by the relevant matrix.
<details>
<summary>Rotation</summary>

Note: The matrices are stored in column-major order, so they look like they're transposed.
```js
{{#include ../examples/euclidean.html:218:233}}
```
</details>
<details>
<summary>Motion</summary>

Note: The matrices are stored in column-major order, so they look like they're transposed.
```js
{{#include ../examples/euclidean.html:245:298}}
```
</details>

## Making it Non-Euclidean

All of this should be familiar, seen-it-before rendering code.
Now let's modify it to be non-Euclidean!
Specifically, let's render spherical geometry.

### Points and Vectors

The first thing to understand is how to represent points and vectors.

In Euclidean space, a point `p` was a `vec4` satisfying the equation `p.w = 1`.
In spherical space, a point `p` will instead be a `vec4` satisfying the equation `dot(p,p) = 1`.

Vectors are trickier.

In Euclidean space, we're used to having a consistent concept of direction, independent of your location.
But that doesn't work in spherical geometry.

Imagine you're at the north pole, looking down the 90°W meridian. You travel forward ten thousand kilometers.
You arrive at the Galapagos Islands, facing south.
Next, without turning, you travel rightward ten thousand kilometers.
You arrive in Kiribati (a country north-east of Australia), still facing south.
Finally, you back up ten thousand kilometers.
You are back at the north pole. But now you are looking down the 180°W meridian!
You never turned during the trip, yet you find yourself rotated by 90 degrees.

This phenomenon is called *holonomy*, and it occurs in any non-Euclidean space.
It means the concept of direction *depends on your location*.

If we draw two arrows right next to each other, we can obviously tell whether they're pointing in the same direction.
But if the arrows are far apart, it's less clear. We could try carrying one arrow over to the other, but then the answer would depend on which path we took.

A vector is just a length and a direction. So it also depends on your location.
Each point in our space comes with its own collection of tangent vectors.
We call this collection the *tangent space* of that point.


Let's return to our question:
*<p style="text-align: center;">How should we represent vectors?</p>*

We now know that this question should be rephrased:
*<p style="text-align: center;">At a given point `p` of our space, how should we represent its collection of tangent vectors?</p>*

I can now give an answer. A vector `v`, in the tangent space of point `p`, will be a `vec4` satisfying the equation `dot(p,v) = 0`.

### Computations

In any particular tangent space, vectors can be added, scaled, and dotted, exactly as normal.
But you shouldn't try to add or dot vectors from *different* tangent spaces.

Adding a point to a vector, or subtracting two points to get a vector, are a bit sketchier.
They're approximately correct for nearby points and small vectors, but they get worse as the points and vectors get larger.

For example, if `p` is a point, and `v` is a vector at that point, then `p + v` isn't quite a point:
```text
dot(p + v, p + v)
= dot(p, p + v) + dot(v, p + v)
= dot(p, p) + dot(p, v) + dot(v, p) + dot(v, v)
= 1 + 0 + 0 + dot(v, v)
= 1 + dot(v, v)
```
But if `v` is small, it might be close enough.

The distance between points `p` and `q` is given as `acos(dot(p, q))`.

A vector in the tangent space of `p`, pointing toward `q`, is given as `q - p * (dot(p,q)/dot(p,p))`.


### Vertex Shader

All right, let's start modifying the renderer! First up: the vertex shader.

And we're done!

That's right, *no changes need to be made*.

The data flowing through will be different. Instead of `pos.w = 1` and `norm.w = 0`, we'll have `dot(pos, pos) = 1` and `dot(pos, norm) = 0`.
Instead of a transformation matrix whose last row is `0 0 0 1`, we'll have a transformation matrix satisfying `M * Mᵀ = Mᵀ * M = 1`.

But the vertex shader doesn't care. It'll happily apply transformation matrices to points and vectors, no matter which geometry you're using.

```js
{{#include ../examples/spherical.html:110:131}}
```

### Fragment Shader

The fragment shader is more interesting, because it does the lighting calculations.

Consider the diffuse lighting from a particular light source.
We have to calculate the light intensity at the surface, and the angle at which the light hits the surface.
Both of those calculations work differently in spherical space.

First, consider the light intensity.

In Euclidean space, it's well known that the intensity of light is inversely proportional to the square of the distance.
This is because the light spreads out in all directions, and the surface area of a sphere is 4πr².

In spherical space, however, the surface area of a sphere is 4πsin²(r).
So if the light source is at point `p`, the light intensity at `q` should be inversely proportional to:
```text
4 * π * sin²(distance(p,q))
4 * π * sin²(acos(dot(p,q)))
4 * π * (1 - cos²(acos(dot(p,q))))
4 * π * (1 - (dot(p,q))²)
```

There is an interesting special case when `p ≈ -q`. Even though the surface is far from the light source, the light intensity is large.
This is because the curved space acts like a lens, focusing all of the light's energy back onto a point.

Now consider the angle calculation.

As in the Euclidean case, the computation is based on the dot product of two vectors: the normal, and the direction to the light source.
*Unlike* the Euclidean case, though, we need to be careful that both vectors are in the tangent space of the surface.


With these considerations in mind, here's my modified fragment shader.

```js
{{#include ../examples/spherical.html:133:177}}
```

### Transformation Matrices

A valid transformation matrix is one that turns valid points into valid points.

In Euclidean space, that's any matrix with last row `0 0 0 1`:
```text
⎡a b c d⎤ ⎡x⎤       ⎡a*x + b*y + c*z + d*1⎤       ⎡ax + by + cz + d⎤
⎢e f g h⎥ ⎢y⎥  ===  ⎢e*x + f*y + g*z + h*1⎥  ===  ⎢ex + fy + gz + h⎥
⎢i j k l⎥ ⎢z⎥  ===  ⎢i*x + j*y + k*z + l*1⎥  ===  ⎢ix + jy + kz + l⎥
⎣0 0 0 1⎦ ⎣1⎦       ⎣0*x + 0*y + 0*z + 1*1⎦       ⎣        1       ⎦
```

Matrices of this form look include rotations, reflections, translations, scalings, and shear transformations.

In spherical space, on the other hand, that's any matrix with `M * Mᵀ = Mᵀ * M = 1`:
```text
dot(M * v, M * v)
= dot(v, M * Mᵀ * v)
= dot(v, v)
= 1
```

Spherical rotation matrices look the same as Euclidean ones.
```text
⎡ cos(θ) 0  sin(θ) 0 ⎤
⎢   0    1    0    0 ⎥
⎢-sin(θ) 0  cos(θ) 0 ⎥
⎣   0    0    0    1 ⎦
```

But spherical translation matrices don't. In fact, they look kind of like rotation matrices:
```text
⎡ cos(θ) 0  0 sin(θ) ⎤
⎢   0    1  0   0    ⎥
⎢   0    0  1   0    ⎥
⎣-sin(θ) 0  0 cos(θ) ⎦
```

Just as in the Euclidean case, transformation matrices work on both points and vectors.
If `v` is in the tangent space of `p`, then `M*v` will be in the tangent space of `M*p`.

### Perspective Matrices

It turns out that perspective matrix formula works as normal, except you have to input `tan(znear)` and `tan(zfar)` instead of `znear` and `zfar`.

The maximum view distance is π, though this may be cut in half if you're using a library that "helpfully" requires the `zfar` input to be positive.

### Mesh Data

When changing the geometry, you will, of course, have to redesign the mesh and move the lights.

When doing so, keep in mind the note below.

## A Note on Distances

In Euclidean games, we're used to choosing whatever units we like.
Maybe we choose that "one unit" is a meter, or a centimeter.
It doesn't really matter, so you can choose whatever is convenient.

In spherical space, however, it really does matter. The universe is 2π units around, so don't make the player character 2 units tall!