Week 5 Shader Explorations

download-31download-26download-24download-34download-36download-35

For WEEK 5, I worked with Mahnoor. I was tasked with the initial porting of our Week 3 shaders into p5.js and creating an image distortion shader.

For the image distortion shader, I created a distorter that affects the texture UV coordinates of the image, based on Simple Texture Distort by mikiex on Shader Toy. Instead of using a noise texture as mikiex did, I replaced it with generated simplex noise from the Book of Shaders. To create an inverted colour filter for the image, I divided the initial image that I added the distortion value to the UV with a mix of the distorted initial image texture with an altered second version of the image that instead subtracted the distortion from the UV. Mixing the two versions of the distorted image, one that follows the original colour scheme and another that is inverted distorts them in opposite directions. This results in a swipe effect where the value of distortion would become zero in both directions (looped using sin) and will form the original non-distorted image of the flowers for a split second.

The distorted colours and rounded shapes that the noise forms the image into reminds me of the psychedelic closeups of the surface of a soap bubble (caused by a two-beam interference of light).

Overall, I enjoyed the Shader exercises this week and had fun seeing how my partner, Mahnoor, approached shader art. It was also great to have immediate feedback during the process and to support each other and help when we got stuck.

There is not much to say about porting our Week 3 shaders into p5.js, other than every line of setup code counts. It was more complicated than I thought it would be, although fairly straightforward to execute once you are used to the process. I did have fun applying the shaders as textures to some simple (default) shapes (a sphere and box). I added simple interaction to the p5.js sketch to switch between 3D shapes and shaders on mouse click as well allow the viewer to rotate the shapes using mouse coordinates.


//Author: Madelaine Fischer-Bernhut
//Title: Image Distortion

#ifdef GL_ES
precision mediump float;
#endif

//get texture coordinates from vert shader
varying vec2 vTexCoord;
 
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

//texture coming from p5.js
uniform sampler2D imgtext;

//Book of Shaders Simplex Noise (based off Ian McEwan, Ashima Arts)
// Some useful functions
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
float snoise(vec2 v){
 const vec4 C = vec4(0.211324865405187, 0.366025403784439,
 -0.577350269189626, 0.024390243902439);
 vec2 i = floor(v + dot(v, C.yy) );
 vec2 x0 = v - i + dot(i, C.xx);
 vec2 i1;
 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
 vec4 x12 = x0.xyxy + C.xxzz;
 x12.xy -= i1;
 i = mod(i, 289.0);
 vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
 + i.x + vec3(0.0, i1.x, 1.0 ));
 vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
 dot(x12.zw,x12.zw)), 0.0);
 m = m*m ;
 m = m*m ;
 vec3 x = 2.0 * fract(p * C.www) - 1.0;
 vec3 h = abs(x) - 0.5;
 vec3 ox = floor(x + 0.5);
 vec3 a0 = x - ox;
 m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
 vec3 g;
 g.x = a0.x * x0.x + h.x * x0.y;
 g.yz = a0.yz * x12.xz + h.yz * x12.yw;
 return 130.0 * dot(m, g);
}

void main() {
 
 // position of the pixel divided by resolution, to get normalized positions on the canvas
 vec2 st = gl_FragCoord.xy/u_resolution.xy; 
 
 vec2 uv = vTexCoord;
 
 //texture is loaded upside down and backwards by default, so flip it
 uv.y = 1.0 - uv.y;
 
 //create a distorter (based off Simple Texture Distort by mikiex, instead of another texture it uses snoise)
 vec2 pos = vec2(st.x*10.0 - (u_time*0.2), st.y*10.0 + (u_time*0.2));
 
 vec3 dist = vec3(snoise(pos)*0.5+0.5);
 
 vec2 distorter = vec2(dist.rg) * vec2(0.1-sin(u_time*0.2));
 
 //zoom in and out of image by multiplying the "UV" value, use fract to tile zoomed out images 
 vec4 imgText = texture2D(imgtext, fract(uv)+distorter);
 vec4 imgText2 = texture2D(imgtext, fract(uv)-distorter);


 float grey = (imgText.r + imgText.g + imgText.b)/3.0;
 
 vec3 color = vec3(0); 
 
 //grey filter
 // color = vec3(grey); 
 
 //default (full) color
 color = vec3(imgText.r,imgText.g, imgText.b); 
 
 //blend in an "inverted" color filter version of the image
 // color /= mix(color, vec3(1.0), vec3(imgText2.r,imgText2.g, imgText2.b));

 color /= mix(color, vec3((sin(u_time*0.1))), vec3(imgText2.r,imgText2.g, imgText2.b));
 
 //direct image color
 // gl_FragColor = vec4(imgText);
 
 //filtered camera feed
 gl_FragColor = vec4(color, 1.0);
}

 

