<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
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.