<div id="ui-container">
<canvas id="canvas" width="300" height="200" style="width: 300px; height: 200px;"></canvas>
<p id="fragment-shader-formula">
  <div id="ui">
    <div id="u_linewidth"></div>
    <div id="u_color_coef"></div>
    <div id="u_smoothstep_coef_1"></div>
    <div id="u_smoothstep_coef_2"></div>
    <div id="u_resolution_coef"></div>
    <div id="u_y_coef"></div>
    <div id="u_triangles"></div>
    <div id="u_count"></div>
  </div>
</div>
 
  </p>
<!-- vertex shader -->
<script id="vertex-shader" type="x-shader/x-vertex">
  attribute vec2 a_position;

  void main() {
    // Multiply the position by the matrix.
    // gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
    gl_Position = vec4(a_position.xy, 0.0, 1.0);
  }
</script>
<!-- fragment shader -->
<script id="fragment-shader" type="x-shader/x-fragment">
  #ifdef GL_ES
  precision mediump float;
  #endif

  uniform vec2 u_resolution;
  uniform float u_linewidth;
  uniform float u_color_coef;
  uniform float u_smoothstep_coef_1;
  uniform float u_smoothstep_coef_2;
  uniform float u_resolution_coef;
  uniform float u_y_coef;
  uniform float u_count;

  // Plot a line on Y using a value between 0.0-1.0
  float plot(vec2 st, float pct) {
    return u_smoothstep_coef_1 * smoothstep( pct-u_linewidth, pct, st.y)  - 
      u_smoothstep_coef_2 * smoothstep( pct, pct+u_linewidth, st.y);
  }

  void main() {
    vec2 st = gl_FragCoord.xy/ ( u_resolution_coef * u_resolution);
    float y = u_y_coef * st.x;
    vec3 color = vec3(y);

    // Plot a line
    float pct = plot(st, y);
    color = (u_color_coef-pct)*color+pct*vec3(0.0,1.0,0.0);

    gl_FragColor = vec4(color, 1.0);
  }
</script>
#ui-container {
  display: grid;
  width: 600px;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  grid-template-areas: 
    "canvas code"
    "sliders code"
    "sliders code"
}

#canvas {
  grid-area: canvas;
}

#ui {
  width: 200px;
  grid-area: sliders;
}

#fragment-shader-formula {
  grid-area: code;
}

.gman-widget-label {
  font-family: "Courier New";
}

.gman-widget-outer {
  border: 1px lightgrey solid;
}
"use strict";

let shaderCodeDisplay = document.getElementById("fragment-shader-formula");

const setShaderCodeDisplay = (uniform, new_value) => {
  
  // Initial values for uniforms
  let uniforms = {
    u_linewidth: 0.02,
    u_color_coef: 1.0,
    u_smoothstep_coef_1: 1.0,
    u_smoothstep_coef_2: 1.0,
    u_resolution_coef: 1.0,
    u_y_coef: 1.0    
  }
  
  // Set the new value
  if (uniform && new_value) {
    uniforms[uniform] = new_value
  };
  
  let preformattedText = `<pre>
    #ifdef GL_ES
    precision mediump float;
    #endif

    uniform vec2 u_resolution;
    uniform float u_linewidth;
    uniform float u_color_coef;
    uniform float u_smoothstep_coef_1;
    uniform float u_smoothstep_coef_2;
    uniform float u_resolution_coef;
    uniform float u_y_coef;
    uniform float u_count;

    // Plot a line on Y using a value between 0.0-1.0
    float plot(vec2 st, float pct) {
      return ${uniforms.u_smoothstep_coef_1} * smoothstep( pct-${uniforms.u_linewidth}, pct, st.y)  - 
        ${uniforms.u_smoothstep_coef_2} * smoothstep( pct, pct+${uniforms.u_linewidth}, st.y);
    }

    void main() {
      vec2 st = gl_FragCoord.xy/ ( ${uniforms.u_resolution_coef} * u_resolution);
      float y = ${uniforms.u_y_coef} * st.x;
      vec3 color = vec3(y);

      // Plot a line
      float pct = plot(st, y);
      color = (${uniforms.u_color_coef}-pct)*color+pct*vec3(0.0,1.0,0.0);

      gl_FragColor = vec4(color, 1.0);
    }
    </pre>`
  
  shaderCodeDisplay.innerHTML = preformattedText;
}