Shader Explorations Week 3

screenshot-928screenshot-934 screenshot-931screenshot-935

The above 4 images show several iterations of my code below. I had a lot of fun experimenting with creating a kaleidoscope effect by overlapping several dynamic circles (their shapes were altered/deformed using noise) at varying opacities, colour schemes/noise-based gradients, and offsets. Like in the noise tutorial from class, I decided to create the background with a dynamic gradient of colours also controlled by offset noise. Originally the circles used a similar rainbow noise gradient, but I felt that I wanted to create more contrast between the background and circles. I experimented (as shown above) with white, black, grey, and blue. It appeared that using other solid colours like red, green, and yellow didn’t create enough contrast.

I find it really cool how the circles are not the “main” kaleidoscope shapes but are used to create negative space that cuts through the background in order to create the effect. I added a basic outlined square to the tile pattern to add more-dynamic overlay effects. The brightness and opacity of the squares are controlled using horizontal mouse movements.


// Author: Madelaine Fischer-Bernhut
// Title: 2D Perlin Simplex Noise V12

#ifdef GL_ES
precision mediump float;
#endif

#define PI 3.142

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

//Book of Shaders Simplex Noise (based off Ian McEwan, Ashima Arts)
// Some useful functions
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }

float snoise(vec2 v){
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439);
vec2 i = floor(v + dot(v, C.yy) );
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+ i.x + vec3(0.0, i1.x, 1.0 ));
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
dot(x12.zw,x12.zw)), 0.0);
m = m*m ;
m = m*m ;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}

float map (float value, float min1, float max1, float min2, float max2){
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}

//rotation functions from the Book of Shaders "Patterns"
vec2 rotate2D(vec2 st, float angle){
st -= 0.5;
st = mat2(cos(angle), -sin(angle),
sin(angle), cos(angle))*st;
st += 0.5;
return st;

}

vec2 rotateTilePattern(vec2 st){

//scale coordinate system by 2x2
st *= 6.0;

//give each cell and index number according to its position
float i = 0.0;
i += step(1.0, mod(st.x,2.0));
i += step(1.0, mod(st.y,2.0))*2.0;

//each cell between 0.0 -1.0
st = fract(st);

//rotate each cell according to index
//use matrix to rotate the space

if(i == 1.0){
st = rotate2D(st, PI*-0.5);
} else if (i == 2.0){
st = rotate2D(st, PI*0.5);
} else if (i == 3.0){
st = rotate2D(st, PI*2.0);
} else if (i== 0.0){
st = rotate2D(st, PI);
}

return st;

}

float drawCircle(vec2 st, vec2 position, float size){

float result = distance(st, position);
result = 1.0 - step (size, result);

return result;

}

