Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save LeetCodes/1c7f7cc53e0d99382b64679f55fb9e3a to your computer and use it in GitHub Desktop.
Save LeetCodes/1c7f7cc53e0d99382b64679f55fb9e3a to your computer and use it in GitHub Desktop.
Cube Mapped Box/WebGL Fragment Shader

Cube Mapped Box/WebGL Fragment Shader

Fragment shader, just math - no framework. JS window is webgl bootstrap and texture loading/attaching to canvas. HTML window is the fragment and vertex shaders. Basically I'm making a 3D scene that is projected on the canvas by using distance fields (mathematical formulas that represent an object.)

A Pen by 󠀁󠀃󠀃󠀇󠀌󠀀󠀍󠀎 on CodePen.

License.

<!-- VertexShader code here -->
<script id="vertexShader" type="x-shader/x-vertex">#version 300 es
precision highp float;
in vec2 a_texCoord;
in vec4 vPosition;
out vec2 v_texcoord;
void main() {
gl_Position = vPosition;
v_texcoord = a_texCoord;
}
</script>
<!-- FragmentShader code here -->
<script id="fragmentShader" type="x-shader/x-fragment">#version 300 es
#if __VERSION__ < 130
#define TEXTURE2D texture2D
#else
#define TEXTURE2D texture
#endif
precision highp float;
out vec4 fragColor;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;
uniform vec2 v_texCoord;
uniform vec4 mouse;
uniform vec2 resolution;
uniform float time;
#define R resolution
#define T time
#define M mouse
#define PI 3.14159265359
#define PI2 6.28318530718
#define MAX_DIST 100.
#define MIN_DIST .0001
float hash21(vec2 p){ return fract(sin(dot(p,vec2(26.34,45.32)))*4324.23); }
mat2 rot(float a){ return mat2(cos(a),sin(a),-sin(a),cos(a)); }
float vmax(vec3 p){ return max(max(p.x,p.y),p.z); }
//@iq
float cap( vec3 p, float h, float r ){
vec2 d = abs(vec2(length(p.xz),p.y)) - vec2(h,r);
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
//@iq
float vcap( vec3 p, float h, float r ){
p.y -= clamp( p.y, 0.0, h );
return length( p ) - r;
}
float modPolar(inout vec2 p, float rep) {
float angle = 2.*PI/rep;
float a = atan(p.y, p.x) + angle/2.;
float c = floor(a/angle);
a = mod(a,angle) - angle/2.;
p = vec2(cos(a), sin(a))*length(p);
return (abs(c) >= (rep/2.)) ? abs(c) : c;
}
float box(vec3 p, vec3 b){
vec3 d = abs(p) - b;
return length(max(d,vec3(0))) + vmax(min(d,vec3(0)));
}
vec3 hit=vec3(0),hitPoint=vec3(0);
mat2 ria,gia,turn,spin;
const float sz = 3.;
const float hf = sz*.5;
vec2 map(vec3 pos, float sg){
vec2 res = vec2(1e5,0.);
pos.y+=1.25;;
vec3 q = pos-vec3(0,2.5,0);
vec3 b = pos-vec3(0,1.05,0);
q.yx*=spin;
q.zx*=spin;
float d1 = box(q,vec3(1.5))-.0423;
if(d1<res.x){
res = vec2(d1,2.);
hit=q;
}
b.xz=abs(abs(abs(b.xz)-12.)-6.)-3.;
vec3 hitb = b;
float pillar = cap(b,.3,1.5);
vec3 tb =vec3(b.x,abs(b.y),b.z);
float pcap = box(tb-vec3(0,1.45,0),vec3(.5,.15,.5))-.005;
pcap=min(cap(tb-vec3(0,1.3,0),.4,.05),pcap);
modPolar(b.xz,16.);
vec3 bt = b-vec3(.3,-1.1,0);
float grv = vcap(bt,2.2,.045);
pillar = max(pillar,-grv);
pillar = min(pcap,pillar);
if(pillar<res.x)
{
res = vec2(pillar*.75,3.);
hit=hitb;
}
float ground =pos.y+.5;
if(ground<res.x){
res = vec2(ground,1.);
hit=pos;
}
return res;
}
vec3 normal(vec3 p, float t){
float e = MIN_DIST*t;
vec2 h =vec2(1,-1)*.5773;
vec3 n = h.xyy * map(p+h.xyy*e,0.).x+
h.yyx * map(p+h.yyx*e,0.).x+
h.yxy * map(p+h.yxy*e,0.).x+
h.xxx * map(p+h.xxx*e,0.).x;
return normalize(n);
}
vec2 marcher(vec3 ro, vec3 rd, int maxsteps, float sg){
float d = 0.;
float m = 0.;
for(int i=0;i<maxsteps;i++){
vec2 ray = map(ro + rd * d, sg);
if(ray.x<MIN_DIST*d||d>MAX_DIST) break;
d += ray.x * .85;
m = ray.y;
}
return vec2(d,m);
}
// Tri-Planar blending function. GPU Gems 3 - Ryan Geiss:
vec3 tex3D(sampler2D t, in vec3 p, in vec3 n ){
n = max(abs(n), MIN_DIST);
n /= dot(n, vec3(1));
vec3 tx = texture(t, p.yz).xyz;
vec3 ty = texture(t, p.zx).xyz;
vec3 tz = texture(t, p.xy).xyz;
return mat3(tx*tx, ty*ty, tz*tz)*n;
//return (tx*tx*n.x + ty*ty*n.y + tz*tz*n.z);
}
float sface;
vec3 getFace(int face, vec3 p){
float cir = 0.;
vec2 uv;
if(face==0) uv=p.yz;
if(face==1) uv=p.zx;
if(face==2) uv=p.xy;
if(face==3) uv=p.xy;
if(face==4) uv=p.xz;
if(face==5) uv=p.zy;
uv*=2.;
float px = fwidth(uv.x);
vec2 grid_uv = fract(uv)-.5;
vec2 grid_id = floor(uv);
float chk = mod(grid_id.y + grid_id.x,2.) * 2. - 1.;
float hs = hash21(grid_id);
if(hs>.5) grid_uv.x*=-1.;
vec2 d2 = vec2(length(grid_uv-.5), length(grid_uv+.5));
vec2 gx = d2.x<d2.y? vec2(grid_uv-.5) : vec2(grid_uv+.5);
float circle = length(gx)-.5;
float circle2 =abs(circle)-.025;
circle2=smoothstep(.001+px,-px,circle2);
circle=(chk>0.^^ hs>.5)?smoothstep(-px,.001+px,circle):smoothstep(.001+px,-px,circle);
cir= mix(cir,.0,circle);
cir= mix(cir,1.,circle2);
sface=mix(0.,1.,circle);
return vec3(cir);
}
// based on bmp mapping from
// https://www.shadertoy.com/view/ld3yDn
vec3 doBumpMap( vec3 p, vec3 n, float bf, float per, int face){
vec2 e = vec2(per*MIN_DIST, 0);
mat3 m = mat3(
getFace(face, p - e.xyy),
getFace(face, p - e.yxy),
getFace(face, p - e.yyx)
);
vec3 g = vec3(0.299, 0.587, 0.114) * m;
g = (g - dot(getFace(face, p), vec3(0.299, 0.587, 0.114)) )/e.x; g -= n*dot(n, g);
return normalize( n + g*bf );
}
vec4 FC = vec4(0.019,0.019,0.019,0.);
vec4 render(inout vec3 ro, inout vec3 rd, inout vec3 ref, bool last, inout float d, vec2 uv) {
vec3 C = vec3(0);
vec2 ray = marcher(ro,rd,128, 1.);
hitPoint = hit;
gia=ria;
d = ray.x;
float m = ray.y;
float alpha = 0.;
if(d<MAX_DIST)
{
vec3 p = ro + rd * d;
vec3 n = normal(p,d);
vec3 lpos =vec3(3.,8,3.);
vec3 lpos2 =vec3(5.,5,-5.);
vec3 l = normalize(lpos-p);
vec3 l2 = normalize(lpos2-p);
vec3 h = vec3(.5);
vec3 hp = hitPoint;
vec3 cuv;
int face;
vec3 tn = n;
vec3 cn = n;
if(m==2.){
tn = n;
tn.yx*=spin;
tn.zx*=spin;
//https://www.shadertoy.com/view/3sVBDd
//finding the face of a cube using normal
vec3 aN = abs(tn);
ivec3 idF = ivec3(tn.x<-.25? 0 : 5, tn.y<-.25? 1 : 4, tn.z<-.25? 2 : 3);
face = aN.x>.5? idF.x : aN.y>.5? idF.y : idF.z;
// set coords
if(face==0) cuv = hp.xyz;
if(face==1) cuv = hp.xyz;
if(face==2) cuv = hp.xyz;
if(face==3) cuv = hp.xyz;
if(face==4) cuv = hp.zyx;
if(face==5) cuv = hp.xyz;
// get bump map surface
n=doBumpMap( cuv, n, .05, d, face);
}
float diff = clamp(dot(n,l),0.,1.);
float diff2 = clamp(dot(n,l2),0.,1.);
float fresnel = pow(clamp(1.+dot(rd, n), 0., 1.), 9.);
fresnel = mix(.01, .7, fresnel);
float shdw = 1.0;
for( float t=.01; t < 11.; )
{
float h = map(p + l*t,0.).x;
if( h<MIN_DIST ) { shdw = 0.; break; }
shdw = min(shdw, 16.*h/t);
t += h;
if( shdw<MIN_DIST || t>32. ) break;
}
float shdw2 = 1.0;
for( float t=.01; t < 11.; )
{
float h = map(p + l2*t,0.).x;
if( h<MIN_DIST ) { shdw2 = 0.; break; }
shdw2 = min(shdw2, 16.*h/t);
t += h;
if( shdw2<MIN_DIST || t>32. ) break;
}
diff = mix(diff,diff*shdw,.75);
diff2 = mix(diff2,diff2*shdw2,.75);
vec3 diffMix =vec3(0);
diffMix = diff * vec3(0.502,0.290,0.000);
diffMix += diff2 * vec3(0.004,0.510,0.894);
vec3 view = normalize(p - ro);
vec3 ret = reflect(normalize(lpos), n);
float spec = 0.5 * pow(max(dot(view, ret), 0.), (m==2.||m==4.)?24.:64.);
// materials
if(m==1.){
h=tex3D(iChannel1,hitPoint*.25,n).rgb;
C = (diffMix*h);
ref = vec3(clamp(.35-fresnel-(d*.01),.01,1.));
}
if(m==2.){
h = mix(tex3D(iChannel0,cuv*.55,tn).rgb, tex3D(iChannel2,cuv*.75,tn).rgb ,sface);
C = (diffMix*h)+spec;
ref = vec3(clamp(sface-fresnel,.01,.9));
}
if(m==3.){
h=clamp(tex3D(iChannel2,hitPoint*.5,tn).rrr+.5+fresnel,vec3(0),vec3(.8));//vec3(.2);
C = (diffMix*h);
ref = clamp((h-fresnel)-.5,vec3(0),vec3(1));
}
C = mix(FC.rgb,C,exp(-.00015*d*d*d));
ro = p+n*.001;
rd = reflect(rd,n);
} else {
if(last) C = mix(FC.rgb,C,exp(-.000015*d*d*d));
}
C = clamp(C,vec3(0),vec3(1));
return vec4(C,alpha);
}
void main()
{
float timer = T*04.*PI/180.;
turn = rot(timer);
spin = rot(T*15.*PI/180.);
vec2 F = gl_FragCoord.xy;
vec2 uv = (2.*F.xy-R.xy)/max(R.x,R.y);
vec3 ro = vec3(0,0,7.75);
vec3 rd = normalize(vec3(uv,-1));
//mouse
float x = M.xy == vec2(0) ? 0. : -(M.y/R.y * 1. - .5) * PI;
float y = M.xy == vec2(0) ? 0. : -(M.x/R.x * 1. - .5) * PI;
if(x<-.15)x=-.15;
mat2 rx = rot(x+.1);
mat2 ry = rot(y+timer);
ro.yz *= rx;
rd.yz *= rx;
ro.xz *= ry;
rd.xz *= ry;
// reflection loop (@BigWings)
vec3 C = vec3(0);
vec3 ref=vec3(0), fil=vec3(1);
float d =0.;
float numBounces = 2.;
// 3 is pretty but slows down
for(float i=0.; i<numBounces; i++) {
vec4 pass = render(ro, rd, ref, i==numBounces-1., d, uv);
C += pass.rgb*fil;
fil*=ref;
if(i==0.) FC = vec4(FC.rgb,exp(-.000075*d*d*d));
}
C = mix(C,FC.rgb,1.-FC.w);
// gamma
C = clamp(C,vec3(0),vec3(1));
C = pow(C, vec3(.4545));
fragColor = vec4(C,1.);
}
</script>
<div id="container" />
// Mouse Class for movments and attaching to dom //
class Mouse {
constructor(element) {
this.element = element || window;
this.drag = false;
this.x =
~~(document.documentElement.clientWidth, window.innerWidth || 0) / 2;
this.y =
~~(document.documentElement.clientHeight, window.innerHeight || 0) / 2;
this.pointer = this.pointer.bind(this);
this.getCoordinates = this.getCoordinates.bind(this);
this.events = ["mouseenter", "mousemove"];
this.events.forEach((eventName) => {
this.element.addEventListener(eventName, this.getCoordinates);
});
this.element.addEventListener("mousedown", () => {
this.drag = true;
});
this.element.addEventListener("mouseup", () => {
this.drag = false;
});
}
reset = () => {
this.x =
~~(document.documentElement.clientWidth, window.innerWidth || 0) / 2;
this.y =
~~(document.documentElement.clientHeight, window.innerHeight || 0) / 2;
};
getCoordinates(event) {
event.preventDefault();
const x = event.pageX;
const y = event.pageY;
if (this.drag) {
this.x = x;
this.y = y;
}
}
pointer() {
return {
x: this.x,
y: this.y
};
}
}
const woodTexture = "https://assets.codepen.io/163598/tile01.jpg";
const rockTexture = "https://assets.codepen.io/163598/texture3.jpg";
const stoneTexture = "https://assets.codepen.io/163598/texture2.jpg";
const textureList = [stoneTexture, woodTexture, rockTexture];
// Boostrap for WebGL and Attaching Shaders //
// Fragment & Vertex Shaders in HTML window //
class Render {
constructor() {
this.start = Date.now();
this.umouse = [0.0, 0.0, 0.0, 0.0];
this.tmouse = [0.0, 0.0, 0.0, 0.0];
// Setup WebGL canvas and surface object //
// Make Canvas and get WebGl2 Context //
let width = (this.width = ~~(document.documentElement.clientWidth,
window.innerWidth || 0));
let height = (this.height = ~~(document.documentElement.clientHeight,
window.innerHeight || 0));
const canvas = (this.canvas = document.createElement("canvas"));
const container = document.getElementById("container");
canvas.id = "GLShaders";
canvas.width = width;
canvas.height = height;
this.mouse = new Mouse(canvas);
document.body.appendChild(canvas);
const gl = (this.gl = canvas.getContext("webgl2"));
if (!gl) {
console.warn("WebGL 2 is not available.");
return;
}
// WebGl and WebGl2 Extension //
this.gl.getExtension("OES_standard_derivatives");
this.gl.getExtension("EXT_shader_texture_lod");
this.gl.getExtension("OES_texture_float");
this.gl.getExtension("WEBGL_color_buffer_float");
this.gl.getExtension("OES_texture_float_linear");
this.gl.viewport(0, 0, canvas.width, canvas.height);
// always nice to let people resize
window.addEventListener(
"resize",
() => {
let width = ~~(document.documentElement.clientWidth,
window.innerWidth || 0);
let height = ~~(document.documentElement.clientHeight,
window.innerHeight || 0);
this.mouse.reset();
this.canvas.width = width;
this.canvas.height = height;
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.resolution = new Float32Array([width, height]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program, "resolution"),
this.resolution
);
this.clearCanvas();
},
false
);
this.init();
}
// Shader Bootstrap code //
createShader = (type, source) => {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
const success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (!success) {
console.log(this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return false;
}
return shader;
};
createWebGL = (vertexSource, fragmentSource, images) => {
// Setup Vertext/Fragment Shader functions
this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
this.fragmentShader = this.createShader(
this.gl.FRAGMENT_SHADER,
fragmentSource
);
// Setup Program and Attach Shader functions
this.program = this.gl.createProgram();
this.gl.attachShader(this.program, this.vertexShader);
this.gl.attachShader(this.program, this.fragmentShader);
this.gl.linkProgram(this.program);
this.gl.useProgram(this.program);
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
console.warn(
"Unable to initialize the shader program: " +
this.gl.getProgramInfoLog(this.program)
);
return null;
}
// Create and Bind buffer //
const buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
this.gl.STATIC_DRAW
);
const vPosition = this.gl.getAttribLocation(this.program, "vPosition");
this.gl.enableVertexAttribArray(vPosition);
this.gl.vertexAttribPointer(
vPosition,
2, // size: 2 components per iteration
this.gl.FLOAT, // type: the data is 32bit floats
false, // normalize: don't normalize the data
0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
this.clearCanvas();
this.importUniforms(images);
};
/**
Textures
*/
isPowerOf2 = (value) => {
return (value & (value - 1)) == 0;
};
getImage = (url) => {
return new Promise((resolve, reject) => {
let img = new Image();
img.crossOrigin = "Anonymous";
img.addEventListener("load", (e) => resolve(img));
img.addEventListener("error", () => {
reject(new Error(`Failed to load image's URL: ${url}`));
});
img.src = url;
});
};
loadTexture = (textureList) => {
const lockNames = [
this.gl.TEXTURE0,
this.gl.TEXTURE1,
this.gl.TEXTURE2,
this.gl.TEXTURE3
];
const textureOptions = [
[this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE],
[this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE],
[this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST],
[this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST]
];
let promises = textureList.map((item) => this.getImage(item));
Promise.all(promises).then((images) => {
const amount = images.length;
let textures = [];
for (let ii = 0; ii < amount; ++ii) {
const texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
// Upload the image into the texture.
this.gl.texImage2D(
this.gl.TEXTURE_2D,
0,
this.gl.RGBA,
this.gl.RGBA,
this.gl.UNSIGNED_BYTE,
images[ii]
);
if (
this.isPowerOf2(images[ii].width) &&
this.isPowerOf2(images[ii].height)
) {
this.gl.generateMipmap(this.gl.TEXTURE_2D);
} else {
// Set the parameters so we can render any size image.
this.gl.texParameteri(...textureOptions[0]);
this.gl.texParameteri(...textureOptions[1]);
this.gl.texParameteri(...textureOptions[2]);
this.gl.texParameteri(...textureOptions[3]);
}
// add the texture to the array of textures.
textures.push(texture);
}
// lookup the sampler locations.
const u_image0Location = this.gl.getUniformLocation(
this.program,
"iChannel0"
);
const u_image1Location = this.gl.getUniformLocation(
this.program,
"iChannel1"
);
const u_image2Location = this.gl.getUniformLocation(
this.program,
"iChannel2"
);
// set which texture units to render with.
this.gl.uniform1i(u_image0Location, 0); // texture unit 0
this.gl.uniform1i(u_image1Location, 1); // texture unit 1
this.gl.uniform1i(u_image2Location, 2); // texture unit 1
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, textures[0]);
this.gl.activeTexture(this.gl.TEXTURE1);
this.gl.bindTexture(this.gl.TEXTURE_2D, textures[1]);
this.gl.activeTexture(this.gl.TEXTURE2);
this.gl.bindTexture(this.gl.TEXTURE_2D, textures[2]);
});
console.log("done loading textures");
};
clearCanvas = () => {
this.gl.clearColor(0, 0, 0, 0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
};
// add other uniforms here
importUniforms = (images) => {
const width = ~~(document.documentElement.clientWidth,
window.innerWidth || 0);
const height = ~~(document.documentElement.clientHeight,
window.innerHeight || 0);
this.resolution = new Float32Array([width, height]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program, "resolution"),
this.resolution
);
// get the uniform ins from the shader fragments
this.ut = this.gl.getUniformLocation(this.program, "time");
this.ms = this.gl.getUniformLocation(this.program, "mouse");
this.loadTexture(images);
};
// things that need to be updated per frame
updateUniforms = () => {
let tm = (Date.now() - this.start) / 1000;
//prevent time from getting too big
if (tm > 2000) this.start = Date.now();
this.gl.uniform1f(this.ut, (Date.now() - this.start) / 1000);
const mouse = this.mouse.pointer();
this.umouse = [mouse.x, this.canvas.height - mouse.y, 0];
const factor = 0.15;
this.tmouse[0] =
this.tmouse[0] - (this.tmouse[0] - this.umouse[0]) * factor;
this.tmouse[1] =
this.tmouse[1] - (this.tmouse[1] - this.umouse[1]) * factor;
this.tmouse[2] = this.mouse.drag ? 1 : 0;
this.gl.uniform4fv(this.ms, this.tmouse);
this.gl.drawArrays(
this.gl.TRIANGLE_FAN, // primitiveType
0, // Offset
4 // Count
);
};
// setup shaders and send to render loop
init = () => {
this.createWebGL(
document.getElementById("vertexShader").textContent,
document.getElementById("fragmentShader").textContent,
textureList
);
this.renderLoop();
};
renderLoop = () => {
this.updateUniforms();
this.animation = window.requestAnimationFrame(this.renderLoop);
};
}
const demo = new Render(document.body);
html {
height: 100%;
}
img {
display: none;
}
body {
background: #000;
overflow: hidden;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
text-align: center;
}
canvas {
height: 100%;
width: 100%;
margin: auto;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment