diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..73ed802
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.DS_Store
+old/
+new/
+pjsk.mp4
+_5.jpg
+*.bak*
diff --git a/1.jpg b/1.jpg
new file mode 100644
index 0000000..7dcab8a
Binary files /dev/null and b/1.jpg differ
diff --git a/2.jpg b/2.jpg
new file mode 100644
index 0000000..9d787d8
Binary files /dev/null and b/2.jpg differ
diff --git a/3.jpg b/3.jpg
new file mode 100644
index 0000000..0b0545c
Binary files /dev/null and b/3.jpg differ
diff --git a/4.jpg b/4.jpg
new file mode 100644
index 0000000..9bb2c0b
Binary files /dev/null and b/4.jpg differ
diff --git a/5.jpg b/5.jpg
new file mode 100644
index 0000000..c6ba47e
Binary files /dev/null and b/5.jpg differ
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e3156be
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# graphics_hw3
+[link](https://billsun.dev/graphics/hw3)
diff --git a/RTXoff.svg b/RTXoff.svg
new file mode 100644
index 0000000..80488f7
--- /dev/null
+++ b/RTXoff.svg
@@ -0,0 +1,103 @@
+
+
+
diff --git a/RTXon.svg b/RTXon.svg
new file mode 100644
index 0000000..d3124d7
--- /dev/null
+++ b/RTXon.svg
@@ -0,0 +1,105 @@
+
+
+
diff --git a/img.jpg b/img.jpg
new file mode 100644
index 0000000..d5be235
Binary files /dev/null and b/img.jpg differ
diff --git a/lib3.js b/lib3.js
new file mode 100644
index 0000000..7a08045
--- /dev/null
+++ b/lib3.js
@@ -0,0 +1,206 @@
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// THIS IS THE SUPPORT LIBRARY. YOU PROBABLY DON'T WANT TO CHANGE ANYTHING HERE JUST YET.
+//
+//////////////////////////////////////////////////////////////////////////////////////////
+
+let fragmentShaderHeader = ['' // WHATEVER CODE WE WANT TO PREDEFINE FOR FRAGMENT SHADERS
+ , 'precision highp float;'
+ , 'float noise(vec3 point) { float r = 0.; for (int i=0;i<16;i++) {'
+ , ' vec3 D, p = point + mod(vec3(i,i/4,i/8) , vec3(4.0,2.0,2.0)) +'
+ , ' 1.7*sin(vec3(i,5*i,8*i)), C=floor(p), P=p-C-.5, A=abs(P);'
+ , ' C += mod(C.x+C.y+C.z,2.) * step(max(A.yzx,A.zxy),A) * sign(P);'
+ , ' D=34.*sin(987.*float(i)+876.*C+76.*C.yzx+765.*C.zxy);P=p-C-.5;'
+ , ' r+=sin(6.3*dot(P,fract(D)-.5))*pow(max(0.,1.-2.*dot(P,P)),4.);'
+ , '} return .5 * sin(r); }'
+].join('\n');
+let ns = 5, cns = 3;
+fragmentShaderHeader+= 'const int ns = ' + ns + ';\n';
+let fragmentShaderDefs = 'const int cns = ' + cns + ';\n';
+let nfsh = fragmentShaderHeader.split('\n').length + 1; // NUMBER OF LINES OF CODE IN fragmentShaderHeader
+
+let isFirefox = navigator.userAgent.indexOf('Firefox') > 0; // IS THIS THE FIREFOX BROWSER?
+let errorMsg = '';
+//
+// Initialize a texture and load an image.
+// When the image finished loading copy it into the texture.
+//
+function getBlob(data) {
+ let bytes = new Array(data.length);
+ for (let i = 0; i < data.length; i++) {
+ bytes[i] = data.charCodeAt(i);
+ }
+ return new Blob([new Uint8Array(bytes)]);
+ }
+let texture = [], gl, program;
+let textures = [];
+let lock = false;
+function loadTexture(gl, url, i) {
+ const level = 0;
+ const internalFormat = gl.RGBA;
+ const width = 1;
+ const height = 1;
+ const border = 0;
+ const srcFormat = gl.RGBA;
+ const srcType = gl.UNSIGNED_BYTE;
+ if (texture[i] == null)
+ {
+ texture[i] = gl.createTexture();
+ const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
+ gl.activeTexture(gl.TEXTURE0+i);
+ gl.bindTexture(gl.TEXTURE_2D, texture[i]);
+ gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
+ width, height, border, srcFormat, srcType,
+ pixel);
+ }
+ // Because images have to be downloaded over the internet
+ // they might take a moment until they are ready.
+ // Until then put a single pixel in the texture so we can
+ // use it immediately. When the image has finished downloading
+ // we'll update the texture with the contents of the image.
+
+ const image = new Image();
+ image.onload = function () {
+ gl.activeTexture(gl.TEXTURE0+i);
+ gl.bindTexture(gl.TEXTURE_2D, texture[i]);
+ gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
+ srcFormat, srcType, image);
+
+ // WebGL1 has different requirements for power of 2 images
+ // vs non power of 2 images so check if the image is a
+ // power of 2 in both dimensions.
+ if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
+ // Yes, it's a power of 2. Generate mips.
+ gl.generateMipmap(gl.TEXTURE_2D);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
+ } else {
+ // No, it's not a power of 2. Turn off mips and set
+ // wrapping to clamp to edge
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ }
+ };
+ image.src = url;
+}
+
+function isPowerOf2(value) {
+ return (value & (value - 1)) == 0;
+}
+function gl_start(canvas, vertexShader, fragmentShader) { // START WEBGL RUNNING IN A CANVAS
+
+ setTimeout(function () {
+ try {
+ canvas.gl = canvas.getContext('experimental-webgl'); // Make sure WebGl is supported. IT WOULD BE GREAT TO USE WEBGL2 INSTEAD.
+ } catch (e) { throw 'Sorry, your browser does not support WebGL.'; }
+
+ canvas.setShaders = function (vertexShader, fragmentShader) { // Add the vertex and fragment shaders:
+
+ gl = this.gl;
+ program = gl.createProgram(); // Create the WebGL program.
+
+ function addshader(type, src) { // Create and attach a WebGL shader.
+ function spacer(color, width, height) {
+ return '
';
+ }
+ errorMessage.innerHTML = '
';
+ // errorMarker.innerHTML = spacer('white', 1, 1) + '\u25B6';
+ let shader = gl.createShader(type);
+ gl.shaderSource(shader, src);
+ gl.compileShader(shader);
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ let msg = gl.getShaderInfoLog(shader);
+ console.log('Cannot compile shader:\n\n' + msg);
+
+ let a = msg.substring(6, msg.length);
+ let line = 0;
+ if (a.substring(0, 3) == ' 0:') {
+ a = a.substring(3, a.length);
+ line = parseInt(a) - nfsh;
+
+ editor.session.setAnnotations([{
+ row: line,
+ column: 0,
+ text: msg,
+ type: "error"
+ }]);
+ }
+ let j = a.indexOf(':');
+ a = 'line ' + (line+1) + a.substring(j, a.length);
+ if ((j = a.indexOf('\n')) > 0)
+ a = a.substring(0, j);
+ errorMessage.innerHTML = a;
+ }
+ else
+ editor.session.clearAnnotations();
+ gl.attachShader(program, shader);
+ };
+
+ addshader(gl.VERTEX_SHADER, vertexShader); // Add the vertex and fragment shaders.
+ addshader(gl.FRAGMENT_SHADER, fragmentShaderHeader +fragmentShaderDefs+ fragmentShader);
+
+ gl.linkProgram(program); // Link the program, report any errors.
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS))
+ console.log('Could not link the shader program!');
+ gl.useProgram(program);
+ gl.program = program;
+ for(let i = 0; i < ns; ++i){
+ loadTexture(gl, './'+(i+1)+'.jpg', i); //Texture loading.
+ textures[i] = i;
+ }
+ gl.uniform1iv(gl.getUniformLocation(program, 'uSampler'), textures);
+ let cx = Math.cos(mousedx), cy = Math.cos(mousedy), sx = Math.sin(mousedx), sy = Math.sin(mousedy);
+ setUniform('Matrix3fv', 'transformation', false, [cx, sy*sx, sx*cy, 0, cy, -sy, -sx, cx*sy, cx*cy]);
+ let attribs = [
+ .05,.05,.1, .5,.5,1., 1.,.5,.5,20., 0., .0, 1.3,
+ .1,.05,.05, 1.,.5,.5, 1.,.5,.5,10., .2,0.8,1.3,
+ .1,.05,.05, .71,.71,.71, .71,.71,.71,10., 0.3,.0,1.5,
+ .1,.1,.1, .71,.71,.71, .71,.71,.71,10., 0.05,0., 1.
+ ]
+ var offset = 0;
+ for(let i = 0; i < ns-1; i++){
+ setUniform('3fv', 'Ambient['+i+']', attribs.slice(offset, offset += 3));
+ setUniform('3fv', 'Diffuse['+i+']', attribs.slice(offset, offset += 3));
+ setUniform('4fv', 'Specular['+i+']', attribs.slice(offset, offset += 4));
+ setUniform('1fv', 'ks['+i+']', attribs.slice(offset, offset += 1));
+ setUniform('1fv', 'kr['+i+']', attribs.slice(offset, offset += 1));
+ setUniform('1fv', 'kf['+i+']', attribs.slice(offset, offset += 1));
+ }
+
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); // Create a square as a triangle strip
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array( // consisting of two triangles.
+ [-1, 1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0]), gl.STATIC_DRAW);
+
+ let aPos = gl.getAttribLocation(program, 'aPos'); // Set aPos attribute for each vertex.
+ gl.enableVertexAttribArray(aPos);
+ gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0);
+ }
+
+ canvas.setShaders(vertexShader, fragmentShader); // Initialize everything,
+ setInterval(function () { // Start the animation loop.
+ gl = canvas.gl;
+ if (gl.startTime === undefined) // First time through,
+ gl.startTime = Date.now(); // record the start time.
+ animate(gl);
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // Render the square.
+ }, 30);
+
+ }, 100); // Wait 100 milliseconds after page has loaded before starting WebGL.
+}
+
+// THE animate() CALLBACK FUNCTION CAN BE REDEFINED IN index.html.
+
+function animate() { }
+
+function setUniform(type, name, a, b, c, d, e, f) {
+ if(gl)
+ {
+ let loc = gl.getUniformLocation(gl.program, name);
+ (gl['uniform' + type])(loc, a, b, c, d, e, f);
+ }
+}
+
diff --git a/shader.frag b/shader.frag
new file mode 100644
index 0000000..4fde7c8
--- /dev/null
+++ b/shader.frag
@@ -0,0 +1,252 @@
+
+#define _DEBUG_BREAK {gl_FragColor=vec4(1,0,0,1); return;}
+#define REFRACTION normalize(eta*W + (eta*c1 - sqrt(1.-eta*eta*(1.-c1*c1)))*N)
+vec3 foregroundColor = vec3(.0841, .5329, .9604);
+vec3 groundColor = vec3(.2, .3, .5);
+vec4 groundSpecular = vec4(.71, .71, .71, 10.);
+uniform float uTime;// TIME, IN SECONDS
+uniform int f_tex, f_rt, f_moved;
+uniform float dFL; //DELTA on FOCAL LENGTH
+uniform mat3 transformation, invTr;
+uniform vec3 Ambient[ns], Diffuse[ns];
+uniform vec4 Specular[ns];
+uniform float ks[ns], kr[ns], kf[ns];
+uniform vec4 Sph[ns];
+uniform sampler2D uSampler[ns];
+
+const float kf_air = 1.000293;
+varying vec3 vPos;
+float fl=3.;//ORIGINAL FOCAL LENGTH
+const float pi=3.14159265359;
+const float _2pi=2.*pi;
+
+/***********PLEASE DO INCREASE n_ref(RT DEPTH) FOR BETTER RESULTS************/
+/*---->*/const int n_ref=31; //2^n-1 because each hit now spawn at most 2 rays.
+/**BUT BE CAUTIOUS IF YOU DON'T HAVE A DECENT GRAPHICS CARD (below GTX 950M)**/
+
+const int max_stack = (n_ref+1)/4;
+
+vec3 scolor = vec3(0,0,0); //Actually 2^n_ref
+struct Ray{
+ vec3 V;
+ vec3 W;
+ float kf, cumulativeK;
+} stack1[max_stack], stack2[max_stack];
+bool modulo2(int n){
+ return n-2*(n/2) == 1;
+}
+vec2 getTextCoord(vec3 tex_sph, float R){
+ float tex_x=acos(abs(tex_sph.x)/sqrt(R*R-tex_sph.y*tex_sph.y));
+ if(tex_sph.x>0.)
+ tex_x=pi-tex_x;
+ tex_x*=1.5708;//*Correct aspect ratio of texture 2:1 -> 2pir:2r
+ tex_x=tex_x+float(uTime);
+ float quo=float(int(tex_x/_2pi));
+ tex_x=tex_x/_2pi - quo;
+ return vec2(tex_x,((R-tex_sph.y)/(2.*R)));
+}
+void main(){
+ vec3 LDir=vec3(.5,.5,.5);
+ vec3 LCol=vec3(1.,1.,1.);
+ float currKf = kf_air;
+ vec3 color=vec3(.2, .3, .5);
+ vec3 trPos = transformation*((dFL+fl+1.)/(fl+1.))*vec3(vPos.xy, -1);
+ vec3 V0=transformation*vec3(0.,0.,fl+dFL), V = V0;
+ vec3 W=(trPos-V);
+ bool rtxoff = false, showtexture = true, moved = false;
+ float currentK = 1.;
+ int curr_ptr = 0, curr_top = 0, next_top = 0;
+ bool final = false, stackswap = false;
+ for(int j=0;j 0){
+ Ray currR;
+ if(stackswap)
+ currR = stack1[curr];
+ else
+ currR = stack2[curr];
+ currKf = currR.kf;
+ currentK = currR.cumulativeK;
+ if(currKf <= 0.001 || currentK <= 0.001)
+ skip = true;
+ V = currR.V;
+ W = currR.W;
+ }
+ else
+ W = normalize(W);
+ if(!skip){
+ float tMin=10000.;
+ int iMin = -1;
+ for(int i=0;i0.){
+ float t=-B-sqrt(D);
+ if(t >= 0.01 && t < tMin){
+ tMin = t; // This is an optimization, we don't have to do lighting/tex
+ iMin = i; // for objects that are occuluded, which is expensive!
+ }
+ else if (t >= -0.01 && t <0.01){
+ t = -(t + 2.*B);
+ if(t > 0.01 && t < tMin){
+ tMin = t;
+ iMin = i;
+ }
+ }
+ }
+ }
+
+ if(iMin >= 0){
+ float t = tMin;
+ vec3 S=V+t*W;
+ for(int i = 0; i < cns; ++ i)
+ if(i == iMin)
+ {
+ vec3 texture_color;
+ if(showtexture)
+ {
+ vec3 tex_sph = (S-Sph[i].xyz);
+ if(moved)
+ ;//tex_sph=invTr*tex_sph; too expensive
+ texture_color=texture2D(uSampler[i],getTextCoord(tex_sph, Sph[i].w)).xyz;
+ }
+ else texture_color = foregroundColor;
+
+ vec3 N=normalize(S-Sph[i].xyz);
+ vec3 realLDir=normalize(LDir-S);
+ float c1 =dot(N, W);
+ if(c1<0.){
+ color=(Ambient[i]+Diffuse[i]*max(0.,dot(N,realLDir))*LCol)*texture_color;
+ if(rtxoff || final) //if it's the last hit
+ {
+ color += Specular[i].xyz*pow(max(0.,
+ dot(-2.*c1*N-realLDir,realLDir)),Specular[i].w);
+ scolor += color * currentK;
+ }
+ else{
+ c1 = -c1;
+ float eta =currKf/kf[i];
+ float nextks = currentK * ks[i], nextkr = currentK * kr[i];
+ bool refl = nextks > 0.001, refr = nextkr > 0.001;
+ if(refl || refr)
+ for(int k = 0; k < max_stack; ++k)
+ if(k == next_top){
+ if(stackswap){
+ if(refl)
+ {
+ stack2[k] = Ray(S, 2. * c1 * N + W, currKf, nextks); //reflection
+ currentK -= nextks;
+ next_top ++;
+ }
+ if(refr)
+ {
+ if(refl)
+ stack2[k+1] = Ray(S, REFRACTION, kf[i], nextkr); //refraction
+ else
+ stack2[k] = Ray(S, REFRACTION, kf[i], nextkr); //refraction
+ currentK -= nextkr;
+ next_top ++;
+ }
+ }else{
+ if(refl)
+ { //remember, c1 = -NW now
+ stack1[k] = Ray(S, 2. * c1 * N + W, currKf, nextks); //reflection
+ currentK -= nextks;
+ next_top ++;
+ }
+ if(refr)
+ {
+ if(refl)
+ stack1[k+1] = Ray(S, REFRACTION, kf[i], nextkr); //refraction
+ else
+ stack1[k] = Ray(S, REFRACTION, kf[i], nextkr); //refraction
+ currentK -= nextkr;
+ next_top ++;
+ }
+ }
+ break;
+ }
+ scolor += currentK * color;
+ }
+ }
+ else{
+ float eta = currKf/kf_air;
+ N = -N; //inside the sphere, normal is inward!
+ float c2 = (1.-eta*eta*(1.-c1*c1));
+ for(int k = 0; k < max_stack; ++k)
+ if(k == next_top){
+ if(stackswap)
+ {
+ stack2[k+1] = Ray(S, -2. * c1 * N + W, currKf, currentK*ks[i]); //reflection inside
+ if(c2 >= 0.)
+ stack2[k] = Ray(S, normalize(eta*W + (eta*c1 - sqrt(c2))*N), kf_air, currentK*kr[i]); //refraction
+ else //on the edge, the light won't bend anymore and will keep perpendicular to normal
+ stack2[k] = Ray(S, normalize((W + c1*N)/sqrt(1.-c1*c1)), kf_air, currentK*kr[i]); //refraction
+ }else{
+ stack1[k+1] = Ray(S, -2. * c1 * N + W, currKf, currentK*ks[i]); //reflection inside
+ if(c2 >= 0.)
+ stack1[k] = Ray(S, normalize(eta*W + (eta*c1 - sqrt(c2))*N), kf_air, currentK*kr[i]); //refraction
+ else
+ stack1[k] = Ray(S, normalize((W + c1*N)/sqrt(1.-c1*c1)), kf_air, currentK*kr[i]); //refraction
+ }
+ next_top += 2;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ else {
+ float t = -(.2+V.y)/W.y;
+ float sx = V.x + t* W.x, sz = V.z + t * W.z;
+
+ if(t >= 0. && abs(sx) < 1.5 && abs(sz) < 3.)
+ {
+ vec3 S = vec3(sx, -.2, sz);
+ vec3 realLDir=normalize(LDir - S);
+ color=(0.5+0.5*max(0.,realLDir.y)*LCol)*texture2D(uSampler[4],vec2((sx+1.4)/3., (sz+1.5)/4.)).xyz;
+ if(rtxoff || final&&abs(sx)<1.5 && abs(sz+.6)<3.)
+ {
+ color += groundSpecular.xyz* //specular for ground.
+ pow(max(0., dot(vec3(-realLDir.x, realLDir.y,-realLDir.z),-W)),groundSpecular.w);
+ scolor += currentK * color;
+ }
+ else
+ {
+ for(int k = 0; k < max_stack; ++k)
+ if(k == next_top){
+ if(stackswap)
+ stack2[k] = Ray(S, vec3(W.x, -W.y, W.z), kf_air, currentK * 0.15); //reflection
+ else
+ stack1[k] = Ray(S, vec3(W.x, -W.y, W.z), kf_air, currentK * 0.15); //reflection
+ next_top ++;
+ break;
+ }
+ scolor += (currentK*.85)*color;
+ }
+ }
+ else{
+ if(j > 0)
+ scolor += currentK * (pow(max(0.,dot(W, normalize(LDir - V))), 10.) * vec3(1.,1.,1.) + foregroundColor*0.1);
+ else scolor = foregroundColor*0.6;
+ }
+ }
+ }
+ if(++curr_ptr >= curr_top){
+ curr_top = next_top;
+ curr_ptr = 0;
+ if(next_top * 2 > max_stack)
+ final = true;
+ stackswap = !stackswap;
+ }
+ break;
+ }
+ }
+ }
+ gl_FragColor=vec4(sqrt(scolor),1.);
+}