float drawRect(vec2 st, vec2 pos, vec2 size){
float result = 1.0;

//invert size so the size variables will control the size based on the white dimensions
vec2 border = (1.0-size)/2.0;

result = step(border.x + pos.x, st.x);
result *= step(border.x - pos.x, 1.0-st.x);
result *= step(border.y + pos.y, st.y);
result *= step(border.y - pos.y, 1.0-st.y);

return result;
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
st.x *= u_resolution.x/u_resolution.y;

vec3 color = vec3(0.0);

//scale coordinate system to see noise
vec2 pos = vec2(st.x*1.0 - sin(u_time*0.2), st.y*1.0 + cos(u_time*0.2));

//BACKGROUND
//background gradient color
color.r = snoise(pos) * 0.5 + 0.5;
color.b = snoise(pos + vec2(u_time*0.3)) * 0.5 + 0.5;
// color.r = snoise(pos + vec2(u_time*0.3)) * 0.5 + 0.5;
// color.g = snoise(pos + vec2(u_time*0.3)) * 0.5 + 0.5;
color.g = snoise(pos + vec2(u_time*0.1)) * 0.5 + 0.5;

//TILE and ROTATE
st *= vec2(0.5, 0.5);
st += rotate2D(st,PI*0.5);

//then rotate title patterns based on index/location
st = rotateTilePattern(st+0.25);

//CIRCLE SETUP
//Animated circle size with noise function, add "st" in vector with noise to create a dynamic shape
//create an offset in size and opacity for each subsequent circle
float circle = drawCircle(st, vec2(0.580,0.550), (snoise(st + vec2(u_time*0.2))*0.5 +0.5) * 0.5 + 0.1)*1.0;
float circle2 = drawCircle(st, vec2(0.500,0.500), (snoise(st + vec2(u_time*0.2))*0.5 +0.5) * 1.0 + 0.1)*0.75;
float circle3 = drawCircle(st, vec2(0.710,0.330), (snoise(st + vec2(u_time*0.2))*0.5 +0.5) * 1.5 + 0.1)*0.5;

vec3 circleColor = vec3(0.005,0.005,0.005);

//circle color gradient
// circleColor.r = snoise(pos) * 0.5 + 0.5;
// circleColor.b = snoise(pos + vec2(-u_time*0.3)) * 0.5 + 0.5;
// circleColor.g = snoise(pos + vec2(-u_time*0.1)) * 0.5 + 0.5;

//circle color blue gradient
// circleColor.b *= (snoise(pos) * 0.5 + 0.5)*0.2;
// circleColor.b += (snoise(pos + vec2(-u_time*0.3)) * 0.5 + 0.5)*0.2;
// circleColor.b += (snoise(pos + vec2(-u_time*0.1)) * 0.5 + 0.5)*0.2;

//circle color normal black and white gradient
circleColor = vec3(snoise(pos) * 0.5 + 0.5)*0.2;
circleColor += vec3(snoise(pos + vec2(-u_time*0.3)) * 0.5 + 0.5)*0.2;
circleColor += vec3(snoise(pos + vec2(-u_time*0.1)) * 0.5 + 0.5)*0.2;


//RECTANGLE OVERLAY
//solid black outline
// float rectangle = drawRect(st, vec2(0.0), vec2(0.9)-0.1);

//low opacity outline
float rectangle = drawRect(st, vec2(0.0), vec2(0.6)-0.1);
rectangle -= drawRect(st, vec2(0.0), vec2(0.8)-0.1);

//full opactity fill of rectangle
// rectangle = drawRect(st, vec2(0.0), vec2(0.8)-0.1);

//MIX/LAYER CIRCLES
//second mix parameter controls color of shape (circle)
color = vec3(mix(color, circleColor, circle));
color = vec3(mix(color, circleColor, circle2));
color = vec3(mix(color, circleColor, circle3));

//second mix parameter controls brightness/opacity of rectangular overlay
color = vec3(mix(color, vec3(u_mouse.x/u_resolution.x), rectangle));

gl_FragColor = vec4(color,1.0);
}

 

Shader Explorations 2

download-16download-17download-18download-19

The above four images show is a sequence of animated shader stills from left to right and top to bottom.

For this week’s homework shader explorations I decided to integrate my first shader, a Piet Mondrian inspired shader (created when I was exploring rectangle placement and layering effects for the Book of Shaders’ “recreate a Piet Mondrian painting” exercise), as I felt the design would make for a cool tile effect. The original colours followed Mondrian’s primary colour scheme (similar to the last still, but the small rectangle was yellow), but I felt that it needed to be more dynamic so I decided to animate a colour shifting gradient using u_time and smoothstep.

I still felt like I needed to add more variety of shapes, as I took the effort to make functions based on all of the shape exercises, so I decided on adding a polar shape. I found experimenting with the different parameters of polar shapes extremely fun, therefore I created a polar shape function that allowed me to control everything from position to length and width of spokes. Although I could have gone for a more complicated shape, I decided against it because the tiling pattern was also complex. It was an awesome moment when I realized I was able to layer the tile pattern over the polar design and see the solar shapes through the gap made with one of the rectangles.

Overall, I had a lot of fun learning about shapes and creating my own functions for them.

// Author: Madelaine Fischer-Bernhut
// Title: Animated Piet Mondrian Pattern v2

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

#define PI 3.14159265358979323846


