diff --git a/examples/droste_effect_1/index.js b/examples/droste_effect_1/index.js new file mode 100644 index 0000000..a592d7c --- /dev/null +++ b/examples/droste_effect_1/index.js @@ -0,0 +1,41 @@ +import vert0 from './renderpass0/vert.js'; +import compute0 from './renderpass0/compute.js'; +import frag0 from './renderpass0/frag.js'; + +import vert1 from './renderpass1/vert.js'; +import compute1 from './renderpass1/compute.js'; +import frag1 from './renderpass1/frag.js'; + +import Points from 'points'; +import ShaderType from 'shadertype'; +import RenderPasses from 'renderpasses'; +import RenderPass from 'renderpass'; + + +/** + * Genuary 3: Droste Effect + * http://roy.red/posts/droste/ + * https://github.com/ruby-processing/picrate-examples/blob/master/library/video/capture/data/droste.glsl + */ + +const base = { + + renderPasses: [ + new RenderPass(vert0, frag0, compute0), + new RenderPass(vert1, frag1, compute1) + ], + /** + * + * @param {Points} points + */ + init: async (points, folder) => { + points.setSampler('imageSampler', null, ShaderType.FRAGMENT); + points.setTexture2d('feedbackTexture', true); + points.setStorage('variables', 'Variables'); + points.setStorage('colors', 'array'); + }, + update: points => { + } +} + +export default base; \ No newline at end of file diff --git a/examples/droste_effect_1/renderpass0/compute.js b/examples/droste_effect_1/renderpass0/compute.js new file mode 100644 index 0000000..ff3883d --- /dev/null +++ b/examples/droste_effect_1/renderpass0/compute.js @@ -0,0 +1,30 @@ +import { structs } from '../structs.js'; + +const compute = /*wgsl*/` + +${structs} + +@compute @workgroup_size(8,8,1) +fn main( + @builtin(global_invocation_id) GlobalId: vec3, + @builtin(workgroup_id) WorkGroupID: vec3, + @builtin(local_invocation_id) LocalInvocationID: vec3 +) { + + if(variables.init == 0){ + var index = 0; + colors[index] = vec3f(248, 208, 146) / 255; index++; + colors[index] = vec3f(21, 144, 151) / 255; index++; + colors[index] = vec3f(56, 164, 140) / 255; index++; + colors[index] = vec3f(26, 86, 120) / 255; index++; + colors[index] = vec3f(37, 36, 93) / 255; index++; + colors[index] = vec3f(87, 28, 86) / 255; index++; + + variables.init = 1; + } + + +} +`; + +export default compute; diff --git a/examples/droste_effect_1/renderpass0/frag.js b/examples/droste_effect_1/renderpass0/frag.js new file mode 100644 index 0000000..37e7691 --- /dev/null +++ b/examples/droste_effect_1/renderpass0/frag.js @@ -0,0 +1,127 @@ +import { fnusin } from 'animation'; +import { structs } from '../structs.js'; +const frag = /*wgsl*/` + +${fnusin} +${structs} + +fn opSmoothSubtraction(d1: f32, d2: f32, k: f32) -> f32{ + let h = clamp(.5 - .5 * (d2 + d1) / k, 0., 1.); + return mix(d2, -d1, h) + k * h * (1. - h); +} + +fn smin(d1: f32, d2: f32, k: f32) -> f32{ + let h = max(k-abs(d1-d2), 0.)/k; + return min(d1, d2) - h*h*h*k*(1./6.); +} + +fn sdBox(p:vec3f, b: vec3f) -> f32 { + let q = abs(p) - b; + return length(max(q, vec3f(0.) )) + min(max(q.x, max(q.y, q.z)), 0.); +} + +fn rot2d(angle: f32) -> mat2x2 { + let s = sin(angle); + let c = cos(angle); + return mat2x2(c, -s, s, c); +} + +fn paletteLerp(a:array, value:f32) -> vec3f { + let numElements = 6.; + let elementPercent = 1 / numElements; + let index = value / elementPercent; + let minIndex = i32(floor(index)); + let maxIndex = i32(ceil(index)); + + let a0 = a[minIndex]; + let a1 = a[maxIndex]; + + return mix(a0, a1, fract(index)); +} + +fn map(p: vec3f, step:f32) -> f32 { + // input copy to rotate + var q = p; + var qRotated = q.xy * rot2d(params.time * .53); + q = vec3(qRotated, q.z); + + qRotated = q.xz * rot2d(params.time * .633); + q = vec3(qRotated, q.y); + + // scale down by 4 with p*4 and correcting distotrion dividing by 4 + let scale = .5; + + // for repetition + let boxBase = sdBox(q * scale, vec3(.5)) / scale; // cube sdf + let boxHollow1 = sdBox(q * scale, vec3(.4,.4, 1.)) / scale; // cube sdf + let boxHollow2 = sdBox(q * scale, vec3(1.,.4, .4)) / scale; // cube sdf + let boxHollow3 = sdBox(q * scale, vec3(.4,1., .4)) / scale; // cube sdf + + var box = opSmoothSubtraction(boxHollow1, boxBase, .1); + box = opSmoothSubtraction(boxHollow2, box, .1); + box = opSmoothSubtraction(boxHollow3, box, .1); + + let ground = p.y + .75; + + // closest distance to the scene + return smin(ground, box, 1.); +} + +@fragment +fn main( + @location(0) color: vec4, + @location(1) uv: vec2, + @location(2) ratio: vec2, // relation between params.screen.x and params.screen.y + @location(3) uvr: vec2, // uv with aspect ratio corrected + @location(4) mouse: vec2, + @builtin(position) position: vec4 +) -> @location(0) vec4 { + let sliderA = 1.; + let sliderB = .1116; + let uv2 = uvr * 4 - (vec2(2) * ratio); // clip space + // let m = mouse * 4 - (vec2(2) * ratio); + let m = vec2f(.5, .5) * 4 - (vec2(2) * ratio); + + // initialization + var ro = vec3f(0, 0, -3); // ray origin + var rd = normalize(vec3(uv2 * sliderB * 5, 1)); // ray direction one ray per uv position + + var t = 0.; // total distance traveled // travel distance + + var col = vec3f(); + + // Vertical camera rotation + let mouseRotY = rot2d(-m.y); + // ro.xz *= rot2d(-m.x); + ro = vec3(ro.x, ro.yz * mouseRotY); + // rd.xz *= rot2d(-m.x); + rd = vec3(rd.x, rd.yz * mouseRotY); + + // Horizontal camera rotation + let mouseRotX = rot2d(-m.x); + // ro.xz *= rot2d(-m.x); + ro = vec3(ro.xz * mouseRotX, ro.y).xzy; + // rd.xz *= rot2d(-m.x); + rd = vec3(rd.xz * mouseRotX, rd.y).xzy; + + // Raymarching + var i = 0; + for (; i < 80; i++) { + let p = ro + rd * t; // position along the ray + let d = map(p, f32(i)); // current distance to the scene + t += d; // "march" the ray + + // early stop if close enough, test this .001 value with others to test + // early stop if too far + if(d < .001 || d > 100.){ + break; + } + } + let value = (t * sliderA * f32(i) * .005); + col = paletteLerp(colors, value); + + return vec4(col, 1); +} +`; + +export default frag; diff --git a/examples/droste_effect_1/renderpass0/vert.js b/examples/droste_effect_1/renderpass0/vert.js new file mode 100644 index 0000000..3a7e5c9 --- /dev/null +++ b/examples/droste_effect_1/renderpass0/vert.js @@ -0,0 +1,17 @@ +import { structs } from '../structs.js'; + +const vert = /*wgsl*/` +${structs} +@vertex +fn main( + @location(0) position: vec4, + @location(1) color: vec4, + @location(2) uv: vec2, + @builtin(vertex_index) vertexIndex: u32 +) -> Fragment { + + return defaultVertexBody(position, color, uv); +} +`; + +export default vert; diff --git a/examples/droste_effect_1/renderpass1/compute.js b/examples/droste_effect_1/renderpass1/compute.js new file mode 100644 index 0000000..7e21271 --- /dev/null +++ b/examples/droste_effect_1/renderpass1/compute.js @@ -0,0 +1,15 @@ +import { structs } from '../structs.js'; + +const compute = /*wgsl*/` +${structs} +@compute @workgroup_size(8,8,1) +fn main( + @builtin(global_invocation_id) GlobalId: vec3, + @builtin(workgroup_id) WorkGroupID: vec3, + @builtin(local_invocation_id) LocalInvocationID: vec3 +) { + // let time = params.time; +} +`; + +export default compute; diff --git a/examples/droste_effect_1/renderpass1/frag.js b/examples/droste_effect_1/renderpass1/frag.js new file mode 100644 index 0000000..8c2191a --- /dev/null +++ b/examples/droste_effect_1/renderpass1/frag.js @@ -0,0 +1,106 @@ +import { sdfLine2, sdfSegment } from 'sdf'; +import { fnusin } from 'animation'; +import { snoise } from 'noise2d'; +import { PI, E, rotateVector } from 'math'; +import { texturePosition } from 'image'; +import { RGBAFromHSV, layer } from 'color'; +import { structs } from '../structs.js'; +const frag = /*wgsl*/` + +${structs} +${fnusin} +${snoise} +${sdfSegment} +${sdfLine2} +${PI} +${E} +${texturePosition} +${rotateVector} +${layer} +${RGBAFromHSV} + +const RADIAN = 0.0174533; + +fn sdfRing(position:vec2f, radius1:f32, uv:vec2f) -> f32 { + let d = distance(uv, position); + let st0 = 1. - smoothstep(radius1, radius1, d); + let st1 = smoothstep(radius1 - .004, radius1 - .004, d); + return st0 * st1; +} + +fn annulus(position:vec2f, radius1:f32, radius2:f32, uv:vec2f) -> f32 { + let ring1 = sdfRing(position, radius1, uv); + let ring0 = sdfRing(position, radius2, uv); + const w = .004; + var vertical = sdfLine2(position + vec2(0, radius1), position + vec2(0, radius2), w, uv); + vertical += sdfLine2(position + vec2(0, -radius1), position + vec2(0, -radius2), w, uv); + var horizontal = sdfLine2(position + vec2(radius1, 0), position + vec2(radius2, 0), w, uv); + horizontal += sdfLine2(position + vec2(-radius1, 0), position + vec2(-radius2, 0), w, uv); + return ring0 + ring1 + vertical + horizontal; +} + +fn complexExp(z: vec2f) -> vec2f { + return vec2(exp(z.x)*cos(z.y),exp(z.x)*sin(z.y)); +} + +fn complexLog(z: vec2f) -> vec2f { + return vec2(log(length(z)), atan2(z.y, z.x)); +} +fn complexMult(a:vec2f, b:vec2f) -> vec2f { + return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); +} + +fn complexMag(z:vec2f) -> f32 { + return pow(length(z), 2.0); +} +fn complexReciprocal(z:vec2f) -> vec2f { + return vec2(z.x / complexMag(z), -z.y / complexMag(z)); +} + +fn complexDiv(a:vec2f, b:vec2f) -> vec2f { + return complexMult(a, complexReciprocal(b)); +} + +@fragment +fn main( + @location(0) color: vec4, + @location(1) uv: vec2, + @location(2) ratio: vec2, // relation between params.screen.x and params.screen.y + @location(3) uvr: vec2, // uv with aspect ratio corrected + @location(4) mouse: vec2, + @builtin(position) position: vec4 +) -> @location(0) vec4 { + + let sliderA = .192; + let sliderB = 1 + 40 * fnusin(1); + let r1 = 0.3; + let r2 = 0.7; + let pos = vec2f(0); + var z = (uvr * 2) - (vec2(1) * ratio); + z = z * sliderB * 10; + + // 4. Take the tiled strips back to ordinary space. + z = complexLog(z); + // 3. Scale and rotate the strips + let scale = log(r2/r1); + // Negate the angle to twist the other way + let angle = atan(scale/(2.0*PI)); + z = complexDiv(z, complexExp(vec2(0,angle))*cos(angle)); + + // 2. Tile the strips + z.x = z.x % log(r2/r1); + // 1. Take the annulus to a strip + z = complexExp(z) * r1; + + let c = RGBAFromHSV( atan2(z.y,z.x)/PI*2,1.,1.); + let imageColor = texturePosition(feedbackTexture, imageSampler, vec2(-.5) * ratio, z / sliderA / 10 , false); + + var a = annulus(pos, r1, r2, z); + + let board = sin(z*20.0)*10.; + + return imageColor; +} +`; + +export default frag; diff --git a/examples/droste_effect_1/renderpass1/vert.js b/examples/droste_effect_1/renderpass1/vert.js new file mode 100644 index 0000000..3a7e5c9 --- /dev/null +++ b/examples/droste_effect_1/renderpass1/vert.js @@ -0,0 +1,17 @@ +import { structs } from '../structs.js'; + +const vert = /*wgsl*/` +${structs} +@vertex +fn main( + @location(0) position: vec4, + @location(1) color: vec4, + @location(2) uv: vec2, + @builtin(vertex_index) vertexIndex: u32 +) -> Fragment { + + return defaultVertexBody(position, color, uv); +} +`; + +export default vert; diff --git a/examples/droste_effect_1/structs.js b/examples/droste_effect_1/structs.js new file mode 100644 index 0000000..d4bf461 --- /dev/null +++ b/examples/droste_effect_1/structs.js @@ -0,0 +1,7 @@ +export const structs = /*wgsl*/` + +struct Variables { + init: i32, +} + +`; diff --git a/examples/index_files/shader_projects.js b/examples/index_files/shader_projects.js index 12b73ae..7ff718a 100644 --- a/examples/index_files/shader_projects.js +++ b/examples/index_files/shader_projects.js @@ -12,6 +12,7 @@ export const shaderProjects = [ { name: 'Dithering 3 - 2', path: './dithering3_2/index.js', uri: 'dithering3_2', desc: 'Better dithering that affects the entire image at once.', author: 'absulit', authlink: 'http://absulit.com', fitWindow: false, enabled: true, tax: 'showcase' }, { name: 'Dithering 4', path: './dithering4/index.js', uri: 'dithering4', desc: '', author: 'absulit', authlink: 'http://absulit.com', fitWindow: true, enabled: true, tax: 'showcase' }, { name: 'Dithering Video 1', path: './dithering_video_1/index.js', uri: 'dithering_video_1', desc: '', author: 'absulit', authlink: 'http://absulit.com', fitWindow: false, enabled: true, tax: 'showcase' }, + { name: 'Droste Effect 1', path: './droste_effect_1/index.js', uri: 'droste_effect_1', desc: 'Raymarched scene with Droste Effect.
Based on http://roy.red/posts/droste/', author: 'absulit', authlink: 'http://absulit.com', fitWindow: true, enabled: true, tax: 'showcase, raymarching' }, { name: 'Events 1', path: './events1/index.js', uri: 'events1', desc: 'WGSL fires an event and is read on the JavaScript side. Visible in console.', author: 'absulit', authlink: 'http://absulit.com', fitWindow: true, enabled: true, tax: 'reference' }, { name: 'Image Scale 1', path: './imagescale1/index.js', uri: 'imagescale1', desc: 'Layering of images', author: 'absulit', authlink: 'http://absulit.com', fitWindow: true, enabled: true, tax: 'reference' }, { name: 'Image Texture 1', path: './imagetexture1/index.js', uri: 'imagetexture1', desc: 'How to load a texture.', author: 'absulit', authlink: 'http://absulit.com', fitWindow: true, enabled: true, tax: 'reference' }, diff --git a/examples/main.js b/examples/main.js index 87a5af7..4a64a4a 100644 --- a/examples/main.js +++ b/examples/main.js @@ -110,6 +110,10 @@ const authorLinkEl = infoEl.querySelector('#author-link'); async function loadShaderByIndex(index) { console.clear(); + if (index > shaderProjects.length) { + index = 0; + } + localStorage.setItem('selected-shader', index); const shaderProject = shaderProjects[index]; sourceBtn.href = `https://github.com/Absulit/points/tree/master/examples/${shaderProject.uri}`; @@ -137,7 +141,7 @@ async function loadShaderByURI() { } lastSelected = Array.from(document.querySelectorAll('#nav a')).filter(a => a.index == index)[0]; - lastSelected.classList.add('selected'); + lastSelected?.classList.add('selected'); } //--- diff --git a/examples/style.css b/examples/style.css index 2602168..3cd7cfd 100644 --- a/examples/style.css +++ b/examples/style.css @@ -130,4 +130,8 @@ h2 { #nowebgpu.show { display: flex; -} \ No newline at end of file +} + +div.dg.ac { + z-index: 2; +}