Related
As shown below, all I can think of is to describe all the points on a circle and then use triangulation to draw a ring with width
I can also think of a way to use overlay. First draw a circle and then draw a circle with a smaller radius
const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
export function arc(x0: number, y0: number, radius: number, startAng = 0, endAng = Math.PI * 2) {
const ang = Math.min(TAU, endAng - startAng);
const ret = ang === TAU ? [] : [x0, y0];
const segments = Math.round(TAU_SEGMENTS * ang / TAU);
for(let i = 0; i <= segments; i++) {
const x = x0 + radius * Math.cos(startAng + ang * i / segments);
const y = y0 + radius * Math.sin(startAng + ang * i / segments);
ret.push(x, y);
}
return ret;
}
const gl = container.current.getContext("webgl2");
const program = initProgram(gl);
const position = new Float32Array(arc(0, 0, 1).concat(...arc(0, 0, 0.9)));
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);
gl.drawArrays(gl.LINE_STRIP, 0, position.length / 2);
The final effect of the code I wrote is as follows. May I ask how I should modify it to become the same as the above picture
You have to add a color attributes for the vertices and you have to draw a gl.TRIANGLE_STRIP primitive instead of a gl.LINE_STRIP primitive.
The color can be calculated from the angle. Map the angle from the range [0, PI] to the range [0, 1] and use the formula for the hue value from the HSL and HSV color space:
function HUEtoRGB(hue) {
return [
Math.min(1, Math.max(0, Math.abs(hue * 6.0 - 3.0) - 1.0)),
Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 2.0))),
Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 4.0)))
];
}
Create vertices in pairs for the inner and outer arcs with the corresponding color attribute:
const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
function arc(x0, y0, innerRadius, outerRadius, startAng = 0, endAng = Math.PI * 2) {
const ang = Math.min(TAU, endAng - startAng);
const position = ang === TAU ? [] : [x0, y0];
const color = []
const segments = Math.round(TAU_SEGMENTS * ang / TAU);
for(let i = 0; i <= segments; i++) {
const angle = startAng + ang * i / segments;
const x1 = x0 + innerRadius * Math.cos(angle);
const y1 = y0 + innerRadius * Math.sin(angle);
const x2 = x0 + outerRadius * Math.cos(angle);
const y2 = y0 + outerRadius * Math.sin(angle);
position.push(x1, y1, x2, y2);
let hue = (Math.PI/2 - angle) / (2 * Math.PI);
if (hue < 0) hue += 1;
const rgb = HUEtoRGB(hue);
color.push(...rgb);
color.push(...rgb);
}
return { 'position': position, 'color': color };
}
Create a shader with a color attribute and pass the color attribute from the vertex to the fragment shader:
#version 300 es
precision highp float;
in vec2 position;
in vec4 color;
out vec4 vColor;
void main()
{
vColor = color;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
#version 300 es
precision highp float;
in vec4 vColor;
out vec4 fragColor;
void main()
{
fragColor = vColor;
}
Vertex specification:
const attributes = arc(0, 0, 0.6, 0.9);
position = new Float32Array(attributes.position);
color = new Float32Array(attributes.color);
let position_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);
let color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW);
let gl_color = gl.getAttribLocation(program, "color");
gl.vertexAttribPointer(gl_color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_color);
Complet and runnable example:
const canvas = document.getElementById( "ogl-canvas");
const gl = canvas.getContext("webgl2");
const program = gl.createProgram();
for (let i = 0; i < 2; ++i) {
let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
gl.attachShader(program, shaderObj);
gl.linkProgram(program);
}
status = gl.getProgramParameter(program, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(program));
gl.useProgram(program);
function HUEtoRGB(hue) {
return [
Math.min(1, Math.max(0, Math.abs(hue * 6.0 - 3.0) - 1.0)),
Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 2.0))),
Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 4.0)))
];
}
const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
function arc(x0, y0, innerRadius, outerRadius, startAng = 0, endAng = Math.PI * 2) {
const ang = Math.min(TAU, endAng - startAng);
const position = ang === TAU ? [] : [x0, y0];
const color = []
const segments = Math.round(TAU_SEGMENTS * ang / TAU);
for(let i = 0; i <= segments; i++) {
const angle = startAng + ang * i / segments;
const x1 = x0 + innerRadius * Math.cos(angle);
const y1 = y0 + innerRadius * Math.sin(angle);
const x2 = x0 + outerRadius * Math.cos(angle);
const y2 = y0 + outerRadius * Math.sin(angle);
position.push(x1, y1, x2, y2);
let hue = (Math.PI/2 - angle) / (2 * Math.PI);
if (hue < 0) hue += 1;
const rgb = HUEtoRGB(hue);
color.push(...rgb);
color.push(...rgb);
}
return { 'position': position, 'color': color };
}
const attributes = arc(0, 0, 0.6, 0.9);
position = new Float32Array(attributes.position);
color = new Float32Array(attributes.color);
let position_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);
let color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW);
let gl_color = gl.getAttribLocation(program, "color");
gl.vertexAttribPointer(gl_color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_color);
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight];
vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, attributes.position.length / 2);
<script id="draw-shader-vs" type="x-shader/x-vertex">#version 300 es
precision highp float;
in vec2 position;
in vec4 color;
out vec4 vColor;
void main()
{
vColor = color;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">#version 300 es
precision highp float;
in vec4 vColor;
out vec4 fragColor;
void main()
{
fragColor = vColor;
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>
I have been trying to create shading in webgl just like in this image: where we can see a cone, a sphere, and a light (which we can change his position with the sliders).
I've tried to write some code in the html file by seeing multiple examples of shading from some webgl tutoring sites, but right now, I can't even see the shapes. It's sure that I'm doing something wrong, but I just don't know where. Here's my code and I also included a link because it contains multiple files. Thanks in advance.
Link: https://wetransfer.com/downloads/cd0f66f2e2866c0d118e95b02e01cb0520200923203442/274553
<html>
<head>
<title>Light and Shading</title>
<meta http-equiv='content-type' content='text/html; charset=ISO-8859-1'>
<!-- CSS Styles //-->
<link href='css/style.css' type='text/css' rel='stylesheet'>
<link href='css/desert.css' type='text/css' rel='stylesheet'/>
<link href='css/colorpicker.css' type='text/css' rel='stylesheet'/>
<link href='css/smoothness/jquery-ui-1.8.13.custom.css' type='text/css' rel='stylesheet' />
<!-- JavaScript Libraries //-->
<script type='text/javascript' src='js/gl-matrix-min.js'></script>
<script type='text/javascript' src='js/jquery-1.5.1.min.js'></script>
<script type='text/javascript' src='js/jquery-ui-1.8.13.custom.min.js'></script>
<script type='text/javascript' src='js/prettify.js'></script>
<script type='text/javascript' src='js/utils.js'></script>
<script type='text/javascript' src='js/colorpicker.js'></script>
<script type='text/javascript' src='js/codeview.js'></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
// matrice model-view combinee.
uniform mat4 uMVMatrix;
// matrice de projection
uniform mat4 uPMatrix;
// matrice des normales.
uniform mat3 uNMatrix;
// position de la lumiere.
uniform vec3 uLightPosition;
// La normale transformee
varying vec3 vNormal;
// la direction vertex-lumiere
varying vec3 vLightRay;
// la direction camera-vertex
varying vec3 vEyeVec;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse;
uniform vec4 uLightSpecular;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialSpecular;
void main(void) {
vec4 ambientProduct= uLightAmbient* uMaterialAmbient;
vec4 diffuseProduct= uLightDiffuse*uMaterialDiffuse;
vec4 specularProduct= uLightSpecular*uMaterialSpecular;
vec3 pos = (uMVMatrix*vec4(aVertexPosition, 1.0)).xyz;
// position de l'oeil/camera.
const vec3 eyePosition = vec3(0,0,-40);
//Transformed normal position
vNormal = normalize((uNMatrix* aVertexNormal).xyz) ;
//Transformed light position
vec4 light = uMVMatrix * vec4(uLightPosition,1.0);
vec3 lightPos = (uMVMatrix * light).xyz;
//Light position
vLightRay = normalize(pos - lightPos);
//Vector Eye
vEyeVec = -normalize(pos);
//Final vertex position
gl_Position = uMVMatrix*uPMatrix* vec4(aVertexPosition, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
varying vec3 vNormal;
varying vec3 vLightRay;
varying vec3 vEyeVec;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float uShininess;
void main(void)
{
vec4 diffuse = max(dot( vNormal,vLightRay), 0.0) * diffuseProduct;
vec3 H = normalize(vLightRay+vEyeVec);
vec4 specular =
pow(max(dot(vNormal, H), 0.0), uShininess) * specularProduct;
if (dot(vLightRay, vNormal) < 0.0)
specular = vec4(0.0, 0.0, 0.0, 1.0);
vec4 fColor = ambientProduct + diffuse + specular;
fColor.a = 1.0;
gl_FragColor =fColor;
}
</script>
<script id='code-js' type="text/javascript">
var gl = null; // WebGL context
var prg = null; // The program (shaders)
var c_width = 0; // Variable to store the width of the canvas
var c_height = 0; // Variable to store the height of the canvas
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
var nMatrix = mat4.create(); // The normal matrix
var distance = -40;
var animateFlag = false;
var objects = [];
/**
* The program contains a series of instructions that tell the Graphic Processing Unit (GPU)
* what to do with every vertex and fragment that we pass it.
* The vertex shader and the fragment shader together are called the program.
*/
function initProgram() {
var fragmentShader = utils.getShader(gl, "shader-fs");
var vertexShader = utils.getShader(gl, "shader-vs");
prg = gl.createProgram();
gl.attachShader(prg, vertexShader);
gl.attachShader(prg, fragmentShader);
gl.linkProgram(prg);
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(prg);
prg.aVertexPosition = gl.getAttribLocation(prg, "aVertexPosition");
prg.aVertexNormal = gl.getAttribLocation(prg, "aVertexNormal");
prg.uPMatrix = gl.getUniformLocation(prg, "uPMatrix");
prg.uMVMatrix = gl.getUniformLocation(prg, "uMVMatrix");
prg.uNMatrix = gl.getUniformLocation(prg, "uNMatrix");
prg.uMaterialAmbient = gl.getUniformLocation(prg, "uMaterialAmbient");
prg.uMaterialDiffuse = gl.getUniformLocation(prg, "uMaterialDiffuse");
prg.uMaterialSpecular = gl.getUniformLocation(prg, "uMaterialSpecular");
prg.uShininess = gl.getUniformLocation(prg, "uShininess");
prg.uLightPosition = gl.getUniformLocation(prg, "uLightPosition");
prg.uLightAmbient = gl.getUniformLocation(prg, "uLightAmbient");
prg.uLightDiffuse = gl.getUniformLocation(prg, "uLightDiffuse");
prg.uLightSpecular = gl.getUniformLocation(prg, "uLightSpecular");
}
function initLights(){
//Light uniforms
gl.uniform3fv(prg.uLightPosition,[4.5,3.0,15.0]);
gl.uniform4f(prg.uLightAmbient ,1.0,1.0,1.0,1.0);
gl.uniform4f(prg.uLightDiffuse,1.0,1.0,1.0,1.0);
gl.uniform4f(prg.uLightSpecular,1.0,1.0,1.0,1.0);
//Object Uniforms
gl.uniform4f(prg.uMaterialAmbient, 0.1,0.1,0.1,1.0);
gl.uniform4f(prg.uMaterialDiffuse, 0.5,0.8,0.1,1.0);
gl.uniform4f(prg.uMaterialSpecular, 0.6,0.6,0.6,1.0);
gl.uniform1f(prg.uShininess, 200.0);
}
/**
* Creates an AJAX request to load the scene asynchronously
*/
function loadScene(){
loadObject('models/plane.json');
loadObject('models/cone.json','cone');
loadObject('models/sphere.json','sphere');
loadObject('models/smallsph.json','lightsource');
}
function getObject(alias){
for(var i=0; i<objects.length; i++){
if (alias == objects[i].alias) return objects[i];
}
return null;
}
/**
* Ajax and JSON in action
*/
function loadObject(filename,alias){
var request = new XMLHttpRequest();
console.info('Requesting ' + filename);
request.open("GET",filename);
request.onreadystatechange = function() {
if (request.readyState == 4) {
if(request.status == 404) {
console.info(filename + ' does not exist');
}
else {
var o = JSON.parse(request.responseText);
o.alias = (alias==null)?'none':alias;
handleLoadedObject(filename,o);
}
}
}
request.send();
}
/**
* Creates the buffers that contain the geometry of the object
*/
function handleLoadedObject(filename,object) {
console.info(filename + ' has been retrieved from the server');
var vertexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object.vertices), gl.STATIC_DRAW);
var normalBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(calcNormals(object.vertices, object.indices)), gl.STATIC_DRAW);
var indexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object.indices), gl.STATIC_DRAW);
object.vbo = vertexBufferObject;
object.ibo = indexBufferObject;
object.nbo = normalBufferObject;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER,null);
objects.push(object);
}
/**
* Main rendering function. Called every 500ms according to WebGLStart function (see below)
*/
function drawScene() {
gl.clearColor(0.3,0.3,0.3, 1.0);
gl.clearDepth(100.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.viewport(0, 0, c_width, c_height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(60, c_width / c_height, 0.1, 1000.0, pMatrix);
try{
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.enableVertexAttribArray(prg.aVertexNormal);
for (var i = 0; i < objects.length; i++){
var object = objects[i];
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0.0, 0.0, distance]); //Sets the camera to a reasonable distance to view the part
mat4.rotate(mvMatrix, 30*Math.PI/180, [1,0,0]);
mat4.rotate(mvMatrix, angle*Math.PI/180, [0,1,0]);
if (object.alias == 'lightsource'){
var lightPos = gl.getUniform(prg, prg.uLightPosition);
mat4.translate(mvMatrix,lightPos);
}
gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
gl.uniformMatrix4fv(prg.uPMatrix, false, pMatrix);
mat4.set(mvMatrix, nMatrix);
mat4.inverse(nMatrix);
mat4.transpose(nMatrix);
gl.uniformMatrix4fv(prg.uNMatrix, false, nMatrix);
gl.uniform4fv(prg.uMaterialAmbient, object.ambient);
gl.uniform4fv(prg.uMaterialDiffuse, object.diffuse);
gl.uniform4fv(prg.uMaterialSpecular, object.specular);
gl.bindBuffer(gl.ARRAY_BUFFER, object.vbo);
gl.vertexAttribPointer(prg.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, object.nbo);
gl.vertexAttribPointer(prg.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexNormal);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);
gl.drawElements(gl.TRIANGLES, object.indices.length, gl.UNSIGNED_SHORT,0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
catch(err){
alert(err);
message(err.description);
}
}
var lastTime = 0;
var angle = 0;
/**
* Updates the angle of rotation by a little bit each time
*/
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;
if (animateFlag) angle += (90 * elapsed) / 10000.0;
}
lastTime = timeNow;
}
/**
* Render Loop
*/
function renderLoop() {
requestAnimFrame(renderLoop);
drawScene();
animate();
}
/**
* Entry point. This function is invoked when the page is loaded
*/
function runWebGLApp() {
//Obtains a WebGL context
gl = utils.getGLContext("canvas-element-id");
//Initializes the program (shaders)
initProgram();
//Initializes lights
initLights();
//Load Scene
loadScene();
//Renders the scene!
renderLoop();
}
</script>
</head>
<body onLoad='runWebGLApp()'>
<div id='top'>
<div id='contents'>
<div id='canvasContainer'>
<canvas id='canvas-element-id' width='480' height='400'>
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>
<div id='bottom'>
<table style='padding=0px'>
<tr>
<td>X:</td><td id='slider-x-value' width='30px'>4.5</td><td width='150px'><div id='slider-x'/></td>
</tr>
<tr>
<td>Y:</td><td id='slider-y-value' width='30px'>3.0</td><td width='150px'><div id='slider-y'/></td>
</tr>
<tr>
<td>Z:</td> <td id='slider-z-value' width='30px'>15.0</td><td width='150px'><div id='slider-z'/></td>
</tr>
</table>
</div>
<script>cview.run(cview.MODE_VIEW);</script>
<script>
$('#slider-shininess').slider({value:200, min:1, max:300, step:1, slide:updateShininess});
$('#slider-x').slider({value:4.5, min:-50, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#slider-y').slider({value:3.0, min:0, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#slider-z').slider({value:15.0, min:-50, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#animate-btn').button();
$('#animate-btn').click(
function(){
if ($('#animate-btn:checked').val()==null){
animateFlag = false;
}
else{
animateFlag = true;
}
});
function updateShininess(){
var v = $('#slider-shininess').slider("value");
gl.uniform1f(prg.uShininess, v);
$('#slider-shininess-value').html(v);
}
function updateLightPosition(){
var x = $('#slider-x').slider("value");
var y = $('#slider-y').slider("value");
var z = $('#slider-z').slider("value");
gl.uniform3fv(prg.uLightPosition, [x,y,z]);
$('#slider-x-value').html(x);
$('#slider-y-value').html(y);
$('#slider-z-value').html(z);
}
function updateDistance(){
var d = $('#slider-distance').slider("value");
$('#slider-distance-value').html(distance);
distance = -d;
}
function updateObjectColor(alias, r,g,b){
var object = getObject(alias);
if (object != null){
object.diffuse = [r,g,b,1.0];
}
}
$('#colorSelectorSphere').ColorPicker({
onSubmit: function(hsb, hex, rgb, el) {
$(el).val(hex);
$(el).ColorPickerHide();
},
color: '#00ff00',
onShow: function (colpkr) {
$(colpkr).fadeIn(500);
return false;
},
onHide: function (colpkr) {
$(colpkr).fadeOut(500);
return false;
},
onChange: function (hsb, hex, rgb) {
$('#colorSelectorSphere div').css('backgroundColor', '#' + hex);
updateObjectColor('sphere',rgb.r/256,rgb.g/256,rgb.b/256);
},
onBeforeShow: function (colpkr) {
$(this).ColorPickerSetColor('rgb(0.5,0.8,0.1)');
}
})
$('#colorSelectorCone').ColorPicker({
onSubmit: function(hsb, hex, rgb, el) {
$(el).val(hex);
$(el).ColorPickerHide();
},
color: '#00ff00',
onShow: function (colpkr) {
$(colpkr).fadeIn(500);
return false;
},
onHide: function (colpkr) {
$(colpkr).fadeOut(500);
return false;
},
onChange: function (hsb, hex, rgb) {
$('#colorSelectorCone div').css('backgroundColor', '#' + hex);
updateObjectColor('cone',rgb.r/256,rgb.g/256,rgb.b/256);
},
onBeforeShow: function (colpkr) {
$(this).ColorPickerSetColor('rgb(0.8,0.1,0.5)');
}
})
// Calcule les normales des vertex. La normale de chaque vertex est
// la moyenne des triangles voisins.
//
// vertices: la liste des vertex.
// ind: la liste des indices.
// retour: la liste des normales par vertex.
function calcNormals(vertices, ind){
var x=0;
var y=1;
var z=2;
var v1 = [], v2 = [], thisNormal = [];
// initialiser la liste des normales.
var ns = [];
for(var i=0;i<vertices.length;i++)
{
ns[i]=0.0;
}
for(var i=0;i<ind.length;i=i+3){
//v1 = p1 - p0
v1[x] = vertices[3*ind[i+1]+x] - vertices[3*ind[i]+x];
v1[y] = vertices[3*ind[i+1]+y] - vertices[3*ind[i]+y];
v1[z] = vertices[3*ind[i+1]+z] - vertices[3*ind[i]+z];
// v2 = p2 - p1
v2[x] = vertices[3*ind[i+2]+x] - vertices[3*ind[i]+x];
v2[y] = vertices[3*ind[i+2]+y] - vertices[3*ind[i]+y];
v2[z] = vertices[3*ind[i+2]+z] - vertices[3*ind[i]+z];
// N = v2 x v1 (cross product).
thisNormal[x] = v1[y]*v2[z] - v1[z]*v2[y];
thisNormal[y] = v1[z]*v2[x] - v1[x]*v2[z];
thisNormal[z] = v1[x]*v2[y] - v1[y]*v2[x];
for(j=0;j<3;j++)
{
// N += thisNormal. on additionne les normales.
ns[3*ind[i+j]+x] = ns[3*ind[i+j]+x] + thisNormal[x];
ns[3*ind[i+j]+y] = ns[3*ind[i+j]+y] + thisNormal[y];
ns[3*ind[i+j]+z] = ns[3*ind[i+j]+z] + thisNormal[z];
}
}
// Normalisation.
for(var i=0;i<vertices.length;i=i+3){
var nn=[];
var len = 0;
for(var j = 0; j < 3; j++)
{
nn[j] = ns[i+j];
len += nn[j] * nn[j];
}
// La norme de la normale.
len = Math.sqrt(len);
if (len == 0)
len = 0.00001;
for(var j = 0; j < 3; j++)
ns[i+j] = nn[j] / len;
console.log(len);
}
return ns;
}
</script>
</body>
</html>
Honestly, there are too many issues to cover. Trying to get your code work. First you really need to learn how to make a minimal repo. The code you posted doesn't run, references several scripts that don't exist, and data that doesn't exist.
The tutorial the code is based off appears to be old. No one uses XMLHttpRequest in 2020. There's no such function as requestAnimFrame it's requestAnimationFrame. I think that's left over from a polyfill from like 2011. It's still using <body onload=""> which few use anymore. It's also using new Date().getTime() which there's no reason to use as the time is passed into requestAnimationFrame. It's calling some function to get a webgl context but there's no reason for that either in 2020. It's also apparently using an old version of glMatrix because the current version uses a different API. In the current version every function takes a matrix to store the result as the first argument. Also in the current version perspective takes the field of view in radians. I have no idea if it used to take it in degrees but the code was passing degrees.
I changed the XHR code to just return a cube. (an example of the minimal and complete parts of an "minimal complete verifiable example" (mcve) - I think now S.O calls them a "minimal reproducible example". I also removed all the references to ColorPicker (another example of making a minimal repo)
The code should have gotten errors in the JavaScript console. Did you check the JavaScript console? In particular uNMatrix is a mat3 but the code was calling gl.uniformMatrix4fv to set it which is an error.
Using webgl-lint pointed out several uniforms were not being set including "ambientProduct", "diffuseProduct", "specularProduct" and several were being set that don't exist (that part is not a bug necessarily) but there are several uniforms in the vertex shader that are not actually used and for example the colors seem to be set with
gl.uniform4fv(prg.uMaterialAmbient, object.ambient);
gl.uniform4fv(prg.uMaterialDiffuse, object.diffuse);
gl.uniform4fv(prg.uMaterialSpecular, object.specular);
but those uniforms are not used in the shaders (they appear in the shader but if you look at the code you'll see the effect nothing)
Then, when I finally got rid of all the errors the remaining issue to get something on the screen was the math for gl_Position had the 2 matrices backward. It should be projection * modelView * position but it had modelView * projection * position
But, further, all 3 models are drawn at the same position. Maybe the geometry in those models are at different positions. I have no idea but normally you'd want to give each thing you draw it's own position in some form or another, based on the loop index or some array of positions or per object data.
Anyway, that gets something on the screen but having spent 45 minutes already I don't feel like trying to fix the lighting. Rather, I suggest you read some slightly more up to date tutorials like this one and the ones it links to.
var mat4 = glMatrix.mat4;
var gl = null; // WebGL context
var prg = null; // The program (shaders)
var c_width = 480; // Variable to store the width of the canvas
var c_height = 400; // Variable to store the height of the canvas
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
var nMatrix = mat4.create(); // The normal matrix
var distance = -40;
var animateFlag = false;
var objects = [];
const utils = {
getShader(gl, id) {
const elem = document.getElementById(id);
const type = /vertex/.test(elem.type) ?
gl.VERTEX_SHADER :
gl.FRAGMENT_SHADER;
const sh = gl.createShader(type);
gl.shaderSource(sh, elem.text);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(sh));
}
return sh;
},
};
/**
* The program contains a series of instructions that tell the Graphic Processing Unit (GPU)
* what to do with every vertex and fragment that we pass it.
* The vertex shader and the fragment shader together are called the program.
*/
function initProgram() {
var fragmentShader = utils.getShader(gl, "shader-fs");
var vertexShader = utils.getShader(gl, "shader-vs");
prg = gl.createProgram();
gl.attachShader(prg, vertexShader);
gl.attachShader(prg, fragmentShader);
gl.linkProgram(prg);
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(prg);
prg.aVertexPosition = gl.getAttribLocation(prg, "aVertexPosition");
prg.aVertexNormal = gl.getAttribLocation(prg, "aVertexNormal");
prg.uPMatrix = gl.getUniformLocation(prg, "uPMatrix");
prg.uMVMatrix = gl.getUniformLocation(prg, "uMVMatrix");
prg.uNMatrix = gl.getUniformLocation(prg, "uNMatrix");
prg.uMaterialAmbient = gl.getUniformLocation(prg, "uMaterialAmbient");
prg.uMaterialDiffuse = gl.getUniformLocation(prg, "uMaterialDiffuse");
prg.uMaterialSpecular = gl.getUniformLocation(prg, "uMaterialSpecular");
prg.uShininess = gl.getUniformLocation(prg, "uShininess");
prg.uLightPosition = gl.getUniformLocation(prg, "uLightPosition");
prg.uLightAmbient = gl.getUniformLocation(prg, "uLightAmbient");
prg.uLightDiffuse = gl.getUniformLocation(prg, "uLightDiffuse");
prg.uLightSpecular = gl.getUniformLocation(prg, "uLightSpecular");
prg.ambientProduct = gl.getUniformLocation(prg, "ambientProduct");
prg.diffuseProduct = gl.getUniformLocation(prg, "diffuseProduct");
prg.specularProduct = gl.getUniformLocation(prg, "specularProduct");
}
function initLights() {
//Light uniforms
gl.uniform3fv(prg.uLightPosition, [4.5, 3.0, 15.0]);
gl.uniform4f(prg.uLightAmbient, 1.0, 1.0, 1.0, 1.0);
gl.uniform4f(prg.uLightDiffuse, 1.0, 1.0, 1.0, 1.0);
gl.uniform4f(prg.uLightSpecular, 1.0, 1.0, 1.0, 1.0);
//Object Uniforms
gl.uniform4f(prg.uMaterialAmbient, 0.1, 0.1, 0.1, 1.0);
gl.uniform4f(prg.uMaterialDiffuse, 0.5, 0.8, 0.1, 1.0);
gl.uniform4f(prg.uMaterialSpecular, 0.6, 0.6, 0.6, 1.0);
gl.uniform1f(prg.uShininess, 200.0);
}
/**
* Creates an AJAX request to load the scene asynchronously
*/
function loadScene() {
loadObject('models/plane.json');
loadObject('models/cone.json', 'cone');
loadObject('models/sphere.json', 'sphere');
loadObject('models/smallsph.json', 'lightsource');
}
function getObject(alias) {
for (var i = 0; i < objects.length; i++) {
if (alias == objects[i].alias) return objects[i];
}
return null;
}
/**
* Ajax and JSON in action
*/
const vertices = [
-1, -1, 1,
1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
1, 1, 1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, -1,
-1, 1, 1,
1, 1, -1,
-1, 1, -1,
1, 1, 1,
-1, 1, 1,
1, -1, 1,
-1, -1, 1,
1, -1, -1,
-1, -1, -1,
];
let modelNum = 0
function loadObject(filename, alias) {
setTimeout(() => {
const o = {
alias: (alias == null) ? 'none' : alias,
ambient: [0.1, 0.1, 0.1, 1],
diffuse: [Math.random(), Math.random(), Math.random(), 1],
specular: [1, 1, 1, 1],
// to make up for the fact the code does not have different positions
// for each model we'll move the vertices (bad)
vertices: vertices.map((v, i) => i % 3 === 0 ? v + modelNum * 3 : v),
indices: [
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
12, 13, 14, 14, 13, 15,
16, 17, 18, 18, 17, 19,
20, 21, 22, 22, 21, 23,
],
};
handleLoadedObject(filename, o);
++modelNum;
});
}
/**
* Creates the buffers that contain the geometry of the object
*/
function handleLoadedObject(filename, object) {
//console.info(filename + ' has been retrieved from the server');
var vertexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object.vertices), gl.STATIC_DRAW);
var normalBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(calcNormals(object.vertices, object.indices)), gl.STATIC_DRAW);
var indexBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object.indices), gl.STATIC_DRAW);
object.vbo = vertexBufferObject;
object.ibo = indexBufferObject;
object.nbo = normalBufferObject;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
objects.push(object);
}
/**
* Main rendering function. Called every 500ms according to WebGLStart function (see below)
*/
function drawScene() {
gl.clearColor(0.3, 0.3, 0.3, 1.0);
//gl.clearDepth(100.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.viewport(0, 0, c_width, c_height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(pMatrix, 60 * Math.PI / 180, c_width / c_height, 0.1, 1000.0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.enableVertexAttribArray(prg.aVertexNormal);
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, mvMatrix, [0.0, 0.0, distance]); //Sets the camera to a reasonable distance to view the part
mat4.rotate(mvMatrix, mvMatrix, 30 * Math.PI / 180, [1, 0, 0]);
mat4.rotate(mvMatrix, mvMatrix, angle * Math.PI / 180, [0, 1, 0]);
if (object.alias == 'lightsource') {
var lightPos = gl.getUniform(prg, prg.uLightPosition);
mat4.translate(mvMatrix, mvMatrix, lightPos);
}
gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
gl.uniformMatrix4fv(prg.uPMatrix, false, pMatrix);
mat4.set(mvMatrix, nMatrix);
mat4.invert(nMatrix, nMatrix);
mat4.transpose(nMatrix, nMatrix);
const t3 = glMatrix.mat3.create();
glMatrix.mat3.fromMat4(t3, nMatrix);
gl.uniformMatrix3fv(prg.uNMatrix, false, t3);
gl.uniform4fv(prg.ambientProduct, object.ambient);
gl.uniform4fv(prg.diffuseProduct, object.diffuse);
gl.uniform4fv(prg.specularProduct, object.specular);
gl.bindBuffer(gl.ARRAY_BUFFER, object.vbo);
gl.vertexAttribPointer(prg.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, object.nbo);
gl.vertexAttribPointer(prg.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(prg.aVertexNormal);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);
gl.drawElements(gl.TRIANGLES, object.indices.length, gl.UNSIGNED_SHORT, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
var lastTime = 0;
var angle = 0;
/**
* Updates the angle of rotation by a little bit each time
*/
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;
if (animateFlag) angle += (90 * elapsed) / 10000.0;
}
lastTime = timeNow;
}
/**
* Render Loop
*/
function renderLoop() {
drawScene();
animate();
requestAnimationFrame(renderLoop);
}
/**
* Entry point. This function is invoked when the page is loaded
*/
function runWebGLApp() {
//Obtains a WebGL context
gl = document.getElementById("canvas-element-id").getContext('webgl');
//Initializes the program (shaders)
initProgram();
//Initializes lights
initLights();
//Load Scene
loadScene();
//Renders the scene!
renderLoop();
}
$('#slider-shininess').slider({
value: 200,
min: 1,
max: 300,
step: 1,
slide: updateShininess
});
$('#slider-x').slider({
value: 4.5,
min: -50,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#slider-y').slider({
value: 3.0,
min: 0,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#slider-z').slider({
value: 15.0,
min: -50,
max: 50,
step: 0.1,
slide: updateLightPosition,
change: updateLightPosition
});
$('#animate-btn').button();
$('#animate-btn').click(
function() {
if ($('#animate-btn:checked').val() == null) {
animateFlag = false;
} else {
animateFlag = true;
}
});
function updateShininess() {
var v = $('#slider-shininess').slider("value");
gl.uniform1f(prg.uShininess, v);
$('#slider-shininess-value').html(v);
}
function updateLightPosition() {
var x = $('#slider-x').slider("value");
var y = $('#slider-y').slider("value");
var z = $('#slider-z').slider("value");
gl.uniform3fv(prg.uLightPosition, [x, y, z]);
$('#slider-x-value').html(x);
$('#slider-y-value').html(y);
$('#slider-z-value').html(z);
}
function updateDistance() {
var d = $('#slider-distance').slider("value");
$('#slider-distance-value').html(distance);
distance = -d;
}
function updateObjectColor(alias, r, g, b) {
var object = getObject(alias);
if (object != null) {
object.diffuse = [r, g, b, 1.0];
}
}
// Calcule les normales des vertex. La normale de chaque vertex est
// la moyenne des triangles voisins.
//
// vertices: la liste des vertex.
// ind: la liste des indices.
// retour: la liste des normales par vertex.
function calcNormals(vertices, ind) {
var x = 0;
var y = 1;
var z = 2;
var v1 = [],
v2 = [],
thisNormal = [];
// initialiser la liste des normales.
var ns = [];
for (var i = 0; i < vertices.length; i++) {
ns[i] = 0.0;
}
for (var i = 0; i < ind.length; i = i + 3) {
//v1 = p1 - p0
v1[x] = vertices[3 * ind[i + 1] + x] - vertices[3 * ind[i] + x];
v1[y] = vertices[3 * ind[i + 1] + y] - vertices[3 * ind[i] + y];
v1[z] = vertices[3 * ind[i + 1] + z] - vertices[3 * ind[i] + z];
// v2 = p2 - p1
v2[x] = vertices[3 * ind[i + 2] + x] - vertices[3 * ind[i] + x];
v2[y] = vertices[3 * ind[i + 2] + y] - vertices[3 * ind[i] + y];
v2[z] = vertices[3 * ind[i + 2] + z] - vertices[3 * ind[i] + z];
// N = v2 x v1 (cross product).
thisNormal[x] = v1[y] * v2[z] - v1[z] * v2[y];
thisNormal[y] = v1[z] * v2[x] - v1[x] * v2[z];
thisNormal[z] = v1[x] * v2[y] - v1[y] * v2[x];
for (j = 0; j < 3; j++) {
// N += thisNormal. on additionne les normales.
ns[3 * ind[i + j] + x] = ns[3 * ind[i + j] + x] + thisNormal[x];
ns[3 * ind[i + j] + y] = ns[3 * ind[i + j] + y] + thisNormal[y];
ns[3 * ind[i + j] + z] = ns[3 * ind[i + j] + z] + thisNormal[z];
}
}
// Normalisation.
for (var i = 0; i < vertices.length; i = i + 3) {
var nn = [];
var len = 0;
for (var j = 0; j < 3; j++) {
nn[j] = ns[i + j];
len += nn[j] * nn[j];
}
// La norme de la normale.
len = Math.sqrt(len);
if (len == 0)
len = 0.00001;
for (var j = 0; j < 3; j++)
ns[i + j] = nn[j] / len;
}
return ns;
}
runWebGLApp();
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet">
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
// matrice model-view combinee.
uniform mat4 uMVMatrix;
// matrice de projection
uniform mat4 uPMatrix;
// matrice des normales.
uniform mat3 uNMatrix;
// position de la lumiere.
uniform vec3 uLightPosition;
// La normale transformee
varying vec3 vNormal;
// la direction vertex-lumiere
varying vec3 vLightRay;
// la direction camera-vertex
varying vec3 vEyeVec;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse;
uniform vec4 uLightSpecular;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialSpecular;
void main(void) {
vec4 ambientProduct= uLightAmbient* uMaterialAmbient;
vec4 diffuseProduct= uLightDiffuse*uMaterialDiffuse;
vec4 specularProduct= uLightSpecular*uMaterialSpecular;
vec3 pos = (uMVMatrix*vec4(aVertexPosition, 1.0)).xyz;
// position de l'oeil/camera.
const vec3 eyePosition = vec3(0,0,-40);
//Transformed normal position
vNormal = normalize((uNMatrix* aVertexNormal).xyz) ;
//Transformed light position
vec4 light = uMVMatrix * vec4(uLightPosition,1.0);
vec3 lightPos = (uMVMatrix * light).xyz;
//Light position
vLightRay = normalize(pos - lightPos);
//Vector Eye
vEyeVec = -normalize(pos);
//Final vertex position
gl_Position = uPMatrix*uMVMatrix* vec4(aVertexPosition, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
varying vec3 vNormal;
varying vec3 vLightRay;
varying vec3 vEyeVec;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float uShininess;
void main(void)
{
vec4 diffuse = max(dot( vNormal,vLightRay), 0.0) * diffuseProduct;
vec3 H = normalize(vLightRay+vEyeVec);
vec4 specular =
pow(max(dot(vNormal, H), 0.0), uShininess) * specularProduct;
if (dot(vLightRay, vNormal) < 0.0)
specular = vec4(0.0, 0.0, 0.0, 1.0);
vec4 fColor = ambientProduct + diffuse + specular;
fColor.a = 1.0;
gl_FragColor =fColor;
}
</script>
<div id='top'>
<div id='contents'>
<div id='canvasContainer'>
<canvas id='canvas-element-id' width='480' height='400'>
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>
<div id='bottom'>
<table style='padding=0px'>
<tr>
<td>X:</td><td id='slider-x-value' width='30px'>4.5</td><td width='150px'><div id='slider-x'/></td>
</tr>
<tr>
<td>Y:</td><td id='slider-y-value' width='30px'>3.0</td><td width='150px'><div id='slider-y'/></td>
</tr>
<tr>
<td>Z:</td> <td id='slider-z-value' width='30px'>15.0</td><td width='150px'><div id='slider-z'/></td>
</tr>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/gl-matrix#3.3.0/gl-matrix-min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js"
data-gman-debug-helper='
{
"warnUndefinedUniforms": false
}
'
></script>
I am trying to preserve the proportions at least a little when the window is resized to smaller sizes at the CodePen here. Currently it turns out to be really hard to see the lines and the interaction on mobile. Do you have a solution for this? Maybe it makes sense to double the scale on resize based on the window but I am a bit lost on how I can implement it.
The responsible part of the JS:
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(event) {
container.style.height = window.innerHeight+"px";
container.style.width = window.innerWidth+"px";
canvasWidth = container.offsetWidth;
canvasHeight = container.offsetHeight;
//send new size value to the shader and resize the window
uniforms.resolution.value.x = canvasWidth;
uniforms.resolution.value.y = canvasHeight;
//var res = canvasWidth / cols;
//rows = canvasHeight / res;
uniforms.colsrows.value.x = cols;
uniforms.colsrows.value.y = rows;//rows;
renderer.setSize(canvasWidth, canvasHeight);
}
Here is the pen:
//Create var for the contenair, the webGL 3D scene, uniforms to bind into shader and timer
var container;
var camera, scene, renderer;
var uniforms;
var startTime;
var cols = 50.;
var rows = 50.0;
init(); //init scene
animate(); //updateScene
function init() {
//get contenaire
container = document.getElementById('container');
//Create THREE.JS scene and timer
startTime = Date.now();
camera = new THREE.Camera();
camera.position.z = 1;
scene = new THREE.Scene();
//create a simple plance
var geometry = new THREE.PlaneBufferGeometry(16, 9);
//create uniform table which provide all our GLSL binding
uniforms = {
time: { type: "f", value: 1.0 },
resolution: { type: "v2", value: new THREE.Vector2() },
colsrows: {type: "v2", value: new THREE.Vector2()},
mouse: {type: "v2", value: new THREE.Vector2()}
};
//create THREE.JS material
var material = new THREE.ShaderMaterial( {
//set shaders and uniforms into material
uniforms: uniforms,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent
} );
//create mesh, add it to the scene
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
//create renderer and add it to the DOM
renderer = new THREE.WebGLRenderer();
container.appendChild(renderer.domElement);
//check window for resize This will give us the proper resolution values to bind
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(event) {
container.style.height = window.innerHeight+"px";
container.style.width = window.innerWidth+"px";
canvasWidth = container.offsetWidth;
canvasHeight = container.offsetHeight;
//send new size value to the shader and resize the window
uniforms.resolution.value.x = canvasWidth;
uniforms.resolution.value.y = canvasHeight;
//var res = canvasWidth / cols;
//rows = canvasHeight / res;
uniforms.colsrows.value.x = cols;
uniforms.colsrows.value.y = rows;//rows;
renderer.setSize(canvasWidth, canvasHeight);
}
function animate() {
render();
requestAnimationFrame(animate);
}
function render() {
var currentTime = Date.now();
var elaspedSeconds = (currentTime - startTime) / 1000.0;
var maxTime = 4.0;
var normTime = (elaspedSeconds % maxTime) / maxTime;
uniforms.time.value = elaspedSeconds * 1.0;
renderer.render(scene, camera);
}
function move(ev){
mx = ev.clientX
my = ev.clientY;
// console.log(mx+" , "+my);
uniforms.mouse.value.x = mx;
uniforms.mouse.value.y = my;
}
document.addEventListener('mousemove', move)
html, body {
margin: 0;
height: 100%;
background : #1a1a1a;
}
canvas {
display: block;
cursor: none;
}
#container{
background : black;
color : white;
margin: auto;
width : 500px;
height : 500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<div id="container"></div>
<!-- GLSL SCRIPT -->
<!-- vertex shader -->
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
gl_Position = vec4(position, 1.0);
}
</script>
<!-- fragment shader -->
<script id="fragmentShader" type="x-shader/x-fragment">
#define TWO_PI 6.28318530718
#define EPSILON 0.000011
uniform vec2 resolution;
uniform float time;
uniform vec2 colsrows;
uniform vec2 mouse;
float HueToRGB(float f1, float f2, float hue)
{
if (hue < 0.0)
hue += 1.0;
else if (hue > 1.0)
hue -= 1.0;
float res;
if ((6.0 * hue) < 1.0)
res = f1 + (f2 - f1) * 6.0 * hue;
else if ((2.0 * hue) < 1.0)
res = f2;
else if ((3.0 * hue) < 2.0)
res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0;
else
res = f1;
return res;
}
vec3 HSLToRGB(vec3 hsl)
{
vec3 rgb;
if (hsl.y == 0.0)
rgb = vec3(hsl.z); // Luminance
else
{
float f2;
if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y);
else
f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);
float f1 = 2.0 * hsl.z - f2;
rgb.r = HueToRGB(f1, f2, hsl.x + (1.0/3.0));
rgb.g = HueToRGB(f1, f2, hsl.x);
rgb.b= HueToRGB(f1, f2, hsl.x - (1.0/3.0));
}
return rgb;
}
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
vec2 rotateFrom(vec2 uv, vec2 center, float angle){
vec2 uv_ = uv - center;
uv_ = rotate2d(angle) * uv_;
uv_ = uv_ + center;
return uv_;
}
float random(float value){
return fract(sin(value) * 43758.5453123);
}
float random(vec2 tex){
return fract(sin(dot(tex.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
vec2 random2D(vec2 uv){
uv = vec2(dot(uv, vec2(127.1, 311.7)), dot(uv, vec2(269.5, 183.3)));
//return -1.0 + 2.0 * fract(sin(uv) * 43758.5453123);
return fract(sin(uv) * 43758.5453123); //return without offset on x, y
}
vec3 random3D(vec3 uv){
uv = vec3(dot(uv, vec3(127.1, 311.7, 120.9898)), dot(uv, vec3(269.5, 183.3, 150.457)), dot(uv, vec3(380.5, 182.3, 170.457)));
return -1.0 + 2.0 * fract(sin(uv) * 43758.5453123);
}
float cubicCurve(float value){
return value * value * (3.0 - 2.0 * value); // custom cubic curve
}
vec2 cubicCurve(vec2 value){
return value * value * (3.0 - 2.0 * value); // custom cubic curve
}
vec3 cubicCurve(vec3 value){
return value * value * (3.0 - 2.0 * value); // custom cubic curve
}
float noise(vec2 uv){
vec2 iuv = floor(uv);
vec2 fuv = fract(uv);
vec2 suv = cubicCurve(fuv);
float dotAA_ = dot(random2D(iuv + vec2(0.0)), fuv - vec2(0.0));
float dotBB_ = dot(random2D(iuv + vec2(1.0, 0.0)), fuv - vec2(1.0, 0.0));
float dotCC_ = dot(random2D(iuv + vec2(0.0, 1.0)), fuv - vec2(0.0, 1.0));
float dotDD_ = dot(random2D(iuv + vec2(1.0, 1.0)), fuv - vec2(1.0, 1.0));
return mix(
mix(dotAA_, dotBB_, suv.x),
mix(dotCC_, dotDD_, suv.x),
suv.y);
}
float noise(vec3 uv){
vec3 iuv = floor(uv);
vec3 fuv = fract(uv);
vec3 suv = cubicCurve(fuv);
float dotAA_ = dot(random3D(iuv + vec3(0.0)), fuv - vec3(0.0));
float dotBB_ = dot(random3D(iuv + vec3(1.0, 0.0, 0.0)), fuv - vec3(1.0, 0.0, 0.0));
float dotCC_ = dot(random3D(iuv + vec3(0.0, 1.0, 0.0)), fuv - vec3(0.0, 1.0, 0.0));
float dotDD_ = dot(random3D(iuv + vec3(1.0, 1.0, 0.0)), fuv - vec3(1.0, 1.0, 0.0));
float dotEE_ = dot(random3D(iuv + vec3(0.0, 0.0, 1.0)), fuv - vec3(0.0, 0.0, 1.0));
float dotFF_ = dot(random3D(iuv + vec3(1.0, 0.0, 1.0)), fuv - vec3(1.0, 0.0, 1.0));
float dotGG_ = dot(random3D(iuv + vec3(0.0, 1.0, 1.0)), fuv - vec3(0.0, 1.0, 1.0));
float dotHH_ = dot(random3D(iuv + vec3(1.0, 1.0, 1.0)), fuv - vec3(1.0, 1.0, 1.0));
float passH0 = mix(
mix(dotAA_, dotBB_, suv.x),
mix(dotCC_, dotDD_, suv.x),
suv.y);
float passH1 = mix(
mix(dotEE_, dotFF_, suv.x),
mix(dotGG_, dotHH_, suv.x),
suv.y);
return mix(passH0, passH1, suv.z);
}
float drawLine(vec2 uv, vec2 p1, vec2 p2, float r)
{
//from https://www.shadertoy.com/view/MtlSDr
vec2 l = p2 - p1;
float L = length(l);
float L2 = L*L;
float d1 = length(uv - p1);
float d2 = length(uv - p2);
float d = min(d1, d2);
float ds = dot(uv - p1, l);
if (ds >= 0.0 && ds <= L2)
{
vec2 n = vec2(-l.y, l.x) / L;
d = min(d, abs(dot(uv - p1, n)));
}
return 1.0 - smoothstep(0.0, 0.01, d - r);
}
vec2 fishey(vec2 uv, vec2 center, float ratio, float dist){
vec2 puv = uv + vec2(1.0);
//center coords
vec2 m = vec2(center.x, center.y/ratio) + vec2(1.0);
//vector from center to current fragment
vec2 d = puv - m;
// distance of pixel from center
float r = sqrt(dot(d, d));
//amount of effect
float power = ( TWO_PI / (2.0 * sqrt(dot(m, m)))) * mix(0.1, 0.4, pow(dist, 0.75));
//radius of 1:1 effect
float bind;
if (power > 0.0) bind = sqrt(dot(m, m));//stick to corners
//else {if (ratio < 1.0) bind = m.x; else bind = m.y;}//stick to borders
//Weird formulas
vec2 nuv;
if (power > 0.0)//fisheye
nuv = m + normalize(d) * tan(r * power) * bind / tan( bind * power);
else if (power < 0.0)//antifisheye
nuv = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0);
else
nuv = puv;//no effect for power = 1.0
return nuv - vec2(1.0);
}
vec4 addGrain(vec2 uv, float time, float grainIntensity){
float grain = random(fract(uv * time)) * grainIntensity;
return vec4(vec3(grain), 1.0);
}
void main(){
vec2 ouv = gl_FragCoord.xy / resolution.xy;
vec2 uv = ouv;
float ratio = resolution.x / resolution.y;
vec2 nmouse = vec2(mouse.x, mouse.y) / resolution.xy;
nmouse.y = 1.0 - nmouse.y;
float maxDist = 0.35;
float blurEdge = maxDist * 0.5;
float blurEdge2 = maxDist * 1.0;
vec2 mouseToUV = (uv - nmouse) / vec2(1.0, ratio);
float mouseDistance = 1.0 - smoothstep(maxDist - blurEdge, maxDist, length(mouseToUV));
float mouseDistance2 = 1.0 - smoothstep(maxDist - blurEdge2, maxDist, length(mouseToUV));
uv = fishey(uv, nmouse, ratio, mouseDistance2);
uv = rotateFrom(uv, vec2(0.5), time * 0.1);
//animate y
//wave
uv.y /= ratio;
vec2 basedUV = uv + vec2(1.0);
float complexityX = 10.0;
float complexityY = 10.0;
float maxAmp = mix(0.05, 0.75, mouseDistance);
float amp = 0.01 * mouseDistance + noise(vec3(basedUV.x * complexityX, basedUV.y * complexityY, time * 0.1)) * maxAmp;
float theta = time + mouseDistance + basedUV.y * (TWO_PI);
uv.x = fract(uv.x + sin(theta) * amp);
//divide into cols rows
vec2 nuv = uv * colsrows;
vec2 fuv = fract(nuv);
vec2 iuv = floor(nuv);
float minSpeed = 1.0;
float maxSpeed = 5.0;
float speed = minSpeed + random(floor(uv.x * colsrows.x)) * (maxSpeed - minSpeed);
fuv.y = fract(fuv.y + time * speed);
//draw dash line
float minWeight = 0.005 + random(vec2(iuv.x, 0.0)) * 0.05;
float strokeWeight = mix(minWeight, minWeight * 5.0, mouseDistance);
float dlineWidth = mix(1.0, 0.25 - strokeWeight, mouseDistance);//0.5 - strokeWeight;
float dline = drawLine(fuv, vec2(0.5, 0.5 - dlineWidth * 0.5), vec2(0.5, 0.5 + dlineWidth * 0.5), strokeWeight);
float randIndexHue = random(vec2(iuv.x + floor(time), 0.0));
float noiseHue = noise(vec3(randIndexHue, randIndexHue, time));
float hue = mix(0.111, 0.138, randIndexHue + (noiseHue * 0.5));
vec4 grain = addGrain(ouv, time, 0.15);
//vec3 color = HSLToRGB(vec3(hue, 1.0, 0.5));
//vec3 bgColor = HSLToRGB(vec3(0.772, mix(0.75, 1.0, mouseDistance), mix(0.1, 0.25, mouseDistance)));
vec3 color = vec3(1.0);
vec3 bgColor = vec3(0.0);
float val = dline * (mouseDistance * 0.5 + 0.5);
vec3 albedo = mix(bgColor, color, val);
gl_FragColor = vec4(albedo, 1.0) + grain;
}
</script>
The thickness of the lines is computed in drawLine and depends on the parameters to smoothstep:
float drawLine(vec2 uv, vec2 p1, vec2 p2, float r)
{
// [...]
return 1.0 - smoothstep(0.0, 0.01, d - r);
}
Increase the parameter to edge1, to generate "thicker" lines (e.g. 0.1):
float drawLine(vec2 uv, vec2 p1, vec2 p2, float r)
{
// [...]
return 1.0 - smoothstep(0.0, 0.1, d - r);
}
You can add an additional uniform variable for the line:
uniform float thickness;
float drawLine(vec2 uv, vec2 p1, vec2 p2, float r)
{
// [...]
return 1.0 - smoothstep(0.0, thickness, d - r);
}
uniforms = {
// [...]
thickness: { type: "f", value: 0.1 },
};
I am lately experimenting and learning webgl and shaders, as i am trying to build animation for the website background. I was able to port simple examples from shadertoy into three.js to play with it. Now i am trying to better understand more advanced examples and i am struggling with this particlar example:
https://www.shadertoy.com/view/4sfBWj
I am aware that to port shader program to three.js you need to:
create three.js basic setup with new THREE.PlaneGeometry()
create uniforms for iTime and iResolution
create vertex and fragment shaders script tags
populate fragment shader with shadertoy contents (image section)
populate vertex shader with generic script
change names to gl_FragColor and gl_FragCoord
change name of the function to void main(void)
if there is some texture used in one or more channels then
load texture with new THREE.TextureLoader() and create uniform for iChannel0
Basic examples will be good to go with above. However the one i linked has:
Buffer A
Buffer B
and both of these also include shader programs and main functions that runs them, how do i deal with it to be able to port it to three.js?
my current progress:
var container;
var camera, scene0, scene1, scene2, renderer;
var uniforms0, uniforms1, uniforms2;
var startTime;
var renderTarget0, renderTarget1;
var clock = new THREE.Clock();
init();
animate();
function init() {
container = document.getElementById( 'container' );
startTime = Date.now();
camera = new THREE.Camera();
camera.position.z = 1;
scene0 = new THREE.Scene();
scene1 = new THREE.Scene();
scene2 = new THREE.Scene();
renderTarget0 = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
renderTarget1 = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
/* scene0 */
var geometry0 = new THREE.PlaneGeometry(700, 394, 1, 1);
uniforms0 = {
iTime: { type: "f", value: 1.0 },
iResolution: { type: "v1", value: new THREE.Vector2(), }
};
var material0 = new THREE.ShaderMaterial( {
uniforms: uniforms0,
vertexShader: document.getElementById( 'vs0' ).textContent,
fragmentShader: document.getElementById( 'fs0' ).textContent
});
/* scene0 */
var mesh0 = new THREE.Mesh( geometry0, material0 );
/* scene1 */
var geometry1 = new THREE.PlaneGeometry(700, 394, 1, 1);
uniforms1 = {
iTime: { type: "f", value: 1.0 },
iResolution: { type: "v1", value: new THREE.Vector2(), }
};
var material1 = new THREE.ShaderMaterial( {
uniforms: uniforms1,
vertexShader: document.getElementById( 'vs1' ).textContent,
fragmentShader: document.getElementById( 'fs1' ).textContent,
iChannel0: {type: 't', value: renderTarget0 }
});
var mesh1 = new THREE.Mesh( geometry1, material1 );
/* scene1 */
/* scene2 */
var geometry2 = new THREE.PlaneGeometry(700, 394, 1, 1);
uniforms2 = {
iTime: { type: "f", value: 1.0 },
iResolution: { type: "v1", value: new THREE.Vector2(), }
};
var material2 = new THREE.ShaderMaterial( {
uniforms: uniforms1,
vertexShader: document.getElementById( 'vs2' ).textContent,
fragmentShader: document.getElementById( 'fs2' ).textContent,
iChannel0: {type: 't', value: renderTarget0 },
iChannel1: {type: 't', value: renderTarget1 }
});
var mesh2 = new THREE.Mesh( geometry2, material2 );
/* scene2 */
scene0.add( mesh0 );
scene1.add( mesh1 );
scene2.add( mesh2 );
renderer = new THREE.WebGLRenderer();
container.appendChild( renderer.domElement );
onWindowResize();
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize( event ) {
uniforms0.iResolution.value.x = window.innerWidth;
uniforms0.iResolution.value.y = window.innerHeight;
uniforms1.iResolution.value.x = window.innerWidth;
uniforms1.iResolution.value.y = window.innerHeight;
uniforms2.iResolution.value.x = window.innerWidth;
uniforms2.iResolution.value.y = window.innerHeight;
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
//renderer.render(scene0, camera, renderTarget0);
//renderer.render(scene1, camera, renderTarget1);
uniforms1.iChannel0.value = rendertarget0.texture;
uniforms2.iChannel0.value = rendertarget0.texture;
uniforms2.iChannel1.value = rendertarget1.texture;
uniforms0.iTime.value += clock.getDelta();
uniforms1.iTime.value += clock.getDelta();
uniforms2.iTime.value += clock.getDelta();
//renderer.render( scene2, camera );
}
body {
margin:0;
padding:0;
overflow:hidden;
}
<script src="//threejs.org/build/three.min.js"></script>
<div id="container"></div>
<!-- BREAK --->
<script id="vs0" type="x-shader/x-vertex">
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fs0" type="x-shader/x-fragment">
uniform vec2 iResolution;
uniform float iTime;
const mat2 m = mat2( 0.8, 0.6, -0.6, 0.8 );
const mat3 m3 = mat3( 0.8, 0.6, 0.0, -0.6, 0.80, 0.0, 0.0, 0.0, 1.0) *
mat3( 1.0, 0.0, 0.0, 0.0, -0.60, 0.80, 0.0, 0.8, 0.6) *
mat3( 0.8, 0.6, 0.0, -0.6, 0.80, 0.0, 0.0, 0.0, 1.0) *
mat3( 1.0, 0.0, 0.0, 0.0, -0.60, 0.80, 0.0, 0.8, 0.6);
float time;
float n1f0(float p) {
return fract(sin(p * 1.7227636) * 8.03e2);
}
float n1f1(float p) {
return fract(sin(p * 1.42736 + 1.12) * 5.1e2);
}
float n1f2(float p) {
return fract(sin(p * 1.22712 + 12.161) * 5.2e2);
}
float n3f(vec3 p) {
return fract(n1f0(p.x) + n1f1(p.y) + n1f2(p.z) + n1f0(p.x * 1.613) + n1f1(p.y * 3.112) + n1f2(p.z * 4.112));
}
float n3(vec3 p) {
vec3 b = floor(p);
vec3 e = b + vec3(1.0);
vec3 f = smoothstep(vec3(0.0), vec3(1.0), fract(p));
float c000 = n3f(b);
float c001 = n3f(vec3(b.x, b.y, e.z));
float c010 = n3f(vec3(b.x, e.y, b.z));
float c011 = n3f(vec3(b.x, e.y, e.z));
float c100 = n3f(vec3(e.x, b.y, b.z));
float c101 = n3f(vec3(e.x, b.y, e.z));
float c110 = n3f(vec3(e.x, e.y, b.z));
float c111 = n3f(e);
vec4 z = mix(vec4(c000, c100, c010, c110), vec4(c001, c101, c011, c111), f.z);
vec2 yz = mix(z.xy, z.zw, f.y);
return mix(yz.x, yz.y, f.x);
}
float fbm4( vec3 p )
{
float f = 0.0;
p = m3 * p;
f += 0.5000*n3( p ); p = m3*p*2.02;
f += 0.2500*n3( p ); p = m3*p*2.03;
f += 0.1250*n3( p ); p = m3*p*2.01;
f += 0.0625*n3( p );
return f/0.9375;
}
float fbm4( vec2 p )
{
return fbm4(vec3(p, time));
}
float fbm6( vec3 p )
{
float f = 0.0;
p = m3 * p;
f += 0.500000*n3( p ); p = m3*p*2.02;
f += 0.250000*n3( p ); p = m3*p*2.03;
f += 0.125000*n3( p ); p = m3*p*2.01;
f += 0.062500*n3( p ); p = m3*p*2.04;
f += 0.031250*n3( p ); p = m3*p*2.01;
f += 0.015625*n3( p );
return f/0.984375;
}
float fbm6( vec2 p )
{
return fbm6(vec3(p, time));
}
float grid(vec2 p) {
p = sin(p * 3.1415);
return smoothstep(-0.01, 0.01, p.x * p.y);
}
void main(void) {
time = iTime * 0.7;
vec2 q = gl_FragCoord.xy / iResolution.xy;
vec2 p = -1.0 + 2.0 * q;
p.x *= iResolution.x/iResolution.y;
p.y *= 0.3;
p.y -= time * 1.5;
float tc = time * 1.2;
float tw1 = time * 2.5;
float tw2 = time * 0.6;
vec3 vw1 = vec3(p, tw1);
vw1.y *= 2.8;
vec2 ofs1 = vec2(fbm4(vw1), fbm4(vw1 + vec3(10.0, 20.0, 50.0)));
ofs1.y *= 0.3;
ofs1.x *= 1.3;
vec3 vw2 = vec3(p, tw2);
vw2.y *= 0.8;
vec2 ofs2 = vec2(fbm4(vw2), fbm4(vw2 + vec3(10.0, 20.0, 50.0)));
ofs2.y *= 0.3;
ofs2.x *= 1.3;
vec2 vs = (p + ofs1 * 0.5 + ofs2 * 0.9) * 4.0;
vec3 vc = vec3(vs, tc);
float l;
l = fbm6(vc);
l = smoothstep(0.0, 1.0, l);
l = max(0.0, (l - pow(q.y * 0.8, 0.6)) * 1.8);
float r = pow(l , 1.5);
float g = pow(l , 3.0);
float b = pow(l , 6.0);
//r = grid(vs);
gl_FragColor = vec4( r, g, b, 1.0 );
}
</script>
<!-- BREAK --->
<script id="vs1" type="x-shader/x-vertex">
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fs1" type="x-shader/x-fragment">
uniform vec2 iResolution;
uniform float iTime;
uniform sampler2D iChannel0;
#ifdef GL_ES
precision mediump float;
#endif
#define SIGMA 5.0
float normpdf(in float x, in float sigma)
{
return 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma;
}
void main(void) {
vec3 c = texture2D(iChannel0, gl_FragCoord.xy / iResolution.xy).rgb;
//declare stuff
const int mSize = int(SIGMA * 11.0/7.0);
const int kSize = (mSize-1)/2;
float kernel[mSize];
vec3 finalColor = vec3(0.0);
//create the 1-D kernel
float sigma = SIGMA;
float Z = 0.0;
for (int j = 0; j <= kSize; ++j)
{
kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma);
}
//get the normalization factor (as the gaussian has been clamped)
for (int j = 0; j < mSize; ++j)
{
Z += kernel[j];
}
//read out the texels
for (int i=-kSize; i <= kSize; ++i)
{
for (int j=-kSize; j <= kSize; ++j)
{
finalColor += kernel[kSize+j]*kernel[kSize+i]*texture2D(iChannel0, (gl_FragCoord.xy+vec2(float(i),float(j))) / iResolution.xy).rgb;
}
}
finalColor /= Z*Z;
//finalColor = c + finalColor * 0.3;
gl_FragColor = vec4(finalColor, 1.0);
}
</script>
<!-- BREAK --->
<script id="vs2" type="x-shader/x-vertex">
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fs2" type="x-shader/x-fragment">
uniform vec2 iResolution;
uniform float iTime;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
#ifdef GL_ES
precision mediump float;
#endif
#define SIGMA 5.0
float normpdf(in float x, in float sigma)
{
return 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma;
}
void main(void) {
vec3 c = texture2D(iChannel0, gl_FragCoord.xy / iResolution.xy).rgb;
//gl_FragColor = vec4(c, 1.0);
//return;
//declare stuff
const int mSize = int(SIGMA * 11.0/7.0);
const int kSize = (mSize-1)/2;
float kernel[mSize];
vec3 finalColor = vec3(0.0);
//create the 1-D kernel
float sigma = SIGMA;
float Z = 0.0;
for (int j = 0; j <= kSize; ++j)
{
kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma);
}
//get the normalization factor (as the gaussian has been clamped)
for (int j = 0; j < mSize; ++j)
{
Z += kernel[j];
}
//read out the texels
for (int i=-kSize; i <= kSize; ++i)
{
for (int j=-kSize; j <= kSize; ++j)
{
finalColor += kernel[kSize+j]*kernel[kSize+i]*texture2D(iChannel1, (gl_FragCoord.xy+vec2(float(i),float(j))) / iResolution.xy).rgb;
}
}
finalColor /= Z*Z;
finalColor = c + pow(finalColor, vec3(0.5)) * 0.5;
gl_FragColor = vec4(finalColor, 1.0);
}
</script>
This example uses multiple renders per frame. It works like this:
render shaderA to buffer
pass output to shaderB
render shaderB to buffer
pass output to shaderC
render shaderC to canvas
To replicate this in Three.js, you'll need a WebGLRenderTarget as intermediary to pass the output from one render as a texture to the next shader. Here's the pseudocode with only 2 renders, you'll need to extend it if you need more:
var renderer = new WebGLRenderer(w, h, ...);
var scene0 = new Scene();
var scene1 = new Scene();
var plane0 = new THREE.PlaneBufferGeometry(1, 1);
var plane1 = new THREE.PlaneBufferGeometry(1, 1);
// ... continue building materials, shaders, etc
// Add plane mesh to its corresponding scene
scene0.add(planeMesh0);
scene1.add(planeMesh1);
// You should only need one camera if they're both in the same position.
var cam = new Camera();
// renderTarget will store the first buffer
var renderTarget = new WebGLRenderTarget(w, h);
update() {
// This pass will render the first shader
// Its output will be drawn on the rendertarget's texture
renderer.render(scene0, cam, renderTarget);
// We assign the output of the first render pass to the second plane
plane1.uniforms.texture.value = rendertarget.texture;
// Now we render the second shader to the canvas
renderer.render(scene1, cam);
}
Keep in mind you have to render separate scenes in each pass to avoid recursion issues, so you'll have to add each plane to a separate scene. To learn more about WebGLRenderTarget you can read about it in the docs
The debugger statement (billed as surefire way to stop) gives me an "ERROR undeclared identifier" in chrome. Source code is JavaScript WebGL
I have even tried to set breakpoints in pages that run correctly. I have enabled WebGL Inspector extension and checked the "Allow access to file URLs" box
I'm sorry to bother anyone because I am missing something obvious and basic.
Julia mine
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 vPos;
void main(void) {
gl_Position = vec4(vPos, 1.);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 c;
uniform vec2 scale;
void main(void) {
float x1 = 2.0;
float y1 = -2.0;
float R = (gl_FragCoord.x - scale.x) / scale.y;
float I = (gl_FragCoord.y - scale.x) / scale.y;
// float R2 = R*R, I2 = I*I;
float R2 = R;
debugger;
float I2 = I;
int mm;
for (int m = 0; m < 255; m++) {
debugger;
x1 = 2.0 + exp(R2) / 2.0 * cos(I2);
y1 = -2.0 + exp(R2) * sin(I2);
if (x1 >= 0.0000001) break;
R2 = x1;
I2 = y1;
} //end for m
if (mm == 254) gl_FragColor = vec4(0., 0., 0., 1.);
else {
float a = float(mm);
a = mod(a, 15.) / 5.;
gl_FragColor = vec4(max(0., abs(a - 1.5) - .5),
max(0., 1. - abs(a - 0.8)), max(0., 1. - abs(a - 2.)), 0.94);
} //end else
}
</script>
<script type="text/javascript">
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) str += k.textContent;
k = k.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment")
shader = gl.createShader(gl.FRAGMENT_SHADER);
else if (shaderScript.type == "x-shader/x-vertex")
shader = gl.createShader(gl.VERTEX_SHADER);
else return null;
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0)
alert(gl.getShaderInfoLog(shader));
return shader;
}
var gl, canvas;
var cLoc, size, frames = 0,
timer_fr, time,
n = 8,
k, To = 30,
T, Tp, animation = true;
var orb = [
[.248, 0, .15],
[.27, 0, .2],
[.33, .033, .1],
[.42, .228, .1],
[.27, .564, .1],
[-.162, .78, .1],
[-.534, .612, .1],
[-.726, .3, .1],
[-.75, .0, .05],
[.248, 0, .15]
];
function webGLStart() {
canvas = document.getElementById("canned");
size = Math.min(window.innerWidth, window.innerHeight) - 35;
canvas.width = size;
canvas.height = size;
if (!window.WebGLRenderingContext) {
alert("Your browser does not support WebGL. See http://get.webgl.org");
return;
}
try {
gl = canvas.getContext("experimental-webgl");
} catch (e) {}
if (!gl) {
alert("Can't get WebGL");
return;
}
gl.viewport(0, 0, size, size);
var prog = gl.createProgram();
gl.attachShader(prog, getShader(gl, "shader-vs"));
gl.attachShader(prog, getShader(gl, "shader-fs"));
gl.linkProgram(prog);
gl.useProgram(prog);
var posAtrLoc = gl.getAttribLocation(prog, "vPos");
gl.enableVertexAttribArray(posAtrLoc);
var posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
var vertices = new Float32Array([-1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(posAtrLoc, 3, gl.FLOAT, false, 0, 0);
cLoc = gl.getUniformLocation(prog, "c");
gl.uniform2f(gl.getUniformLocation(prog, "scale"), size / 2, size / 3);
time = new Date().getTime();
k = 0;
Tp = -1;
T = time / 1000 + orb[k][2] * To;
timer_fr = setInterval(fr, 500);
anim();
canvas.resize = function () {
var size = Math.min(window.innerWidth, window.innerHeight) - 35;
canvas.width = size;
canvas.height = size;
gl.uniform2f(gl.getUniformLocation(prog, "scale"), size / 2, size / 3);
gl.viewport(0, 0, size, size);
draw();
}
}
function anim() {
var tim = new Date().getTime() / 1000;
var a = (T - tim) / (To * orb[k][2]);
gl.uniform2f(cLoc, orb[k][0] * a + orb[k + 1][0] * (1 - a),
orb[k][1] * a + orb[k + 1][1] * (1 - a));
draw();
if (tim > T) {
k++;
if (k > n) k = 0;
T += orb[k][2] * To;
}
frames++;
if (animation) requestAnimationFrame(anim);
}
function draw() {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function setT(v) {
To = v.valueOf();
}
function run(v) {
if (animation) {
animation = false;
Tp = new Date().getTime() / 1000;
document.getElementById('runBtn').value = "Run ";
} else {
animation = true;
if (Tp > 0) {
T += new Date().getTime() / 1000 - Tp;
Tp = -1;
}
anim();
document.getElementById('runBtn').value = "Stop";
}
}
function fr() {
var ti = new Date().getTime();
var fps = Math.round(1000 * frames / (ti - time));
document.getElementById("framerate").value = fps;
frames = 0;
time = ti;
}
</script>
<br>T
<input size="2" value="30" onchange="setT( this.value )">sec
<input type="button" onclick="run()" value="Stop" size="1" id="runBtn">fps
<input size="2" id="framerate">
<br>Julia sets animation (canvas is matched to the browser window and you can change period of animation <i>T</i>). Simplified remake of the Java based
Julia Orbit trip.
<i>C</i> is moved near the main cardioid of the Mandelbrot set.
<hr>WebGL Demos
<i>updated</i> 18 August 2010
You cant use the "debugger" statement in glsl shader code.
The "debugger" statement is an extension by the developer tools and is only valid in javascript.