//rotation and tile functions from the Book of Shaders "Patterns"
vec2 rotate2D(vec2 st, float angle){
 st -= 0.5;
 st = mat2(cos(angle), -sin(angle),
 sin(angle), cos(angle))*st;
 st += 0.5;
 return st;
 
}

vec2 tile(vec2 st, float zoom){
 //scale up space
 st *= zoom;
 
 //wrap around 1.0
 return fract(st);
}

vec2 rotateTilePattern(vec2 st){
 
 //scale coordinate system by 2x2
 st *= 2.0;
 
 //give each cell and index number according to its position
 float i = 0.0;
 i += step(1.0, mod(st.x,2.0));
 i += step(1.0, mod(st.y,2.0))*2.0;
 
 //each cell between 0.0 -1.0
 st = fract(st);
 
 //rotate each cell according to index
 //use matrix to rotate the space

 if(i == 1.0){
 st = rotate2D(st, PI*-0.5);
 } else if (i == 2.0){
 st = rotate2D(st, PI*0.5);
 } else if (i == 3.0){
 st = rotate2D(st, PI*2.0);
 } else if (i== 0.0){
 st = rotate2D(st, PI);
 }
 
 return st;
 
}

float drawRect(vec2 st, vec2 size, vec2 pos){
 float result = 1.0;
 
 //invert size so the size variables will controle the size based on the white dimensions
 vec2 border = (1.0-size)/2.0;
 
 result = step(border.x + pos.x, st.x);
 result *= step(border.x - pos.x, 1.0-st.x);
 result *= step(border.y + pos.y, st.y);
 result *= step(border.y - pos.y, 1.0-st.y);
 
 return result;
}

float createPolShape(vec2 st, float size, float spokesNum, vec2 spokeSize, vec2 pos, float rot, float blur){
 float result = 1.0;
 
 //control the position of the shape
 pos -= st;
 
 //control the size of the shape
 float r = length(pos)*size;
 float a = atan(pos.y, pos.x);
 
 //control the number of "spokes" and their shape
 //rotate and animate the shape by adding or subtracting from the "a" in cos or sin
 float f = cos(a*spokesNum + rot)*spokeSize.y+spokeSize.x;
 f = abs(cos(a*spokesNum+rot)*sin(a*spokesNum+rot))*spokeSize.y+spokeSize.x;
 // f = smoothstep(-.5,1.0, cos(a*spokesNum+rot))*spokeSize.y+spokeSize.x;
 
 //changing f+value effects focus of shape (blurred)
 result = 1.0-smoothstep(f,f+blur,r);

 
 return result;
}

void main() {
 //st are texture coordinates, s is along the horizontal axis and t along the vertical axis (surface)
 vec2 st = gl_FragCoord.xy/u_resolution.xy;
 st.x *= u_resolution.x/u_resolution.y;

 float outline = 0.05;
 
 //for testing: centre of polar shape position is based on mouse
 vec2 pos = u_mouse/u_resolution;
 pos = vec2(0.5);
 
 vec3 color = vec3(0.0);
 
 //polar shape will appear underneath Piet Mondrian pattern
 color = vec3(createPolShape(st, 2.15, 10.0, vec2(0.350,0.860), pos, u_time*2.0, -0.260));

 //divide space by value
 st = tile(st, 1.0);
 
 //first rotate entire space by 90 deg
 st += rotate2D(st,PI*0.5);
 
 //then rotate title patterns based on index/location
 st = rotateTilePattern(st);

 //create bordered rectangles
 
 //white rectangles
 //adding the initial rectangle layer instead of multiplying results in the Piet Mondrian Pattern being masked by the polar shape
 // color += vec3(drawRect(st, vec2(0.360,0.590) - vec2(outline), vec2(-0.320,0.210)));
 
 //multiplying a rectangle layer in with the other two added results in that rectangle becoming "transparent"
 color *= vec3(drawRect(st, vec2(0.360,0.590) - vec2(outline), vec2(-0.320,0.210)));
 color += vec3(drawRect(st, vec2(0.480,0.585) - vec2(outline), vec2(0.259,-0.067)));
 color += vec3(drawRect(st, vec2(1.0,0.165) - vec2(outline), vec2(0.000,-0.417)));
 
 //coloured rectangles (with animated gradient overlay effect using smoothstep to control placement, and offsetting those colour gradients by overlaying sin & cos time animations)
 color.r += drawRect(st, vec2(0.540,0.300) - vec2(outline), vec2(0.105,0.350)) * smoothstep(sin(u_time*0.4)*0.9,1.1,st.x) ;
 color.g += drawRect(st, vec2(0.540,0.300) - vec2(outline), vec2(0.105,0.350)) * smoothstep(cos(u_time*0.4)*0.9,1.1,st.x) ;
 color.r += drawRect(st, vec2(0.150,0.3) - vec2(outline), vec2(0.425,0.35))* smoothstep(cos(u_time*0.4)*0.9,1.5, st.y);
 color.g += drawRect(st, vec2(0.150,0.3) - vec2(outline), vec2(0.425,0.35))* smoothstep(sin(u_time*0.4)*0.9,1.5, st.y);
 color.b += drawRect(st, vec2(0.540,0.300) - vec2(outline), vec2(-0.230,-0.210)) * smoothstep(sin(u_time*0.4)*0.95,1.2, 1.0-st.x);
 color.g += drawRect(st, vec2(0.540,0.300) - vec2(outline), vec2(-0.230,-0.210)) * smoothstep(cos(u_time*0.4)*0.95,1.2, 1.0-st.x);


 gl_FragColor = vec4(color,1.0);
}

