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.); +}