Skip to content

Instantly share code, notes, and snippets.

@antoineMoPa
Created December 7, 2020 00:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antoineMoPa/bc26eb6b0c422cb0875a8a6111b16531 to your computer and use it in GitHub Desktop.
Save antoineMoPa/bc26eb6b0c422cb0875a8a6111b16531 to your computer and use it in GitHub Desktop.
texture based on Mandelbrot (Procedural starry sky?)
<!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