setShaderCodeDisplay();

//

/*
 * Copyright 2016, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of his
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


(function(root, factory) {  // eslint-disable-line
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define([], function() {
      return factory.call(root);
    });
  } else {
    // Browser globals
    root.webglLessonsUI = factory.call(root);
  }
}(this, function() {

  const gopt = getQueryParams();

  function setupSlider(selector, options) {
    var parent = document.querySelector(selector);
    if (!parent) {
      return; // like jquery don't fail on a bad selector
    }
    if (!options.name) {
      options.name = selector.substring(1);
    }
    return createSlider(parent, options);
  }

  function createSlider(parent, options) {
    var precision = options.precision || 0;
    var min = options.min || 0;
    var step = options.step || 1;
    var value = options.value || 0;
    var max = options.max || 1;
    var fn = options.slide;
    var name = gopt["ui-" + options.name] || options.name;
    var uiPrecision = options.uiPrecision === undefined ? precision : options.uiPrecision;
    var uiMult = options.uiMult || 1;

    min /= step;
    max /= step;
    value /= step;

    parent.innerHTML = `
      <div class="gman-widget-outer">
        <div class="gman-widget-label">${name}</div>
        <div class="gman-widget-value"></div>
        <input class="gman-widget-slider" type="range" min="${min}" max="${max}" value="${value}" />
      </div>
    `;
    var valueElem = parent.querySelector(".gman-widget-value");
    var sliderElem = parent.querySelector(".gman-widget-slider");

    function updateValue(value) {
      valueElem.textContent = (value * step * uiMult).toFixed(uiPrecision);
    }

    updateValue(value);

    function handleChange(event) {
      var value = parseInt(event.target.value);
      updateValue(value);
      fn(event, { value: value * step });
    }

    sliderElem.addEventListener('input', handleChange);
    sliderElem.addEventListener('change', handleChange);

    return {
      elem: parent,
      updateValue: (v) => {
        v /= step;
        sliderElem.value = v;
        updateValue(v);
      },
    };
  }

  function makeSlider(options) {
    const div = document.createElement("div");
    return createSlider(div, options);
  }

  var widgetId = 0;
  function getWidgetId() {
    return "__widget_" + widgetId++;
  }

  function makeCheckbox(options) {
    const div = document.createElement("div");
    div.className = "gman-widget-outer";
    const label = document.createElement("label");
    const id = getWidgetId();
    label.setAttribute('for', id);
    label.textContent = gopt["ui-" + options.name] || options.name;
    label.className = "gman-checkbox-label";
    const input = document.createElement("input");
    input.type = "checkbox";
    input.checked = options.value;
    input.id = id;
    input.className = "gman-widget-checkbox";
    div.appendChild(label);
    div.appendChild(input);
    input.addEventListener('change', function(e) {
       options.change(e, {
         value: e.target.checked,
       });
    });

    return {
      elem: div,
      updateValue: function(v) {
        input.checked = !!v;
      },
    };
  }

  function makeOption(options) {
    const div = document.createElement("div");
    div.className = "gman-widget-outer";
    const label = document.createElement("label");
    const id = getWidgetId();
    label.setAttribute('for', id);
    label.textContent = gopt["ui-" + options.name] || options.name;
    label.className = "gman-widget-label";
    const selectElem = document.createElement("select");
    options.options.forEach((name, ndx) => {
      const opt = document.createElement("option");
      opt.textContent = gopt["ui-" + name] || name;
      opt.value = ndx;
      opt.selected = ndx === options.value
      selectElem.appendChild(opt);
    });
    selectElem.className = "gman-widget-select";
    div.appendChild(label);
    div.appendChild(selectElem);
    selectElem.addEventListener('change', function(e) {
       options.change(e, {
         value: selectElem.selectedIndex,
       });
    });

    return {
      elem: div,
      updateValue: function(v) {
        selectedElem.selectedIndex = v;
      },
    };
  }

  function noop() {
  }

  function genSlider(object, ui) {
    const changeFn = ui.change || noop;
    ui.name = ui.name || ui.key;
    ui.value = object[ui.key];
    ui.slide = ui.slide || function(event, uiInfo) {
      object[ui.key] = uiInfo.value;
      changeFn();
    };
    return makeSlider(ui);
  }

  function genCheckbox(object, ui) {
    const changeFn = ui.change || noop;
    ui.value = object[ui.key];
    ui.name = ui.name || ui.key;
    ui.change = function(event, uiInfo) {
      object[ui.key] = uiInfo.value;
      changeFn();
    };
    return makeCheckbox(ui);
  }

  function genOption(object, ui) {
    const changeFn = ui.change || noop;
    ui.value = object[ui.key];
    ui.name = ui.name || ui.key;
    ui.change = function(event, uiInfo) {
      object[ui.key] = uiInfo.value;
      changeFn();
    };
    return makeOption(ui);
  }

  const uiFuncs = {
    slider: genSlider,
    checkbox: genCheckbox,
    option: genOption,
  };

  function setupUI(parent, object, uiInfos) {
    const widgets = {};
    uiInfos.forEach(function(ui) {
      const widget = uiFuncs[ui.type](object, ui);
      parent.appendChild(widget.elem);
      widgets[ui.key] = widget;
    });
    return widgets;
  }

  function updateUI(widgets, data) {
    Object.keys(widgets).forEach(key => {
      const widget = widgets[key];
      widget.updateValue(data[key]);
    });
  }

  function getQueryParams() {
    var params = {};
    if (window.hackedParams) {
      Object.keys(window.hackedParams).forEach(function(key) {
        params[key] = window.hackedParams[key];
      });
    }
    if (window.location.search) {
      window.location.search.substring(1).split("&").forEach(function(pair) {
        var keyValue = pair.split("=").map(function (kv) {
          return decodeURIComponent(kv);
        });
        params[keyValue[0]] = keyValue[1];
      });
    }
    return params;
  }

  return {
    setupUI: setupUI,
    updateUI: updateUI,
    setupSlider: setupSlider,
    makeSlider: makeSlider,
    makeCheckbox: makeCheckbox,
  };

}));

/** 
  * Creates and compiles a shader.
  * @param {!WebGLRenderingContext} gl The WebGL Context.
  * @param {string} shaderSource The GLSL source code for the shader.
  * @param {number} shaderType The type of shader, VERTEX_SHADER or 
  *     FRAGMENT_SHADER.
  * @return {!WebGLShader} The shader.
*/
const compileShader = (gl, shaderSource, shaderType) => {
  // Create the shader object
  let shader = gl.createShader(shaderType);

  // Set the shader source code
  gl.shaderSource(shader, shaderSource);

  // Compile the shader
  gl.compileShader(shader);

  // Check if it compiled
  let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!success) {
    // Something went wrong during compilation; get the error
    throw "could not compile shader:" + gl.getShaderInfoLog(shader);
  }

  return shader;
}

/** 
         * Creates a program from 2 shaders.
         * 
         * @param {!WebGLRenderingContext} gl The WebGL context.
         * @param {!WebGLShader} vertexShader A vertex shader.
         * @param {!WebGLShader} fragmentShader A fragment shader.
         * @param {!WebGLProgram} A program.
         */
const createProgram = (gl, vertexShader, fragmentShader) => {
  // create a program.
  let program = gl.createProgram();

  // attach the shaders.
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  // link the program.
  gl.linkProgram(program);

  // Check if it linked.
  let success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!success) {
    // something went wrong with the link
    throw ("program failed to link:" + gl.getProgramInfoLog(program));
  }

  return program;
};

/** 
         * Creates a shader from the content of a script tag
         * 
         * @param {!WebGLRenderingContext} gl The WebGL Context.
         * @param {string} scriptId The id of the script tag.
         * @param {string} opt_shaderType. The type of shader to create.
         *     If not passed in will use the type attribute from the
         *     script tag.
         * @return {!WebGLShader} A shader. 
         */
const createShaderFromScript = (gl, scriptId, opt_shaderType)=> {
  // look up the script tag by id.
  let shaderScript = document.getElementById(scriptId);
  if (!shaderScript) {
    throw("*** Error: unknown script element" + scriptId);
  }

  // extract the contents of the script tag.
  let shaderSource = shaderScript.text;

  // If we didn't pass in a type, use the 'type' from
  // the script tag.
  if (!opt_shaderType) {
    if (shaderScript.type == "x-shader/x-vertex") {
      opt_shaderType = gl.VERTEX_SHADER;
    } else if (shaderScript.type == "x-shader/x-fragment") {
      opt_shaderType = gl.FRAGMENT_SHADER;
    } else if (!opt_shaderType) {
      throw("*** Error: shader type not set");
    }
  }
  return compileShader(gl, shaderSource, opt_shaderType);
}

/**
         * Creates a program from 2 script tags.
         * 
         * @param {!WebGLRenderingContext} gl The WebGL Context.
         * @param {string[]} shaderScriptIds Array of ids of the script
         *        tags for the shaders. The first is assumed to be the 
         *        vertex shader, the second the fragment shader.
         * @return {!WebGLProgram} A program
         */
const createProgramFromScripts = (gl, shaderScriptIds) => {
  let vertexShader = createShaderFromScript(gl, shaderScriptIds[0], gl.VERTEX_SHADER);
  let fragmentShader = createShaderFromScript(gl, shaderScriptIds[1], gl.FRAGMENT_SHADER);
  return createProgram(gl, vertexShader, fragmentShader);

}

const main = () => {
  // Get a WebGL context
  /** @type {HTMLCanvasElement} */
  let canvas = document.getElementById("canvas");
  let gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }

  // Shader Script IDs
  let shaderScriptIds = ["vertex-shader", "fragment-shader"];

  // Setup GLSL program
  let program = createProgramFromScripts(gl, shaderScriptIds);
  gl.useProgram(program);

  // Look up where the vertex data needs to go.
  let positionAttributeLocation = gl.getAttribLocation(program, "a_position");

  // Look up uniforms.
  let colorLocation = gl.getUniformLocation(program, "u_color");
  let matrixLocation = gl.getUniformLocation(program, "u_matrix");
  let resolutionLocation = gl.getUniformLocation(program, "u_resolution");
  let linewidthLocation = gl.getUniformLocation(program, "u_linewidth");
  let colorCoefLocation = gl.getUniformLocation(program, "u_color_coef")
  let smoothstepCoef1Location = gl.getUniformLocation(program, "u_smoothstep_coef_1");
  let smoothstepCoef2Location = gl.getUniformLocation(program, "u_smoothstep_coef_2");
  let resolutionCoefficient = gl.getUniformLocation(program, "u_resolution_coef");
  let yCoefficient = gl.getUniformLocation(program, "u_y_coef");
  let countLocation = gl.getUniformLocation(program, "u_count");
  gl.uniform1f(countLocation, 3.0);
  gl.uniform1f(yCoefficient, 1.0);
  gl.uniform1f(resolutionCoefficient, 1.0);
  gl.uniform1f(linewidthLocation, 0.02)
  gl.uniform1f(colorCoefLocation, 1.0)
  gl.uniform1f(smoothstepCoef1Location, 1.0);
  gl.uniform1f(smoothstepCoef2Location, 1.0);

  // Create a buffer to put the points
  let positionBuffer = gl.createBuffer();

  // Bind it to ARRAY_BUFFER
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Set the count and draw the scene
  let count = 24;
  drawScene(count);


  // Setup a UI
  webglLessonsUI.setupSlider("#u_linewidth", { value: 0.02, slide: updateLinewidth(0.02), max: 1.0, step: 0.01 })
  function updateLinewidth(value) {
    return function(event, ui) {
      gl.uniform1f(linewidthLocation, ui.value);
      // requestAnimationFrame(drawScene);
      drawScene(count);
      setShaderCodeDisplay("u_linewidth", ui.value);
    }
  };

  webglLessonsUI.setupSlider("#u_color_coef", { value: 1.0, slide: updateColorCoef(1.0), max: 1.0, min: 0.0, step: 0.01})
  function updateColorCoef(value) {
    return function(event, ui) {
      gl.uniform1f(colorCoefLocation, ui.value);
      setShaderCodeDisplay("u_color_coef", ui.value);
      drawScene(count);
    }
  };

  webglLessonsUI.setupSlider("#u_smoothstep_coef_1", { value: 1.0, slide: updateSmoothstep1(1.0), max: 1.0, min: 0.0, step: 0.01})
  function updateSmoothstep1(value) {
    return function(event, ui) {
      gl.uniform1f(smoothstepCoef1Location, ui.value);
      setShaderCodeDisplay("u_smoothstep_coef_1", ui.value);
      drawScene(count);
    }
  }
  webglLessonsUI.setupSlider("#u_smoothstep_coef_2", { value: 1.0, slide: updateSmoothstep2(1.0), max: 1.0, min: 0.0, step: 0.01})
  function updateSmoothstep2(value) {
    return function(event, ui) {
      gl.uniform1f(smoothstepCoef2Location, ui.value);
      setShaderCodeDisplay("u_smoothstep_coef_2", ui.value);
      drawScene(count);
    }
  };
  webglLessonsUI.setupSlider("#u_resolution_coef", { value: 1.0, slide: updateResolutionCoef(1.0), min: 0.0, max: 10.0, step: 0.01})
  function updateResolutionCoef(value) {
    return function(event, ui) {
      gl.uniform1f(resolutionCoefficient, ui.value);
      setShaderCodeDisplay("u_resolution_coef", ui.value);
      drawScene(count);
    }
  };
  webglLessonsUI.setupSlider("#u_count", { value: 24.0, slide: updateCount(3.0), min: 3.0, step: 3.0, max: 24.0})
  function updateCount(value) {
    return function(event, ui) {
      count = ui.value;
      // requestAnimationFrame(drawScene);
      drawScene(count);
    }
  };

  // Draw the scene.
  function drawScene(count) {

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0,0,gl.canvas.width, gl.canvas.height);

    // Clear the canvas
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

    // Turn on the attribute
    gl.enableVertexAttribArray(positionAttributeLocation);

    // Bind the position buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
    let size = 2;
    let type = gl.FLOAT;
    let normalize = false;
    let stride = 0;
    let offset = 0;
    gl.vertexAttribPointer(
      positionAttributeLocation, size, type, normalize, stride, offset);

    // Set geometry
    let test_points = [
      [-1, 1],
      [0, 1],
      [1, 1],
      [-1, 0],
      [0, 0],
      [1, 0],
      [-1, -1],
      [0, -1],
      [1, -1]
    ];

    let triangle_vertices = getTriangleVertices(test_points, 8).flat();

    // Add draw array here
    let buffer_float_array = Float32Array.from(triangle_vertices)
    gl.bufferData(
      gl.ARRAY_BUFFER,
      buffer_float_array,
      gl.STATIC_DRAW);

    // Set resolution
    gl.uniform2fv(resolutionLocation, [gl.canvas.width, gl.canvas.height]);

    // Draw the geometry.
    let primitiveType = gl.TRIANGLES;
    let geometryOffset = 0;

    // Limit the count based on the number of vertices 
    count = count > triangle_vertices / 2.0 ? triangle_vertices / 2.0 : count;
    gl.drawArrays(primitiveType, geometryOffset, count);

  }
}


const getTriangleVertices = (points, triangle_count) => {
  let edge_vertex_count = Math.sqrt(triangle_count / 2.0) + 1.0;
  let triangle_idx = 0;
  let triangle_vertices = [];
  let loop_length = edge_vertex_count * (edge_vertex_count - 1.0);
  for (let i = 0; i < loop_length; i++) {
    triangle_idx = i % edge_vertex_count;
    if (triangle_idx == 0) {
      // Top left triangle
      triangle_vertices.push(points[i]);
      triangle_vertices.push(points[i + 1]);
      triangle_vertices.push(points[i + edge_vertex_count]);
    } else if (triangle_idx == edge_vertex_count - 1) {
      // Bottom right triangle
      triangle_vertices.push(points[i]);
      triangle_vertices.push(points[i + edge_vertex_count]);
      triangle_vertices.push(points[i + edge_vertex_count - 1]);
    } else {
      // Top left triangle 
      triangle_vertices.push(points[i]);
      triangle_vertices.push(points[i + 1]);
      triangle_vertices.push(points[i + edge_vertex_count]);

      // Bottom right triangle
      triangle_vertices.push(points[i]);
      triangle_vertices.push(points[i + edge_vertex_count]);
      triangle_vertices.push(points[i + edge_vertex_count - 1]);
    }
  }
  return triangle_vertices;
}

main();


View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.