Created
December 7, 2020 00:24
-
-
Save antoineMoPa/bc26eb6b0c422cb0875a8a6111b16531 to your computer and use it in GitHub Desktop.
texture based on Mandelbrot (Procedural starry sky?)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<style> | |
document, body, html{ | |
font-family: sans-serif; | |
padding:0; | |
margin:0; | |
max-height:100%; | |
max-width:100%; | |
overflow:hidden; | |
} | |
h1{ | |
position:absolute; | |
top:10px; | |
left:10px; | |
z-index:1100; | |
background:#000; | |
padding:10px; | |
border-radius:3px; | |
color:#fff; | |
} | |
h1, h2{ | |
font-weight:300; | |
margin-top:0; | |
} | |
h2{ | |
font-size:20px; | |
} | |
.formula{ | |
width:calc(50%); | |
margin:10px; | |
padding:15px; | |
background:rgba(255,255,255,0.3); | |
border:none; | |
font-size:20px; | |
position:absolute; | |
top:10px; | |
left:calc(25%); | |
z-index:1000; | |
font-family:monospace; | |
color:rgba(0,0,0,0.4); | |
transition:all 0.3s; | |
text-align:center; | |
border-radius:3px; | |
text-align:left; | |
} | |
.formula:active,.formula:focus{ | |
background:rgba(255,255,255,0.9); | |
color:#335; | |
} | |
label{ | |
margin-left:10px; | |
font-size:12px; | |
} | |
canvas{ | |
position:absolute; | |
top:0; | |
left:0; | |
cursor:crosshair; | |
} | |
canvas:active{ | |
cursor: move; | |
} | |
.info{ | |
margin-left:10px; | |
color:#555; | |
font-size:12px; | |
} | |
.symbol{ | |
font-weight:700; | |
font-style:italic; | |
padding:0 2px; | |
} | |
.error{ | |
color:#f33; | |
font-size:12px; | |
padding:0 10px; | |
} | |
p.intro{ | |
font-size:12px; | |
margin-bottom:0; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>surface-explorer</h1> | |
<input onkeyup="new_formula(event.target.value)" | |
value="vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c" | |
class="formula"/> | |
<div class="error"></div> | |
<div class="info"></div> | |
<canvas class="texplorer" | |
onmousedown="mousedown(event)" | |
onmousemove="mousemove(event)" | |
onmouseup="mouseup(event)" | |
onmouseleave="mouseup(event)" | |
onwheel="wheel(event)" | |
/> | |
<script> | |
class ShaderSurface{ | |
constructor(canvas_el){ | |
this.canvas = canvas; | |
this.resize_handler(); | |
this.init_gl(); | |
this.create_shaders(); | |
this.custom_float_uniforms = {}; | |
this.custom_float_targets = {}; | |
window.addEventListener("resize", this.resize_handler.bind(this)); | |
function updater(timestamp){ | |
try { | |
this.draw(); | |
} catch (e) { | |
console.log(e); | |
} | |
this.update_values(timestamp); | |
window.requestAnimationFrame(updater.bind(this)); | |
} | |
this.draw(); | |
window.requestAnimationFrame(updater.bind(this)); | |
} | |
resize_handler(){ | |
this.canvas.width = window.innerWidth; | |
this.canvas.height = window.innerHeight; | |
} | |
create_shaders(code){ | |
code = code || "(i ^ j) % (j / i) < 1"; | |
this.fragment_shader = `#version 300 es | |
precision highp float; | |
in vec2 UV; | |
out vec4 out_color; | |
uniform float time; | |
uniform float ratio; | |
uniform float offset_i, offset_j, scale; | |
uniform float cursor_i, cursor_j; | |
void main(void){ | |
float x = UV.x * ratio; | |
float y = UV.y; | |
vec2 p = vec2(x - 0.5 * ratio,y - 0.5); | |
vec4 col = vec4(0.0); | |
float s = 10.0 * pow(2.0, scale); | |
p = vec2(-p.y * s - offset_j, p.x * s + offset_i); | |
vec2 c = vec2(p) * 0.2; | |
float cr = length(c); | |
float ca = atan(c.y,c.x); | |
//cr = 1.0-cr; | |
//ca = ca*16.0; | |
//cr -= 10.0; | |
//ca += 0.5; | |
c = vec2(cr * cos(ca), cr * sin(ca)); | |
vec2 z = vec2(0.0,0.0); | |
float circ = 0.0; | |
vec2 lastz = z; | |
for(int i = 0; i < 300; i++){ | |
z = ${code}; | |
float fi = float(i)/300.0; | |
//col.rgb += 0.02; | |
col.r += 0.001/length(lastz - z); | |
if (length(z) > 2.0) { | |
//break; | |
//col.rgb += 0.1; | |
z *= 0.2; | |
} | |
lastz = z; | |
} | |
if (abs(length(c) - 1.0) < 0.002) { | |
col.rb += 0.4; | |
} | |
if (abs(length(c)) < 0.004) { | |
col.rgb *= 0.0; | |
} | |
// debug cursor | |
//if(length(vec2(cursor_i,cursor_j) - p) < 0.01 * s){ | |
// col.r = 1.0; | |
// col.gb *= 0.1; | |
// col.a = 1.0; | |
//} | |
col.a = 1.0; | |
out_color = col; | |
}`; | |
this.vertex_shader = `#version 300 es | |
layout(location = 0) in vec3 position; | |
out vec2 UV; | |
out vec3 v_position; | |
void main(){ | |
v_position = position; | |
UV = vec2((position.x+1.0) / 2.0, (position.y + 1.0)/2.0); | |
gl_Position = vec4(v_position.x,v_position.y, 0.0, 1.0); | |
}`; | |
this.init_program(); | |
} | |
init_gl(){ | |
this.gl = this.canvas.getContext('webgl2'); | |
let gl = this.gl; | |
gl.clearColor(0.0, 0.0, 0.0, 1.0); | |
gl.enable(gl.DEPTH_TEST); | |
gl.depthFunc(gl.LEQUAL); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
// Triangle strip for whole screen square | |
const vertices = [ | |
(-1), (-1), 0, | |
(-1), 1, 0, | |
1, (-1), 0, | |
1, 1, 0 | |
]; | |
const tri = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, tri); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); | |
} | |
init_program() { | |
this.compiled = false; | |
let error = document.querySelectorAll(".error")[0]; | |
error.innerHTML = ""; | |
if (this.gl == null) { | |
return; | |
} | |
const gl = this.gl; | |
// Delete previous program | |
if (typeof gl.program !== 'undefined') { | |
gl.useProgram(gl.program); | |
if (this.fragment_shader_object > -1) { | |
gl.detachShader(gl.program, this.fragment_shader_object); | |
} | |
if (this.vertex_shader_object > -1) { | |
gl.detachShader(gl.program, this.vertex_shader_object); | |
} | |
gl.deleteShader(gl.fragment_shader); | |
gl.deleteShader(gl.vertex_shader); | |
gl.deleteProgram(gl.program); | |
} | |
gl.program = gl.createProgram(); | |
const vertex_shader = add_shader(gl.VERTEX_SHADER, this.vertex_shader); | |
const fragment_shader = add_shader(gl.FRAGMENT_SHADER, this.fragment_shader); | |
this.fragment_shader_object = fragment_shader; | |
this.vertex_shader_object = vertex_shader; | |
function add_shader(type, content) { | |
const shader = gl.createShader(type); | |
gl.shaderSource(shader, content); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
const err = gl.getShaderInfoLog(shader); | |
let error = document.querySelectorAll(".error")[0]; | |
error.innerText = "GLSL error" + err; | |
//error.innerHTML = error.innerHTML.replace("\n", "<br/>"); | |
} | |
gl.attachShader(gl.program, shader); | |
return shader; | |
} | |
if (vertex_shader == -1 || fragment_shader == -1) { | |
return; | |
} | |
gl.linkProgram(gl.program); | |
if (!gl.getProgramParameter(gl.program, gl.LINK_STATUS)) { | |
console.log(gl.getProgramInfoLog(gl.program)); | |
} | |
gl.useProgram(gl.program); | |
const positionAttribute = gl.getAttribLocation(gl.program, 'position'); | |
gl.enableVertexAttribArray(positionAttribute); | |
gl.vertexAttribPointer(positionAttribute, 3, gl.FLOAT, false, 0, 0); | |
this.compiled = true; | |
} | |
update_values(timestamp){ | |
let gl = this.gl; | |
this.last_timestamp = this.last_timestamp || timestamp - 0.1; | |
let elapsed = timestamp - this.last_timestamp; | |
for(let name in this.custom_float_uniforms){ | |
if (name in this.custom_float_targets) { | |
if (this.custom_float_targets[name] == null) { | |
// it was set but target was reached and we set to null | |
continue; | |
} | |
if (this.custom_float_targets[name].start == undefined) { | |
this.custom_float_targets[name].start = timestamp; | |
this.custom_float_targets[name].initial_value = this.custom_float_uniforms[name]; | |
} | |
let initial_value = this.custom_float_targets[name].initial_value; | |
let target = this.custom_float_targets[name].target_value; | |
let duration = 200; | |
let f = (timestamp - this.custom_float_targets[name].start)/duration; | |
f = 1.0 - Math.pow(1.0 - f, 2.0); | |
let value = (1.0-f) * initial_value + (f) * target; | |
if (this.custom_float_targets[name].on_update) { | |
this.custom_float_targets[name].on_update(value); | |
} | |
if (f > 0.99) { | |
if (this.custom_float_targets[name].on_update) { | |
this.custom_float_targets[name].on_update(target); | |
} | |
this.custom_float_targets[name] = null; | |
this.custom_float_uniforms[name] = target; | |
} | |
const att = gl.getUniformLocation(gl.program, name); | |
gl.uniform1f(att, value); | |
this.custom_float_uniforms[name] = value; | |
} | |
} | |
this.last_timestamp = timestamp; | |
} | |
draw() { | |
if (!this.compiled) { | |
return; | |
} | |
const gl = this.gl; | |
if (gl == null || gl.program == null) { | |
return; | |
} | |
const timeAttribute = gl.getUniformLocation(gl.program, 'time'); | |
gl.uniform1f(timeAttribute, new Date().getTime() % 10000); | |
const ratio = this.canvas.width / this.canvas.height; | |
const ratioAttribute = gl.getUniformLocation(gl.program, 'ratio'); | |
gl.uniform1f(ratioAttribute, ratio); | |
for(let name in this.custom_float_uniforms){ | |
let value = this.custom_float_uniforms[name]; | |
const att = gl.getUniformLocation(gl.program, name); | |
gl.uniform1f(att, value); | |
} | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
gl.viewport(0, 0, this.canvas.width, this.canvas.height); | |
} | |
} | |
let canvas = document.querySelectorAll("canvas.texplorer")[0]; | |
let surface = new ShaderSurface(canvas); | |
let info = document.querySelectorAll(".info")[0]; | |
let input = document.querySelectorAll(".formula")[0]; | |
let offset_i = 0; | |
let offset_j = 0; | |
let scale = 0.1; | |
surface.custom_float_uniforms.offset_i = 0; | |
surface.custom_float_uniforms.offset_j = 0; | |
surface.custom_float_uniforms.scale = scale; | |
new_formula(input.value); | |
function new_formula(f){ | |
surface.create_shaders(f) | |
} | |
function set_formula(f){ | |
input.value = f; | |
new_formula(f); | |
offset_i = offset_j = 0; | |
surface.custom_float_uniforms.offset_i = offset_i; | |
surface.custom_float_uniforms.offset_j = offset_j; | |
update_info(); | |
} | |
function go_to(_i, _j, _scale){ | |
offset_i = _i; | |
offset_j = _j; | |
scale = _scale; | |
surface.custom_float_uniforms.scale = scale; | |
surface.custom_float_uniforms.offset_i = offset_i; | |
surface.custom_float_uniforms.offset_j = offset_j; | |
this.update_info(); | |
} | |
// Null if not moving | |
let original_e = null; | |
let original_offset_i = 0; | |
let original_offset_j = 0; | |
function mousedown(e){ | |
original_e = e; | |
original_offset_i = get_mouse_i(e); | |
original_offset_j = get_mouse_j(e); | |
} | |
let delta_i = 0; | |
let delta_j = 0; | |
function update_info(){ | |
info.innerHTML = `x : ${parseInt(offset_i)}<br/> | |
y : ${parseInt(offset_j)}<br/> | |
scale : ${parseInt(scale)}`; | |
} | |
update_info(); | |
function get_mouse_i(e, _scale, _offset_i){ | |
_scale = _scale || scale; | |
_offset_i = _offset_i || offset_i; | |
return (e.clientX - canvas.width/2) / canvas.height * (10.0 * Math.pow(2.0, _scale)) | |
+ _offset_i; | |
} | |
function get_mouse_j(e, _scale, _offset_j){ | |
_scale = _scale || scale; | |
_offset_j = _offset_j || offset_j; | |
return (canvas.height/2 - e.clientY)/canvas.height * (10.0 * Math.pow(2.0, _scale)) | |
+ _offset_j; | |
} | |
function mousemove(e){ | |
// Debug cursor position | |
//surface.custom_float_uniforms.cursor_i = get_mouse_i(e); | |
//surface.custom_float_uniforms.cursor_j = get_mouse_j(e); | |
if(original_e == null){ | |
// Not dragging | |
return; | |
} | |
current_i = get_mouse_i(e); | |
current_j = get_mouse_j(e); | |
delta_i = current_i - original_offset_i; | |
delta_j = current_j - original_offset_j; | |
offset_i -= delta_i; | |
offset_j -= delta_j; | |
surface.custom_float_uniforms.offset_i = offset_i; | |
surface.custom_float_uniforms.offset_j = offset_j; | |
update_info(); | |
} | |
function mouseup(e){ | |
original_e = null; | |
update_info(); | |
} | |
function wheel(e){ | |
mouse_i = get_mouse_i(e); | |
mouse_j = get_mouse_j(e); | |
scale += Math.sign(e.deltaY) * 0.7; | |
// Keep current value for animation | |
let anim_offset_i = mouse_i; | |
let anim_offset_j = mouse_j; | |
// This is the target offset after the animation | |
offset_i += mouse_i - get_mouse_i(e); | |
offset_j += mouse_j - get_mouse_j(e); | |
surface.custom_float_uniforms.offset_i = offset_i; | |
surface.custom_float_uniforms.offset_j = offset_j; | |
surface.custom_float_uniforms.scale = scale; | |
update_info(); | |
} | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment