1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
const STAR_MAX_COUNT = 16;
const BLACKHOLE_MAX_COUNT = 5;
const STAR_COLOR = [0.956,0.5,0.215];
var vShader =`
uniform mat4 uProjectionMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uModelMatrix;
uniform mat4 uNormalMatrix;
uniform bool uUseNormalMatrix;
uniform vec3 uAmbientColor;
uniform vec3 uDiffuseColor;
uniform vec3 uLightDir;
attribute vec3 aXYZ;
attribute vec3 aColor;
attribute vec3 aNormal;
varying vec3 vColor;
varying vec3 vXYZ;
void main ()
{
mat4 mvMatrix = uViewMatrix * uModelMatrix;
gl_Position = uProjectionMatrix * mvMatrix * vec4(aXYZ,1);
mat4 nMatrix = uUseNormalMatrix?uNormalMatrix:mvMatrix;
vColor = uAmbientColor*aColor;
vec3 light = normalize(-uLightDir);
vec3 normal = vec3(normalize(nMatrix*vec4(aNormal,0)));
vColor += aColor*uDiffuseColor*max(dot(normal,light),0.0);
vXYZ = (uModelMatrix * vec4(aXYZ,1)).xyz;
}
`;
var fShader = `
precision highp float; // Needed, since we'll get uniforms which were in vertex shader
// Distance a ray should travel inside a black hole,
// changing it can alter weirdly the amount of rays that don't escape the black hole
#define MAGIC_STEP 0.01
varying vec3 vColor;
varying vec3 vXYZ;
// This is dumb, but we can't just pass the color from vertex shader, because
// we could need it when the fragment shader is ran on the skybox (or just not on a star)
// And in those cases, we recalculate the star color, based on where our ray landed
uniform vec3 uStarColor;
// Settings
uniform bool uDisableRayMarching;
uniform vec4 uBlack; // color when ray doesn't hit anything (or hits the skybox)
uniform vec4 uBHBlack; // color when ray doesn't escape the black hole
// For ray marching
uniform vec4 uStars[`+STAR_MAX_COUNT+`];
uniform vec4 uBH[`+BLACKHOLE_MAX_COUNT+`];
uniform vec3 uEye;
// For light recalculation
uniform mat4 uProjectionMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uModelMatrix;
uniform mat4 uNormalMatrix;
uniform bool uUseNormalMatrix;
uniform vec3 uAmbientColor;
uniform vec3 uDiffuseColor;
uniform vec3 uLightDir;
// Direction implementation of the math from
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation
float linePointDistance(vec3 linePoint, vec3 lineDir, vec3 point) {
vec3 projectionDir = dot(point - linePoint, lineDir) * lineDir; // aka projectionVector - linePoint
float dist = length((point - linePoint) - projectionDir); // aka length of perpendicular vector
// if pointing in opposite directions, meaning if the cloest point is behind us
if (dot(projectionDir, lineDir) < 0.0)
return -dist;
return dist;
}
// Direct implementation of the math from
// https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection#Calculation_using_vectors_in_3D
vec3 spherePoint(vec3 lineOrigin, vec3 lineDir, vec4 sphere) {
lineDir = normalize(lineDir);
float b = dot(lineDir, lineOrigin - sphere.xyz);
float Delta = b * b - (length(lineOrigin - sphere.xyz) * length(lineOrigin - sphere.xyz) - sphere.a * sphere.a);
return lineOrigin + min(-b + sqrt(Delta), -b - sqrt(Delta)) * lineDir;
}
// Direct implementation of the math from
// https://en.wikipedia.org/wiki/Dot_product#Geometric_definition
float angleBetweenVectors(vec3 v1, vec3 v2) {
return acos(radians(dot(v1, v2) / (length(v1) * length(v2))));
}
int rayMarchInBH(vec4 BH, inout vec3 ray, inout vec3 step) {
ray = spherePoint(ray, step, vec4(BH.xyz, BH.a * 3.0));
// h is used in the step changing calculation
float h = length(cross(ray, step));
for (int i = 0; i < 100; i++) {
float dist = distance(ray, BH.xyz);
// if inside event horizon
if (dist <= BH.a) {
return 1;
}
// if outside ISCO
if (dist > BH.a * 3.0 + 1.0) { // adding 1 fixes some rays wrongfully passing directly through
return 0;
}
// Gravity bends light (actually spacetime, which in turn changes it's path for outside observers).
// This is a more or less correct (as far as I can tell) implementation of Riccardo Antonelli's formula
// http://rantonels.github.io/starless/#MathJax-Element-29-Frame
// Though I found it difficult to understand, so I referred to some of it's implementations from
// https://github.com/rantonels/starless/blob/7a0c3c4efdc0666f53dcfa48d310fb2164be2d1a/tracer.py
// and
// https://github.com/dbrant/BlackHoleRaytracer/blob/master/Equation/SchwarzschildBlackHoleEquation.cs
step += normalize(ray - BH.xyz) * -1.5 * (h*h) * MAGIC_STEP / (dist*dist*dist*dist*dist);
ray += step;
}
return 1; // orbits inside
}
vec4 calculateColor(bool notBent, vec4 star, vec3 ray, vec3 step) {
if (notBent) {
return vec4(vColor,1);
}
// This is an exact copy from the vertex shader light calculations
// Since the ray was bent, we don't know what exactly it will hit, so we need to recalculate
// This probably isn't entirely correct, but I like how it looks
vec3 anormal = spherePoint(ray, step, star) - star.xyz;
mat4 mvMatrix = uViewMatrix * uModelMatrix;
mat4 nMatrix = uUseNormalMatrix?uNormalMatrix:mvMatrix;
vec3 color = uAmbientColor*uStarColor;
vec3 light = normalize(-uLightDir);
vec3 normal = vec3(normalize(nMatrix*vec4(anormal,0)));
color += uStarColor*uDiffuseColor*max(dot(normal,light),0.0);
return vec4(color, 1);
}
// Does the ray, when going in the direction step, eventually hit the surface of the sphere
bool hitsSphere(vec3 ray, vec3 step, vec4 sphere) {
float dist = linePointDistance(ray, step, sphere.xyz);
return dist >= 0.0 && dist < sphere.a;
}
// I think this slightly differs than the norm, but ray is the current position of my ray
// and step is a direction vector, so moving the ray forward is as simple as ray += step
vec4 rayMarch(vec3 ray, vec3 step) {
// Does the ray pass through any blackholes
int bhStatus = 2;
for (int i = 0; i < `+BLACKHOLE_MAX_COUNT+`; i++) {
if (hitsSphere(ray, step, vec4(uBH[i].xyz, uBH[i].a * 3.0))) {
// Try to enter black hole, only if there isn't a star that is in front
float bhDist = distance(uBH[i].xyz, ray);
for (int j = 0; j < `+STAR_MAX_COUNT+`; j++) {
if (distance(uStars[j].xyz, ray) < bhDist && hitsSphere(ray, step, uStars[j]))
bhDist = -1.0;
}
if (bhDist > -1.0)
bhStatus = rayMarchInBH(uBH[i], ray, step);
}
}
if (bhStatus == 1) {
return uBHBlack;
}
// Does the ray hit a star (even when it has escaped a black hole)
for (int i = 0; i < `+STAR_MAX_COUNT+`; i++) {
if (hitsSphere(ray, step, uStars[i])) {
return calculateColor(bhStatus == 2, uStars[i], ray, step);
}
}
return uBlack;
}
void main()
{
vec3 step = normalize(vXYZ - uEye);
gl_FragColor = (uDisableRayMarching) ? vec4(vColor, 1.0) : rayMarch(uEye, step);
}
`;
|