-
Notifications
You must be signed in to change notification settings - Fork 0
/
bezier-funhouse.html
1387 lines (1199 loc) · 50 KB
/
bezier-funhouse.html
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>a Bézier funhouse</title>
<script id="glsl/trace-vs.c" type="x-shader/x-vertex">
//
// trace-vs.c
//
// Reed College CSCI 385 Computer Graphics Spring 2022
//
// This is a vertex shader that is used for rendering a simple
// ray-traced scene of some spheres and a mirrored object sitting
// in a room.
//
// Most of the ray tracing is performed in its companion fragment shader
// `trace-fs.c`. The code below basically sends info of the four corners
// of a quadrilateral so that they can be used as the virtual screen for
// our ray tracing.
//
attribute vec4 corner;
varying vec2 jitter; // Value in [-1,1]^2 for the ray offset.
uniform vec4 eyePosition; // Information about the viewer.
uniform vec4 intoDirection;
uniform vec4 rightDirection;
uniform vec4 upDirection;
uniform vec3 lightColor; // Color of the light.
uniform vec4 lightPosition; // Position of the light.
uniform int curvedMirror; // mirror shape (0 = sphere; 1 = curved)
uniform float sphereData[70]; // Position/size/color of spheres.
uniform int numSpheres; // Number of valid spheres in the above.
uniform float controlPoints[6]; // Control points of the Beziér mirror.
void main(void) {
// Place in right half of WebGL viewport coordinates.
gl_Position = vec4(corner.x+0.5, corner.y*2.0, 0.0, 1.0);
// Jitter cast ray by uniform grid of vectors \in [-1,1]^2.
jitter = vec2(corner.x, corner.y);
}
</script>
<script id="glsl/trace-fs.c" type="x-shader/x-vertex">
//
// trace-fs.c
//
// Reed College CSCI 385 Computer Graphics Spring 2022
//
// This is a fragment shader that colors a pixel by tracing a
// ray into a simple 3D scene. The scene consists of cubical
// room with matte-shaded walls, and several glossy spheres.
// There is also one mirrored object (a sphere or a Bezier
// "sheet") that reflects the scene to the viewer. There is
// one light source that illuminates the scene.
//
// The code is sent the light color and position, along with
// the ray tracing projection information from the front end.
// The key component of that projection info is a "jitter"
// vec2 that describes where each ray should be traced through
// the virtual screen.
//
// This `jitter` is sent from the vertex shader, giving an
// (x,y) location chosen uniformly from [-1,1]^2, one value
// pair for each pixel rendered by WebGL.
//
// The code is best further described under `main` and `trace`,
// and these two functions rely on several functions that
// compute intersections of a ray with walls, spheres, and the
// Bezier curved mirror.
//
// Several other pieces of information are sent from the front
// end, specifically 7 floating point values per sphere as
// the uniform array `sphereData`. These floating point values
// are described below within a `struct Sphere`, giving the
// location, size, and material properties of each sphere.
//
// There is also an array `curvePoints` which gives the floor
// 2D points specifying the foorprint of the curved Bezier
// mirror.
//
// Currently, the code uses Sphere #0 as a mirrored surface.
// This happens in the function `rayIntersectMirror`. Your
// assignment is to write this code so that the mirror is
// instead the curved Bezier sheet.
//
precision highp float;
precision highp int;
varying vec2 jitter; // Value from [-1,1]^2 for the ray offset.
uniform vec4 eyePosition; // Point to start tracing each ray.
uniform vec4 intoDirection; // Direction of the camera.
uniform vec4 rightDirection; // Camera right.
uniform vec4 upDirection; // Camera up.
uniform vec3 lightColor; // Color of the light.
uniform vec4 lightPosition; // Position of the light.
uniform int curvedMirror; // mirror shape (0 = sphere; 1 = curved)
uniform float sphereData[70]; // Position/size/color of spheres.
uniform int numSpheres; // Number of valid spheres in the above.
uniform float controlPoints[6]; // Control points of the Beziér mirror.
#define FAR 10.0
#define EPSILON 0.000001
#define ONE_PLUS_TOLERANCE 1.25
// struct Sphere (* not actually used! *)
//
// This struct serves to remind us of the layout of sphereData.
// There are seven consecutive floating point values that describe
// a sphere in the scene.
//
struct Sphere {
float cx; // Center of the sphere.
float cy;
float cz;
//
float r; // Radius of the sphere.
//
float red; // Material of the sphere.
float green;
float blue;
};
// struct Isect
//
// Struct for recording ray-object intersection info.
//
struct ISect {
int yes; // Is it an intersection? (1 = yes, 0 = no)
//
float distance; // If so, what's the distance from the ray's origin?
vec4 location; // Where did the ray intersect the object?
vec4 normal; // What was the surface normal where it intersected?
//
vec3 materialColor; // What are the surface material's properties?
int materialGlossy; // Is it glossy? (1 = glossy, 0 = matte)
};
ISect NO_INTERSECTION() {
//
// Returns an ISect struct representing "no intersection" with
// any scene object. Its `yes` component is set to 0, and the
// others are filled with bogus values.
//
// This always loses in the `bestISect` comparison described
// below.
//
return ISect(0, 0.0,
vec4(0.0,0.0,0.0,0.0),
vec4(0.0,0.0,0.0,0.0),
vec3(0.0,0.0,0.0), 0);
}
ISect bestISect(ISect info1, ISect info2) {
//
// Compare two intersections. If they are both `yes`, then
// it returns the closer of the two.
//
// If either is not a `yes` it returns the other.
//
if (info2.yes == 1
&& (info1.yes == 0
|| info2.distance < info1.distance)) {
return info2;
} else {
return info1;
}
}
ISect rayIntersectPlane(vec4 R, vec4 d, vec4 P, vec4 n) {
//
// Check if a ray intersects an oriented plane. Return an
// `ISect` describing that intersection.
//
// R, d: ray source point and direction.
// P, n: plane point and normal.
//
vec4 du = normalize(d);
//
// Check the origin's signed height above the plane.
//
float height = dot(R - P, n);
if (height < EPSILON) {
return NO_INTERSECTION();
}
//
// Check whether the ray hits the plane.
//
float hits = dot(-du, n);
if (hits < EPSILON) {
// If the ray is pointing away from the plane...
return NO_INTERSECTION();
}
// It hits the plane!
//
// Figure out the distance the plane is away from the ray's origin.
//
float distance = (height / hits);
if (distance < EPSILON) {
// If we're super-close to the plane, set to a minimum distance.
distance = EPSILON;
}
//
// Record and return the intersection info.
//
ISect isect;
isect.yes = 1;
isect.distance = distance;
isect.location = R + distance * du;
isect.normal = n;
return isect;
}
ISect rayClosestWall(vec4 R, vec4 d) {
//
// R, d: ray source point and direction.
//
// This checks to see if a ray hits any of the 4 walls, and
// the floor/ceiling of the cubical room housing the
// scene. Each is axis-aligned:
// * The back wall and entrance have a normal of -/+ z.
// * The left and right walls have a normal of +/- x.
// * The floor and ceiling have a normal of +/- y.
// Each plane is checked and the closest boundary plane
// to the emitted ray is recorded and returned.
//
// The `ISect` also returns the color of that closest
// wall. For the floor, the color calculation is
// complicated. It is a tiling of two colors with a 5 x 5
// checkerboard pattern. For the entrance, there is a
// door with a peephole and a door handle. The appropriate
// material color is set in `materialColor`.
//
// The materials of all these are matte and so
// `materialGlossy` is set to 0.
//
ISect isect = NO_INTERSECTION();
isect.materialColor = vec3(0.3,0.2,0.5);
//
// floor
//
ISect flor = rayIntersectPlane(R, d,
vec4(0.0,0.0,1.0,1.0),
vec4(0.0,1.0,0.0,0.0));
if (flor.yes == 1) {
// Checkerboard floor pattern.
vec4 fl = flor.location;
int i = 0;
if (fl.x < -0.6) i++;
if (fl.x < -0.2) i--;
if (fl.x < 0.2) i++;
if (fl.x < 0.6) i--;
if (fl.z < 0.4) i++;
if (fl.z < 0.8) i--;
if (fl.z < 1.2) i++;
if (fl.z < 1.6) i--;
if (i == 0 || i == 2 || i == -2 || i == -4 || i == 4) {
flor.materialColor = vec3(0.125, 0.175, 0.25); // dark green
} else {
flor.materialColor = vec3(0.125, 0.25, 0.175); // dark blue
}
flor.materialGlossy = 0;
}
isect = bestISect(isect, flor);
//
// left wall
//
ISect left = rayIntersectPlane(R, d,
vec4(-1.0,1.0,1.0,1.0),
vec4(1.0,0.0,0.0,0.0));
left.materialColor = vec3(0.6,0.49,0.48); // slate blue
left.materialGlossy = 0;
isect = bestISect(isect, left);
//
// right wall
//
ISect rght = rayIntersectPlane(R, d,
vec4(1.0,1.0,1.0,1.0),
vec4(-1.0,0.0,0.0,0.0));
rght.materialColor = vec3(0.5,0.59,0.48); // olive-ish
rght.materialGlossy = 0;
isect = bestISect(isect, rght);
//
// back wall
//
ISect back = rayIntersectPlane(R, d,
vec4(0.0,1.0,2.0,1.0),
vec4(0.0,0.0,-1.0,0.0));
back.materialColor = vec3(0.60,0.58,0.55); // warm-ish white
back.materialGlossy = 0;
isect = bestISect(isect, back);
//
// ceiling
//
ISect ceil = rayIntersectPlane(R, d,
vec4(0.0,2.0,1.0,1.0),
vec4(0.0,-1.0,0.0,0.0));
ceil.materialColor = vec3(0.5,0.49,0.48); // warm-ish gray
ceil.materialGlossy = 0;
isect = bestISect(isect, ceil);
//
// entrance
//
ISect entr = rayIntersectPlane(R, d,
vec4(0.0,1.0,0.0,1.0),
vec4(0.0,0.0,1.0,0.0));
if (entr.yes == 1) {
if (abs(entr.location.x) < 0.3 && entr.location.y < 1.4) {
//
// The ray hits the door.
//
float handlex = entr.location.x - 0.24;
float handley = entr.location.y - 0.72;
float eyex = entr.location.x;
float eyey = entr.location.y - 1.0;
if (handlex*handlex+handley*handley < 0.04*0.04
|| eyex*eyex+eyey*eyey < 0.01*0.01) {
//
// The ray hits the door handle or the peephole.
//
entr.materialColor = vec3(0.00,0.00,0.00); // black
} else {
//
// Set to the door color.
//
entr.materialColor = vec3(0.43,0.40,0.17); // brown
}
} else {
//
// Set to the wall color.
//
entr.materialColor = vec3(0.43,0.60,0.67); // blue
}
}
entr.materialGlossy = 0;
isect = bestISect(isect, entr);
return isect;
}
bool rayHitsSphereBefore(vec4 R, vec4 d, vec4 C, float r, float limit) {
//
// Check whether a ray intersects a sphere. Returns a
// boolean indicating whether the sphere is hit.
//
// R, d: ray source point and direction.
// C, r; sphere center point and radius
//
vec4 du = normalize(d);
vec4 toR = R-C;
// This is calculated carefully on the wikipedia entry for
// Ray-Sphere intersection.
//
float delta1 = -dot(du,toR);
float delta2 = dot(toR,toR);
float delta = delta1*delta1 - (delta2 - r*r);
if (delta > EPSILON) {
float distance = (delta1 - sqrt(delta));
if (distance > EPSILON && distance < limit) {
return true;
}
}
return false;
}
// RAY_HITS_SPHERE_BEFORE
//
// Below gives the code for a C/GLSL _macro_ whose code gets
// cut-n-pasted using its template. It is built as a single
// line and gives a template for when it is used below.
//
// The code below checks whether a ray hits a certain sphere in
// the room scene. The particular sphere is given by an integer
// INDEX and the sphere's info is accessed from the `sphereData`
// array of floats. HITS_ANY should be a variable of type `bool`
// and is set to `true` if the sphere is hit by the ray.
//
// The ray is given by an ORIGIN point and DIRECTION vector.
// The code relies on `rayHitsSphere` to do its work.
//
#define RAY_HITS_SPHERE_BEFORE(INDEX, ORIGIN, DIRECTION, DISTANCE, HITS_ANY) \
if (INDEX >= curvedMirror && INDEX < numSpheres) { \
float rhs_x = sphereData[INDEX*7+0]; \
float rhs_y = sphereData[INDEX*7+1]; \
float rhs_z = sphereData[INDEX*7+2]; \
float rhs_r = sphereData[INDEX*7+3]; \
vec4 rhs_c = vec4(rhs_x, rhs_y, rhs_z, 1.0); \
bool rhs_hits = rayHitsSphereBefore(ORIGIN, DIRECTION, rhs_c, rhs_r, DISTANCE); \
HITS_ANY = HITS_ANY || rhs_hits; \
};
#define MIRR_HEIGHT 1.5
float ifInRect(ISect intersectInfo, vec2 b1, vec2 b2) {
vec3 loc = vec3(intersectInfo.location);
if (loc.y > MIRR_HEIGHT + EPSILON || loc.y < 0.0) {
return -1.0;
}
vec2 loc2d = vec2(loc.x, loc.z);
vec2 bar = b1 - b2;
vec2 ref = loc2d - b2;
float len = dot(ref, normalize(bar));
if (len >= 0.0) {
float ratio = len / length(bar);
if (ratio < 1.0) {
return ratio;
}
}
return -1.0;
}
bool rayHitsBezierBeforeBase(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2, float distance) {
vec3 cp0_3d = vec3(cp0.x, 0, cp0.y);
vec3 cp2_3d = vec3(cp2.x, 0, cp2.y);
vec3 cp1_3d_up = vec3(cp1.x, MIRR_HEIGHT, cp1.y);
vec3 cp1_3d = vec3(cp1.x, 0, cp1.y);
vec4 cp1_4d = vec4(cp1_3d, 1.0);
vec3 mid_vertical = cp1_3d_up - cp1_3d;
// get left mirror face normal
vec3 left_bottom = cp0_3d - cp1_3d;
vec4 left_face_normal = normalize(vec4(cross(left_bottom, mid_vertical), 0.0));
ISect leftISect = rayIntersectPlane(R, d, cp1_4d, left_face_normal);
// check if it's on the other side
if (leftISect.yes != 1) {
leftISect = rayIntersectPlane(R, d, cp1_4d, -left_face_normal);
}
// if intersect right, go further in approx
vec3 right_bottom = cp2_3d - cp1_3d;
vec4 right_face_normal = normalize(vec4(cross(mid_vertical, right_bottom), 0.0));
ISect rightISect = rayIntersectPlane(R, d, cp1_4d, right_face_normal);
if (rightISect.yes != 1) {
rightISect = rayIntersectPlane(R, d, cp1_4d, -right_face_normal);
}
return (leftISect.yes == 1 && distance > leftISect.distance && ifInRect(leftISect, cp0, cp1) > 0.0) ||
(rightISect.yes == 1 && distance > rightISect.distance && ifInRect(rightISect, cp1, cp2) > 0.0);
}
bool rayHitsBezierBeforeRec1(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2, float dis) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
return rayHitsBezierBeforeBase(R, d, cp0, lp, mp, dis) ||
rayHitsBezierBeforeBase(R, d, mp, rp, cp2, dis);
}
bool rayHitsBezierBeforeRec2(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2, float dis) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
return rayHitsBezierBeforeRec1(R, d, cp0, lp, mp, dis) ||
rayHitsBezierBeforeRec1(R, d, mp, rp, cp2, dis);
}
bool rayHitsBezierBeforeRec3(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2, float dis) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
return rayHitsBezierBeforeRec2(R, d, cp0, lp, mp, dis) ||
rayHitsBezierBeforeRec2(R, d, mp, rp, cp2, dis);
}
bool rayHitsBezierBeforeRec4(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2, float dis) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
return rayHitsBezierBeforeRec3(R, d, cp0, lp, mp, dis) ||
rayHitsBezierBeforeRec3(R, d, mp, rp, cp2, dis);
}
bool rayHitsBezierBeforeRec5(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2, float dis) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
return rayHitsBezierBeforeRec4(R, d, cp0, lp, mp, dis) ||
rayHitsBezierBeforeRec4(R, d, mp, rp, cp2, dis);
}
bool rayHitsBezierBefore(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2, float distance) {
//
// This should return `true` if shooting a ray from point
// `R` in a direction `d` hits a Bezier mirror before
// traveling a given `distance`.
//
// The mirror is given by control points whose floor
// coordinates sit at `cp0`, `cp1`, and `cp2`. You can make
// the mirror have any height you like. My demo used a
// height of 1.5.
//
// This is used to see the mirror's shadow.
//
return rayHitsBezierBeforeRec3(R, d, cp0, cp1, cp2, distance);
}
bool rayHitsMirrorBefore(vec4 R, vec4 d, float distance) {
bool hits = false;
if (curvedMirror == 0) {
RAY_HITS_SPHERE_BEFORE(0, R, d, distance, hits);
} else {
vec2 cp0 = vec2(controlPoints[0],controlPoints[1]);
vec2 cp1 = vec2(controlPoints[2],controlPoints[3]);
vec2 cp2 = vec2(controlPoints[4],controlPoints[5]);
hits = rayHitsBezierBefore(R, d, cp0, cp1, cp2, distance);
}
return hits;
}
bool rayHitsSomeSphereBefore(vec4 R, vec4 d, float distance) {
//
// Determine whether or not a ray hits some sphere
// in the scene. Returns a boolean indicating whether
// some sphere was hit.
//
// R, d: ray source point and direction.
//
bool hitsSphere = false;
for(int i=1;i<=10;i++) {
RAY_HITS_SPHERE_BEFORE(i, R, d, distance, hitsSphere);
}
return hitsSphere;
}
vec3 computePhong(vec4 P, vec4 n, vec4 V, vec4 L,
vec3 mColor, int mGlossy) {
//
// Figures out Phong shading of an object at a surface point P with
// normal n, and with respect to some light at point L, when viewed
// from point V. The material's properties are specified with
// `mColor` and `mGlossy`, giving the RGB of the color reflected
// and whether the material is glossy (set to 1) or matte (set to 0).
//
// The surface point might be in shadow, so we check occlusion by
// seeing whether a ray between the surface point and the light
// is obstructed by an object in the scene.
//
// This returns an RGB vec3 of the Phong shading's color.
//
// P: surface point
// n: normal direction at that surface point
// V: point from which the surface is being viewed
// L: point from which the surface is being illuminated
//
// mColor, mGlossy: material color, shine
//
//
// If in shadow, ambient reflection only.
//
float ambientMix = 0.75;
vec4 towardsLight = L - P;
vec3 ambient = ambientMix * lightColor * mColor;
//
float within = length(towardsLight);
bool occluded = rayHitsSomeSphereBefore(P, towardsLight, within);
// TODO do something to incorporate this?
occluded = occluded || rayHitsMirrorBefore(P, towardsLight, within);
if (occluded) {
// The surface point is in shadow from the light.
return ambient;
}
//
// If light is behind the surface, then ambient reflection only.
//
vec4 l = normalize(towardsLight);
if (dot(l,n) < EPSILON) {
// The surface point is in shadow from the light.
return ambient;
}
//
// If the surface is matte, not glossy, then diffuse reflection.
//
float diffuseMix = 0.9;
vec3 diffuse = diffuseMix * lightColor * mColor * dot(l,n);
if (mGlossy == 0) {
return ambient + diffuse;
}
//
// If glossy, then compute a specular highlight component.
//
float shininess = 20.0;
float specularMix = 0.3;
//
vec4 e = normalize(V - P);
vec4 r = normalize(-l + 2.0 * dot(l,n) * n);
float p = pow(max(dot(e,r),0.0), shininess);
//
vec3 specular = specularMix * lightColor * p * dot(l,n);
return ambient + diffuse + specular;
}
ISect rayIntersectSphere(vec4 R, vec4 d, vec4 C, float r) {
//
// Check if a ray intersects a sphere.
//
// R, d: ray source point and direction.
// C, r: sphere center point and radius
//
// Fill in the (non-material) information of an `ISect`
// struct if the sphere is hit by the ray; return that
// info.
//
vec4 du = normalize(d);
vec4 toR = R-C;
// This is calculated carefully on the wikipedia entry for
// Ray-Sphere intersection.
//
float delta1 = -dot(du,toR);
float delta2 = dot(toR,toR);
float delta = delta1*delta1 - (delta2 - r*r);
if (delta > EPSILON) {
float distance = (delta1 - sqrt(delta));
if (distance > EPSILON) {
vec4 location = R + distance * du;
//
ISect isect;
isect.yes = 1;
isect.distance = distance;
isect.location = location;
isect.normal = normalize(location - C);
return isect;
}
}
return NO_INTERSECTION();
}
// CHECK_SPHERE_WITH_RAY
//
// Below gives the code for a C/GLSL _macro_ whose code gets
// cut-n-pasted using its template. It is built as a single
// line and gives a template for when it is used below.
//
// The code below checks whether a ray intersects a certain
// sphere in the room scene. The particular sphere is given by
// an integer INDEX and the sphere's info is accessed from the
// `sphereData` array of floats. The INFO is expected to be
// a variable of type `ISect` and it records an intersection,
// if found, with that particular sphere. It only updates
// that INFO if the intersection is found closer to the eye
// point than the given info held by INFO.
//
// The ray is given by an ORIGIN point and DIRECTION vector.
// The code relies on `rayIntersectSphere` to check inter-
// section.
//
#define CHECK_SPHERE_WITH_RAY(INDEX, ORIGIN, DIRECTION, INFO) \
if (INDEX < numSpheres) { \
float cswr_x = sphereData[INDEX*7+0]; \
float cswr_y = sphereData[INDEX*7+1]; \
float cswr_z = sphereData[INDEX*7+2]; \
float cswr_r = sphereData[INDEX*7+3]; \
float cswr_R = sphereData[INDEX*7+4]; \
float cswr_G = sphereData[INDEX*7+5]; \
float cswr_B = sphereData[INDEX*7+6]; \
vec4 cswr_c = vec4(cswr_x,cswr_y,cswr_z,1.0); \
ISect cswr; \
cswr = rayIntersectSphere(ORIGIN, DIRECTION, cswr_c, cswr_r); \
cswr.materialColor = vec3(cswr_R,cswr_G,cswr_B); \
cswr.materialGlossy = 1; \
INFO = bestISect(INFO, cswr); \
};
ISect rayClosestSphere(vec4 R, vec4 d) {
//
// Checks each of the scene spheres, up to 10, to see
// which of them (if any) are hit closest to the source
// of some ray.
//
// The ray comes from point R in the direction d.
//
ISect isect = NO_INTERSECTION();
// CHECK_SPHERE_WITH_RAY(0, R, d, isect); // Sphere #0 is a mirror.
CHECK_SPHERE_WITH_RAY(1, R, d, isect);
CHECK_SPHERE_WITH_RAY(2, R, d, isect);
CHECK_SPHERE_WITH_RAY(3, R, d, isect);
CHECK_SPHERE_WITH_RAY(4, R, d, isect);
CHECK_SPHERE_WITH_RAY(5, R, d, isect);
CHECK_SPHERE_WITH_RAY(6, R, d, isect);
CHECK_SPHERE_WITH_RAY(7, R, d, isect);
CHECK_SPHERE_WITH_RAY(8, R, d, isect);
CHECK_SPHERE_WITH_RAY(9, R, d, isect);
return isect;
}
ISect intersectWhichBezierBase(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2) {
// convert 2d pt to 4d
vec3 cp0_3d = vec3(cp0.x, 0, cp0.y);
vec3 cp2_3d = vec3(cp2.x, 0, cp2.y);
vec3 cp1_3d_up = vec3(cp1.x, MIRR_HEIGHT, cp1.y);
vec3 cp1_3d = vec3(cp1.x, 0, cp1.y);
vec4 cp1_4d = vec4(cp1_3d, 1.0);
vec3 mid_vertical = cp1_3d_up - cp1_3d;
// if intersect left, go further in approx
// x z coor should be enough, hmmm what?????
// get left mirror face normal
vec3 left_bottom = cp0_3d - cp1_3d;
vec4 left_face_normal = normalize(vec4(cross(left_bottom, mid_vertical), 0.0));
ISect leftISect = rayIntersectPlane(R, d, cp1_4d, left_face_normal);
// check if it's on the other side
// TODO alternating slits
if (leftISect.yes != 1) {
leftISect = rayIntersectPlane(R, d, cp1_4d, -left_face_normal);
}
// if intersect right, go further in approx
vec3 right_bottom = cp2_3d - cp1_3d;
vec4 right_face_normal = normalize(vec4(cross(mid_vertical, right_bottom), 0.0));
ISect rightISect = rayIntersectPlane(R, d, cp1_4d, right_face_normal);
if (rightISect.yes != 1) {
rightISect = rayIntersectPlane(R, d, cp1_4d, -right_face_normal);
}
vec4 mid_normal = (leftISect.normal + rightISect.normal) * 0.5;
if (leftISect.yes == 1) {
float ratio = ifInRect(leftISect, cp0, cp1);
if (ratio < 0.0) {
leftISect = NO_INTERSECTION();
} else {
leftISect.normal = mid_normal * (1.0 - ratio) + left_face_normal * ratio;
}
}
if (rightISect.yes == 1) {
float ratio = ifInRect(rightISect, cp1, cp2);
if (ratio < 0.0) {
rightISect = NO_INTERSECTION();
} else {
rightISect.normal = right_face_normal * (1.0 - ratio) + mid_normal * ratio;
}
}
return bestISect(leftISect, rightISect);
}
ISect intersectWhichBezierRec1(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
ISect left = intersectWhichBezierBase(R, d, cp0, lp, mp);
ISect right = intersectWhichBezierBase(R, d, mp, rp, cp2);
return bestISect(left, right);
}
ISect intersectWhichBezierRec2(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
ISect left = intersectWhichBezierRec1(R, d, cp0, lp, mp);
ISect right = intersectWhichBezierRec1(R, d, mp, rp, cp2);
return bestISect(left, right);
}
ISect intersectWhichBezierRec3(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
ISect left = intersectWhichBezierRec2(R, d, cp0, lp, mp);
ISect right = intersectWhichBezierRec2(R, d, mp, rp, cp2);
return bestISect(left, right);
}
ISect intersectWhichBezierRec4(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
ISect left = intersectWhichBezierRec3(R, d, cp0, lp, mp);
ISect right = intersectWhichBezierRec3(R, d, mp, rp, cp2);
return bestISect(left, right);
}
ISect intersectWhichBezierRec5(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2) {
vec2 lp = (cp0 + cp1) * 0.5;
vec2 rp = (cp1 + cp2) * 0.5;
vec2 mp = (lp + rp) * 0.5;
ISect left = intersectWhichBezierRec4(R, d, cp0, lp, mp);
ISect right = intersectWhichBezierRec4(R, d, mp, rp, cp2);
return bestISect(left, right);
}
ISect rayIntersectBezier(vec4 R, vec4 d, vec2 cp0, vec2 cp1, vec2 cp2) {
//
// This should return intersection information that results
// from shooting a ray from point `R` in a direction `d`,
// possibly hitting a Bezier mirror. If the mirror is hit,
// then the ISect struct should contain the location point
// where it was hit, the surface normal where it was hit,
// and its distance from the point along that ray. The `yes`
// component of the `ISect` should be set to 1.
//
// If they don't intersect, return NO_INTERSECTION().
//
// The mirror is given by control points whose floor
// coordinates sit at `cp0`, `cp1`, and `cp2`. You can make
// the mirror have any height you like. My demo used a
// height of 1.5.
//
// ok so vec2 is x z coor, fill in x y z coor
// 5 eats too much performance but you need 5 layers to achieve smoothness
return intersectWhichBezierRec5(R, d, cp0, cp1, cp2);
}
ISect rayIntersectMirror(vec4 R, vec4 d) {
//
// Checks whether a ray hits the one mirrored object in the
// scene. Currently, this is a sphere. Your assignment is to
// change this code to check intersection with a curved
// mirror.
//
// The ray comes from point R in the direction d.
//
ISect isect = NO_INTERSECTION();
//
// Right now, checks intersection with Sphere #0.
//
if (curvedMirror == 0) {
CHECK_SPHERE_WITH_RAY(0, R, d, isect);
} else {
vec2 cp0 = vec2(controlPoints[0],controlPoints[1]);
vec2 cp1 = vec2(controlPoints[2],controlPoints[3]);
vec2 cp2 = vec2(controlPoints[4],controlPoints[5]);
isect = rayIntersectBezier(R, d, cp0, cp1, cp2);
}
return isect;
}
#define EXTRA_BOUNCE 4
vec3 trace(vec4 R, vec4 d) {
//
// Traces a ray from the eye point R, through the virtual
// screen pixel in direction d out into the scene. The
// scene consists of a collection of spheres, one mirrored
// surface, and a cubicle room. It has one point light
// source sitting in the room.
//
// The ray comes from point R in the direction d.
//
// It traces 2 or 3 rays, depending on what's hit by the
// primary ray.
//
// It shoots only two rays when the primary ray directly
// hits a glossy object (a sphere) or a matte room bounding
// surface (e.g. wall, ceiling). In this case, a RGB color
// is returned. That color is computed as Phong
// illumination, though that object might be in shadow from
// the light. This check for shadowing is made by shooting
// a secondary ray from the surface point to the light
// source, seeing if any object is hit.
//
// It shoots three rays when the primary ray hits a mirrored
// surface. In that case a secondary reflection ray is
// shot much in the same way the primary ray is shot as
// described above, in order to see what matte/glossy object
// is hit.
//
// (Note: This would normally be written recursively but
// GLSL does not allow that, so as to work well on GPU
// hardware.)
//
// See if it hits the mirrored object...
//
vec4 source = R;
vec4 direction = d;
ISect mirror = NO_INTERSECTION();
ISect nextMirror = rayIntersectMirror(source, direction);
//
// If it does not hit the mirror, we see what object the
// primary ray hits.
//
//
// If it does hit the mirror, and nothing is blocking that
// ray from hitting the mirror, instead send a secondary ray
// due to reflection to see what object *it* hits.
//
bool blocked = true;
if (nextMirror.yes == 1) {
blocked = rayHitsSomeSphereBefore(nextMirror.location, -direction, FAR);
if (!blocked) {
//
// Compute the reflected ray.
//
mirror = nextMirror;
source = mirror.location;
vec4 n = normalize(mirror.normal);
direction = normalize(direction - 2.0 * dot(direction, n) * n);
}
}
for (int i = 0; i < EXTRA_BOUNCE; i++) {
nextMirror = rayIntersectMirror(source, direction);
if (nextMirror.yes == 1) {
blocked = rayHitsSomeSphereBefore(nextMirror.location, -direction, FAR);
if (!blocked) {
//
// Compute the reflected ray.
//
mirror = nextMirror;
source = mirror.location;
vec4 n = normalize(mirror.normal);
direction = normalize(direction - 2.0 * dot(direction, n) * n);
}
}
}
// Find out the color of the surface hit...
//
// If nothing gets hit, set to some background color.
// (Make notable because we expect *something* to be hit.)
//
vec3 color = vec3(1.0,0.8,0.0);