Shader Explorations 1

For my shader explorations, I had a lot of fun blending and animating together different iterations of the HSB colour spectrum as shown in the example from class and in The Book of Shaders. I spent a lot of time experimenting and creating my shaders partially through trial and error as I get used to the new language. I still have a lot to learn about how shaders work, most of my shader results, especially my first shader below, was as Bob Ross says “A Happy Little Accident”. Still, I was able to reverse engineer some of the variables, in order to create a better understanding of what exactly I was doing.  Even, so this resulted in quite a few unneeded and unused lines of code every now and then, so I learned a lot about how my shaders worked just through the process of cleaning up code as well.

HSB HSV Bricks Blend Shader
// Author: Madelaine Fischer-Bernhut
// Title: HSB HSV Colour Bricks Blend V8

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

// Function from Iñigo Quiles
// https://www.shadertoy.com/view/MsS3Wc

vec3 hsb2rgb( in vec3 c ){
 vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
 6.0)-3.0)-1.0,
 0.0,
 1.0 );
 rgb = rgb*rgb*(3.0-2.0*rgb);
 return c.z * mix(vec3(1.0), rgb, c.y);
}

void main() {
 
 vec2 st = gl_FragCoord.xy/u_resolution.xy;
 st.x *= u_resolution.x/u_resolution.y;
 
 vec3 color1 = hsb2rgb( vec3(sin(u_time)*0.1+st.y*0.9, 10.0, 2.588));
 vec3 color2 = color1*1.5 - hsb2rgb( vec3(cos(u_time)*-0.15+st.x*0.7, st.x, 1.8)) * color1;

 gl_FragColor = vec4(color2,1.0);
}
download-17download-11download-24
// Author: Madelaine Fischer-Bernhut 
// Title: HSB HSV Rainbow Wave Blend V6 
#ifdef GL_ES
precision mediump float;
#endif

#define PI 3.14159265359

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

// Function from Iñigo Quiles
// https://www.shadertoy.com/view/MsS3Wc

vec3 hsb2rgb( in vec3 c ){
 vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
 6.0)-3.0)-1.0,
 0.0,
 1.0 );
 rgb = rgb*rgb*(3.0-2.0*rgb);
 return c.z * mix(vec3(1.0), rgb, c.y);
}

void main() {
 
 vec2 st = gl_FragCoord.xy/u_resolution.xy;
 st.x *= u_resolution.x/u_resolution.y;
 
 float y = smoothstep(-st.x,(u_mouse.x/u_resolution.x)*3.0,st.x);

 vec3 color = vec3(y);

 vec3 color2 = hsb2rgb( vec3(exp(sin(u_time*0.4+st.y*0.7)), st.y, 1.0)) + color;
 vec3 color3 = color2*1.1- hsb2rgb( vec3(abs(u_time*0.4+st.y*1.0), st.x, 1.0));

 gl_FragColor = vec4(color3,1.0);
}