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.
Related
I am trying to adapt a webgl code that generates firework explositions, where projectiles fade out from canvas by means of painting over with 0.1 alpha (as I understand it).
However, this technique somehow doesn't work for the explosition of white color.
You can see that white projectiles leave traces and never fully disappear, while other colors work fine (or maybe they also don't work, but I don't see the traces on black background).
Can somebody help me understand what is happening here?
// from https://codepen.io/towc/pen/oBvYEL
var gl = c.getContext('webgl2', {preserveDrawingBuffer: true})
, w = c.width = window.innerWidth
, h = c.height = window.innerHeight
, webgl = {}
, opts = {
projectileAlpha: .8,
projectileLineWidth: 2,
fireworkAngleSpan: .5,
baseFireworkVel: 3,
gravity: .02,
xFriction: .995,
baseShardVel: 1,
addedShardVel: .2,
fireworks: 1, // 1 firework for each 10x10 pixels,
baseShardsParFirework: 10,
addedShardsParFirework: 10,
shardFireworkVelMultiplier: .3
}
// updated to use WebGL2 and GL ES 3.0
// pass full color instead of just hue
// remove moving projectile, keep only the explosion
webgl.vertexShaderSource = `#version 300 es
uniform int u_mode;
uniform vec2 u_res;
in vec4 a_data;
out vec4 v_color;
vec3 c2rgb(int color_int){
int r = color_int >> 16;
int g = color_int >> 8 & 0xff;
int b = color_int & 0xff;
return vec3(r/255, g/255, b/255);
}
void clear(){
gl_Position = vec4( a_data.xy, 0, 1 );
v_color = vec4( 0, 0, 0, a_data.w );
}
void draw(){
gl_Position = vec4( vec2( 1, -1 ) * ( ( a_data.xy / u_res ) * 2. - 1. ), 0, 1 );
v_color = vec4( 1, 1, 1, a_data.w );
}
void main(){
if( u_mode == 0 )
draw();
else
clear();
}
`;
webgl.fragmentShaderSource = `#version 300 es
precision mediump float;
in vec4 v_color;
out vec4 fragColor;
void main(){
fragColor = v_color;
}
`;
webgl.vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(webgl.vertexShader, webgl.vertexShaderSource);
gl.compileShader(webgl.vertexShader);
if (!gl.getShaderParameter(webgl.vertexShader, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(webgl.vertexShader));
webgl.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(webgl.fragmentShader, webgl.fragmentShaderSource);
gl.compileShader(webgl.fragmentShader);
if (!gl.getShaderParameter(webgl.fragmentShader, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(webgl.fragmentShader));
webgl.shaderProgram = gl.createProgram();
gl.attachShader(webgl.shaderProgram, webgl.vertexShader);
gl.attachShader(webgl.shaderProgram, webgl.fragmentShader);
gl.linkProgram(webgl.shaderProgram);
gl.useProgram(webgl.shaderProgram);
webgl.dataAttribLoc = gl.getAttribLocation(webgl.shaderProgram, 'a_data');
webgl.dataBuffer = gl.createBuffer();
gl.enableVertexAttribArray(webgl.dataAttribLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, webgl.dataBuffer);
gl.vertexAttribPointer(webgl.dataAttribLoc, 4, gl.FLOAT, false, 0, 0);
webgl.resUniformLoc = gl.getUniformLocation(webgl.shaderProgram, 'u_res');
webgl.modeUniformLoc = gl.getUniformLocation(webgl.shaderProgram, 'u_mode');
gl.viewport(0, 0, w, h);
gl.uniform2f(webgl.resUniformLoc, w, h);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
gl.lineWidth(opts.projectileLineWidth);
webgl.data = [];
webgl.clear = function () {
gl.uniform1i(webgl.modeUniformLoc, 1);
let a = .1;
webgl.data = [
-1, -1, 0, 0.1,
1, -1, 0, 0.1,
-1, 1, 0, 0.1,
-1, 1, 0, 0.1,
1, -1, 0, 0.1,
1, 1, 0, 0.1
];
webgl.draw(gl.TRIANGLES);
gl.uniform1i(webgl.modeUniformLoc, 0);
webgl.data.length = 0;
}
webgl.draw = function (glType) {
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(webgl.data), gl.STATIC_DRAW);
gl.drawArrays(glType, 0, webgl.data.length / 4);
}
let fireworks = []
, tick = 0
, sins = []
, coss = []
, maxShardsParFirework = opts.baseShardsParFirework + opts.addedShardsParFirework
, tau = 6.283185307179586476925286766559;
for (let i = 0; i < maxShardsParFirework; ++i) {
sins[i] = Math.sin(tau * i / maxShardsParFirework);
coss[i] = Math.cos(tau * i / maxShardsParFirework);
}
function Firework() {
this.reset();
this.shards = [];
for (let i = 0; i < maxShardsParFirework; ++i)
this.shards.push(new Shard(this));
}
Firework.prototype.reset = function () {
let angle = -Math.PI / 2 + (Math.random() - .5) * opts.fireworkAngleSpan
, vel = opts.baseFireworkVel * Math.random();
this.mode = 0;
this.vx = vel * Math.cos(angle);
this.vy = vel * Math.sin(angle);
this.x = Math.random() * w;
this.y = Math.random() * h;
this.hue = 0;
let ph = this.hue
, px = this.x
, py = this.y;
webgl.data.push(
px, py, ph, opts.projectileAlpha * .2,
this.x, this.y, this.hue, opts.projectileAlpha * .2);
}
Firework.prototype.step = function () {
if (this.mode === 0) {
this.mode = 1;
this.shardAmount = opts.baseShardsParFirework + opts.addedShardsParFirework * Math.random() | 0;
let baseAngle = Math.random() * tau
, x = Math.cos(baseAngle)
, y = Math.sin(baseAngle)
, sin = sins[this.shardAmount]
, cos = coss[this.shardAmount];
for (let i = 0; i < this.shardAmount; ++i) {
let vel = opts.baseShardVel + opts.addedShardVel * Math.random();
this.shards[i].reset(x * vel, y * vel)
let X = x;
x = x * cos - y * sin;
y = y * cos + X * sin;
}
}
if (this.mode === 1) {
this.ph = this.hue
this.hue = 0;
let allDead = true;
for (let i = 0; i < this.shardAmount; ++i) {
let shard = this.shards[i];
if (!shard.dead) {
shard.step();
allDead = false;
}
}
if (allDead)
this.reset();
}
}
function Shard(parent) {
this.parent = parent;
}
Shard.prototype.reset = function (vx, vy) {
this.x = this.parent.x;
this.y = this.parent.y;
this.vx = this.parent.vx * opts.shardFireworkVelMultiplier + vx;
this.vy = this.parent.vy * opts.shardFireworkVelMultiplier + vy;
this.starty = this.y;
this.dead = false;
this.tick = 1;
}
Shard.prototype.step = function () {
this.tick += .05;
let px = this.x
, py = this.y;
this.x += this.vx *= opts.xFriction;
this.y += this.vy += opts.gravity;
var proportion = 1 - (this.y - this.starty) / (h - this.starty);
webgl.data.push(
px, py, this.parent.ph, opts.projectileAlpha / this.tick,
this.x, this.y, this.parent.hue, opts.projectileAlpha / this.tick);
if (this.y > h)
this.dead = true;
}
function anim() {
window.requestAnimationFrame(anim);
webgl.clear();
++tick;
if (fireworks.length < opts.fireworks)
fireworks.push(new Firework);
fireworks.map(function (firework) {
firework.step();
});
webgl.draw(gl.LINES);
}
anim();
window.addEventListener('resize', function () {
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
gl.viewport(0, 0, w, h);
gl.uniform2f(webgl.resUniformLoc, w, h);
});
body {
overflow: hidden;
margin:0;
}
#c {
position: absolute;
top: 0;
left: 0;
background-color: #111;
}
<canvas id="c"> </canvas>
I have been trying to create a full circle using the triangle fan approach. However, I've tried increasing the number of fans from 80 to 360. Then I tried increasing it to 500, 5000, 50000. It disappears at 50000 only because the slice is so small... I am wondering how I can fill in that missing slice.
Here is the code I am working with:
// RotatingTriangle.js (c) 2012 matsuda
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'uniform mat4 u_ModelMatrix;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// Write the positions of vertices to a vertex shader
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// Specify the color for clearing <canvas>
gl.clearColor(0, 0, 0, 1);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw the rectangle
gl.drawArrays(gl.TRIANGLE_FAN, 0, n);
}
function initVertexBuffers(gl) {
var circle = {x: 0, y:0, r: 0.75};
var ATTRIBUTES = 2;
var numFans = 64;
var degreePerFan = (2* Math.PI) / numFans;
var vertexData = [
0.0, 0.0
];
// updated here, but the problem still persists
for(var i = 0; i <= numFans; i++) {
var index = 2 + i*2;
var angle = degreePerFan * (i+1);
//console.log(angle)
vertexData[index] = Math.cos(angle) * 0.5;
vertexData[index + 1] = Math.sin(angle) * 0.5;
}
//console.log(vertexData);
var vertexDataTyped = new Float32Array(vertexData);
// Create a buffer object
var vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// Bind the buffer object to target
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// Write date into the buffer object
gl.bufferData(gl.ARRAY_BUFFER, vertexDataTyped, gl.STATIC_DRAW);
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
// Assign the buffer object to a_Position variable
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// Enable the assignment to a_Position variable
gl.enableVertexAttribArray(a_Position);
return numFans;
}
Picture of missing slice
// SOLUTION
for(var i = 0; i <= numFans; i++) {
var index = i*2; <--- (used to be 'var index = 2 + i*2;')
var angle = degreePerFan * (i+1);
//console.log(angle)
vertexData[index] = Math.cos(angle) * 0.5;
vertexData[index + 1] = Math.sin(angle) * 0.5;
}
I cannot reproduce the issue, however the generation of the vertices is not correct. The index of the 2nd vertex coordinate is 0.0, 0.0:
var index = 2*3 + i*2;
var index = 2 + i*2;
Vertex generation:
var vertexData = [
0.0, 0.0,
];
for(var i = 0; i <= numFans; i++) {
var index = 2 + i*2;
var angle = degreePerFan * i;
vertexData[index] = Math.cos(angle) * 0.5;
vertexData[index + 1] = Math.sin(angle) * 0.5;
}
I have seen that it is possible to create a sticky image effect on hover using three.js which inspired me with an idea of doing this effect over a video.
Initially, I converted the video to a gif hover the gif was stuck on the first frame. I then tried canvid.js which uses loops frames instead of video. Finally I tried to pause the video an take an image of the frame that is hovered on and then use the effect on the image.
/* S C R E E N S H O T */
$( document ).ready(function() {
$("#heroVideo").get(0).play();
var video = document.getElementById("heroVideo");
var currentTime = video.currentTime
var imgUrl = "./img/overlay.png";
$(".hoverZone").hover(function(){
$("img").css("opacity","1");
function getVideoImage(path, secs, callback) {
var me = this, video = document.createElement('video');
video.onloadedmetadata = function() {
if ('function' === typeof secs) {
secs = secs(this.duration);
}
this.currentTime = Math.min(Math.max(0, (secs < 0 ? this.duration : 0) + secs), this.duration);
};
video.onseeked = function(e) {
var canvas = document.createElement('canvas');
canvas.height = video.videoHeight;
canvas.width = video.videoWidth;
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
var img = new Image();
img.src = canvas.toDataURL();
callback.call(me, img, this.currentTime, e);
};
video.onerror = function(e) {
callback.call(me, undefined, undefined, e);
};
video.src = path;
}
function showImageAt(currentTime) {
var current;
getVideoImage(
'./img/Morph.mp4',
function(currentTime) {
current = video.currentTime;
return current;
},
function(img, secs, event) {
if (event.type == 'seeked') {
var li = document.createElement('li');
li.appendChild(img);
document.getElementById('olFrames').appendChild(li);
var imgUrl = $('img')[0].src;
console.log(imgUrl);
if (current >= ++secs) {
};
}
}
);
}
showImageAt(currentTime);
}, function(){
$("img").css("opacity","0");
});
/* D I S T O R T I O N */
const imgSize = [1250, 10097];
const vertex = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragment = `
precision highp float;
precision highp int;
uniform sampler2D tWater;
uniform sampler2D tFlow;
uniform float uTime;
varying vec2 vUv;
uniform vec4 res;
void main() {
// R and G values are velocity in the x and y direction
// B value is the velocity length
vec3 flow = texture2D(tFlow, vUv).rgb;
vec2 uv = .5 * gl_FragCoord.xy / res.xy ;
vec2 myUV = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV -= flow.xy * (0.15 * 0.7);
vec2 myUV2 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV2 -= flow.xy * (0.125 * 0.7);
vec2 myUV3 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV3 -= flow.xy * (0.10 * 0.7);
vec3 tex = texture2D(tWater, myUV).rgb;
vec3 tex2 = texture2D(tWater, myUV2).rgb;
vec3 tex3 = texture2D(tWater, myUV3).rgb;
gl_FragColor = vec4(tex.r, tex2.g, tex3.b, 1.0);
}
`;
{
const renderer = new ogl.Renderer({ dpr: 2 });
const gl = renderer.gl;
document.body.appendChild(gl.canvas);
// Variable inputs to control flowmap
let aspect = 1;
const mouse = new ogl.Vec2(-1);
const velocity = new ogl.Vec2();
function resize() {
let a1, a2;
var imageAspect = imgSize[1] / imgSize[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = (window.innerWidth / window.innerHeight) * imageAspect;
a2 = 1;
}
mesh.program.uniforms.res.value = new ogl.Vec4(
window.innerWidth,
window.innerHeight,
a1,
a2
);
renderer.setSize(window.innerWidth, window.innerHeight);
aspect = window.innerWidth / window.innerHeight;
}
const flowmap = new ogl.Flowmap(gl, {
falloff: 1.0, // size of the stamp, percentage of the size
alpha: 0.7, // opacity of the stamp
dissipation: 0.95// affects the speed that the stamp fades. Closer to 1 is slower
});
// Triangle that includes -1 to 1 range for 'position', and 0 to 1 range for 'uv'.
const geometry = new ogl.Geometry(gl, {
position: {
size: 2,
data: new Float32Array([-1, -1, 3, -1, -1, 3])
},
uv: { size: 2, data: new Float32Array([0, 0, 2, 0, 0, 2]) }
});
const texture = new ogl.Texture(gl, {
minFilter: gl.LINEAR,
magFilter: gl.LINEAR
});
const img = new Image();
img.onload = () => (texture.image = img);
img.crossOrigin = "Anonymous";
img.src = imgUrl;
let a1, a2;
var imageAspect = imgSize[1] / imgSize[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = (window.innerWidth / window.innerHeight) * imageAspect;
a2 = 1;
}
const program = new ogl.Program(gl, {
vertex,
fragment,
uniforms: {
uTime: { value: 0 },
tWater: { value: texture },
res: {
value: new ogl.Vec4(window.innerWidth, window.innerHeight, a1, a2)
},
img: { value: new ogl.Vec2(imgSize[0], imgSize[1]) },
// Note that the uniform is applied without using an object and value property
// This is because the class alternates this texture between two render targets
// and updates the value property after each render.
tFlow: flowmap.uniform
}
});
const mesh = new ogl.Mesh(gl, { geometry, program });
window.addEventListener("resize", resize, false);
resize();
// Create handlers to get mouse position and velocity
const isTouchCapable = "ontouchstart" in window;
if (isTouchCapable) {
window.addEventListener("touchstart", updateMouse, false);
window.addEventListener("touchmove", updateMouse, { passive: false });
} else {
window.addEventListener("mousemove", updateMouse, false);
}
let lastTime;
const lastMouse = new ogl.Vec2();
function updateMouse(e) {
e.preventDefault();
if (e.changedTouches && e.changedTouches.length) {
e.x = e.changedTouches[0].pageX;
e.y = e.changedTouches[0].pageY;
}
if (e.x === undefined) {
e.x = e.pageX;
e.y = e.pageY;
}
// Get mouse value in 0 to 1 range, with y flipped
mouse.set(e.x / gl.renderer.width, 1.0 - e.y / gl.renderer.height);
// Calculate velocity
if (!lastTime) {
// First frame
lastTime = performance.now();
lastMouse.set(e.x, e.y);
}
const deltaX = e.x - lastMouse.x;
const deltaY = e.y - lastMouse.y;
lastMouse.set(e.x, e.y);
let time = performance.now();
// Avoid dividing by 0
let delta = Math.max(10.4, time - lastTime);
lastTime = time;
velocity.x = deltaX / delta;
velocity.y = deltaY / delta;
// Flag update to prevent hanging velocity values when not moving
velocity.needsUpdate = true;
}
requestAnimationFrame(update);
function update(t) {
requestAnimationFrame(update);
// Reset velocity when mouse not moving
if (!velocity.needsUpdate) {
mouse.set(-1);
velocity.set(0);
}
velocity.needsUpdate = false;
// Update flowmap inputs
flowmap.aspect = aspect;
flowmap.mouse.copy(mouse);
// Ease velocity input, slower when fading out
flowmap.velocity.lerp(velocity, velocity.len ? 0.15 : 0.1);
flowmap.update();
program.uniforms.uTime.value = t * 0.01;
renderer.render({ scene: mesh });
}
}
});
I'm making a webgl demo with md2 models. I know that it is an old format but I need to use only md2.
I read the documentation for the md2 format.
I watched how this site works:
and used part of it's sources in my program (i saved the author's name in the comments)
I made vertex loading, the model loads great!
But when I'm trying to map the texutre, something strange happens:
First, I thought that it's the problem of fragment shader, but I made an export by using assimp2json, used the data from it and it was drawn as it should draw.
The problem is that assimp2json changed the order vertices in triangles and uvs order, so I can't debug the program using it.
Maybe, someone could help to find the bug, to point to the error in my code?
P. S. since there is no animation, I only use the first frame
The most interesting is that if I will pass unindexed data (just the uvs from the file) it looks more right than the indexed textures:
The problem is that it is wrong in some places, like here:
Full source code and models:
ShaderProgram.js
class ShaderProgram
{
constructor(gl, VSSource, FSSource) // VS - Vertex Shader, FS - Fragment
Shader
{
this.gl = gl;
let vertexShader = this.getShader(VSSource, gl.VERTEX_SHADER);
let fragmentShader = this.getShader(FSSource, gl.FRAGMENT_SHADER);
this.shaderProgram = gl.createProgram();
gl.attachShader(this.shaderProgram, vertexShader);
gl.attachShader(this.shaderProgram, fragmentShader);
gl.linkProgram(this.shaderProgram);
if (!gl.getProgramParameter(this.shaderProgram, gl.LINK_STATUS)) {
alert("Can't load shaders");
}
this.enableAttributes();
this.getUniforms();
}
getShader(source, type)
{
let gl = this.gl;
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("Error compilation: " + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
getUniforms()
{
this.model = gl.getUniformLocation(this.shaderProgram, "model");
this.view = gl.getUniformLocation(this.shaderProgram, "view");
this.projection = gl.getUniformLocation(this.shaderProgram, "projection");
}
enableAttributes()
{
let gl = this.gl;
let shaderProgram = this.shaderProgram;
}
use()
{
this.gl.useProgram(this.shaderProgram);
}
}
Textures.js
function initTexture(filename) {
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
let image = new Image();
image.onload = function() { handleTextureLoaded(image, texture); }
image.src = filename;
return texture;
}
function handleTextureLoaded(image, texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
}
MD2 Import:
// BinaryReader
// Refactored by Vjeux <vjeuxx#gmail.com>
// http://blog.vjeux.com/2010/javascript/javascript-binary-reader.html
// Original
//+ Jonas Raoni Soares Silva
//# http://jsfromhell.com/classes/binary-parser [rev. #1]
BinaryReader = function (data) {
this._buffer = data;
this._pos = 0;
};
BinaryReader.prototype = {
/* Public */
readInt8: function (){ return this._decodeInt(8, true); },
readUInt8: function (){ return this._decodeInt(8, false); },
readInt16: function (){ return this._decodeInt(16, true); },
readUInt16: function (){ return this._decodeInt(16, false); },
readInt32: function (){ return this._decodeInt(32, true); },
readUInt32: function (){ return this._decodeInt(32, false); },
readFloat: function (){ return this._decodeFloat(23, 8); },
readDouble: function (){ return this._decodeFloat(52, 11); },
readChar: function () { return this.readString(1); },
readString: function (length) {
this._checkSize(length * 8);
var result = this._buffer.substr(this._pos, length);
this._pos += length;
return result;
},
seek: function (pos) {
this._pos = pos;
this._checkSize(0);
},
getPosition: function () {
return this._pos;
},
getSize: function () {
return this._buffer.length;
},
/* Private */
_decodeFloat: function(precisionBits, exponentBits)
{
return this._decodeFloat2(precisionBits, exponentBits);
var length = precisionBits + exponentBits + 1;
var size = length >> 3;
this._checkSize(length);
var bias = Math.pow(2, exponentBits - 1) - 1;
var signal = this._readBits(precisionBits + exponentBits, 1, size);
var exponent = this._readBits(precisionBits, exponentBits, size);
var significand = 0;
var divisor = 2;
var curByte = length + (-precisionBits >> 3) - 1;
do
{
var byteValue = this._readByte(++curByte, size);
var startBit = precisionBits % 8 || 8;
var mask = 1 << startBit;
while (mask >>= 1)
{
if (byteValue & mask)
{
significand += 1 / divisor;
}
divisor *= 2;
}
} while (precisionBits -= startBit);
this._pos += size;
return exponent == (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity
: (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand
: Math.pow(2, exponent - bias) * (1 + significand) : 0);
},
// I added this because _decodeFloat gave me some real inaccuarate results? -Terry Butler
_decodeFloat2: function(precisionBits, exponentBits)
{
var length = precisionBits + exponentBits + 1;
var value = this._decodeInt(length);
var sign = (value >> 31) & 0x1;
var allZero = 1;
var mantissa = 0.0;
var exponent = 0.0;
// Mantissa
for (var i = 22; i > -1; i--)
{
var test = 1.0 / Math.pow(2, 23-i);
if ((value >> i & 0x1) == 1)
{
mantissa += test;
allZero = 0;
}
}
if (allZero == 0)
mantissa += 1.0;
// Exponent
for (var i = 30; i > 22; i--)
{
var test = Math.pow(2, i - 23);
if ((value >> i & 0x1) == 1)
{
exponent += test;
}
}
exponent -= 127.0;
//
var total = Math.pow(2.0, exponent) * mantissa;
//
if (sign == 1)
{
total *= -1.0;
}
return total;
},
_decodeInt: function(bits, signed){
var x = this._readBits(0, bits, bits / 8), max = Math.pow(2, bits);
var result = signed && x >= max / 2 ? x - max : x;
this._pos += bits / 8;
return result;
},
//shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
_shl: function (a, b){
for (++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1);
return a;
},
_readByte: function (i, size) {
return this._buffer.charCodeAt(this._pos + size - i - 1) & 0xff;
},
_readBits: function (start, length, size) {
var offsetLeft = (start + length) % 8;
var offsetRight = start % 8;
var curByte = size - (start >> 3) - 1;
var lastByte = size + (-(start + length) >> 3);
var diff = curByte - lastByte;
var sum = (this._readByte(curByte, size) >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1);
if (diff && offsetLeft) {
sum += (this._readByte(lastByte++, size) & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight;
}
while (diff) {
sum += this._shl(this._readByte(lastByte++, size), (diff-- << 3) - offsetRight);
}
return sum;
},
_checkSize: function (neededBits) {
if (!(this._pos + Math.ceil(neededBits / 8) < this._buffer.length)) {
throw new Error("Index out of bound");
}
}
};
/**
* #author oosmoxiecode
* based on http://www.terrybutler.co.uk/web-development/html5-canvas-md2- renderer/
* and
* http://tfc.duke.free.fr/coding/md2-specs-en.html
*
* dependant on binaryReader.js
*
* Returns a object like: {string: json_string, info: {status: "Success", faces: 10, vertices: 10, frames: 5 }}
*
**/
// Library is modified for this program by me
function MD2_converter (file) {
var scope = this;
// Create the Binary Reader
var reader = new BinaryReader(file);
// Setup
var header = {};
var frames = [];
var st = [];
var triag = [];
var string = "";
var info = {};
var returnObject = {string: string, info: info};
// Ident and version
header.ident = reader.readString(4);
header.version = reader.readInt32();
// Valid MD2 file?
if (header.ident != "IDP2" || header.version != 8) {
info.status = "Not a valid MD2 file";
return returnObject;
}
// header
header.skinwidth = reader.readInt32(); // texture width
header.skinheight = reader.readInt32(); // texture height
header.framesize = reader.readInt32(); // size in bytes of a frame
header.num_skins = reader.readInt32(); // number of skins
header.num_vertices = reader.readInt32(); // number of vertices per frame
header.num_st = reader.readInt32(); // number of texture coordinates
header.num_tris = reader.readInt32(); // number of triangles
header.num_glcmds = reader.readInt32(); // number of opengl commands
header.num_frames = reader.readInt32(); // number of frames
header.offset_skins = reader.readInt32(); // offset skin data
header.offset_st = reader.readInt32(); // offset texture coordinate data
header.offset_tris = reader.readInt32(); // offset triangle data
header.offset_frames = reader.readInt32(); // offset frame data
header.offset_glcmds = reader.readInt32(); // offset OpenGL command data
header.offset_end = reader.readInt32(); // offset end of file
// faulty size
if (reader.getSize() != header.offset_end) {
info.status = "Corrupted MD2 file";
return returnObject;
}
// texture coordinates
let count = 0;
reader.seek(header.offset_st);
for (var i = 0; i < header.num_st; i++) {
var s = reader.readInt16();
var t = reader.readInt16();
st[i] = {
s: s / header.skinwidth,
t: t / header.skinheight
};
}
reader.seek(header.offset_tris);
for (var i = 0; i < header.num_tris; i++) {
var a = reader.readInt16();
var b = reader.readInt16();
var c = reader.readInt16();
var uva_i = reader.readUInt16();
var uvb_i = reader.readUInt16();
var uvc_i = reader.readUInt16();
triag[i] = {};
triag[i].vertex = [];
triag[i].st = [];
triag[i].vertex[0] = a;
triag[i].vertex[1] = b;
triag[i].vertex[2] = c;
triag[i].st[0] = uva_i;
triag[i].st[1] = uvb_i;
triag[i].st[2] = uvc_i;
}
// frames
reader.seek(header.offset_frames);
for (var f = 0; f < header.num_frames; f++) {
var frame = {};
frame.name = "";
frame.scale = {};
frame.translate = {};
frame.scale.x = reader.readFloat();
frame.scale.y = reader.readFloat();
frame.scale.z = reader.readFloat();
frame.translate.x = reader.readFloat();
frame.translate.y = reader.readFloat();
frame.translate.z = reader.readFloat();
frame.vertices = [];
frame.name = reader.readString(16).replace(/[^a-z0-9]/gi,''); // 4+4+4 4+4+4 (12 + 12) = 24 + 16 = 40
for (var v = 0; v < header.num_vertices; v++) {
var tempX = reader.readUInt8();
var tempY = reader.readUInt8();
var tempZ = reader.readUInt8();
var normal = reader.readUInt8();
var xx = frame.scale.x * tempX + frame.translate.x;
var yy = frame.scale.z * tempZ + frame.translate.z;
var zz = frame.scale.y * tempY + frame.translate.y;
let vertex = [];
vertex[0] = xx;
vertex[1] = yy;
vertex[2] = zz;
frame.vertices.push(vertex);
}
frames.push(frame);
}
let res = {};
res.st = st;
res.triag = triag;
res.frame = [];
for (var i=0; i<frames.length; ++i )
res.frame[i]=frames[i];
res.faces_count = header.num_tris;
res.vertices_count = header.num_vertices;
res.frames_count = header.num_frames;
return res;
}
main.js
let gl;
let shaderProgram;
let firstMouse = true;
let mouseDown = false;
let lastTime = 0;
let deltaTime;
let test;
let lastX= 0;
let lastY = 0;
let DIRS = {
Forward: 0,
Backward: 1,
Left: 2,
Right: 3
};
let KeyCodes =
{
Up: 38,
Down : 40,
Left : 37,
Right : 39,
W : 87,
S : 83,
A : 65,
D : 68,
Q : 81,
E : 69,
PageDown : 34,
PageUp : 33
};
let pressedKeys = [];
let vertexShaderSource = `
precision highp float;
attribute vec3 aPos;
attribute vec2 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
varying vec2 vTexCoord;
void main(void)
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vTexCoord = aTexCoord;
}
`;
let fragmentShaderSource = `
precision highp float;
varying highp vec2 vTexCoord;
uniform sampler2D uSampler;
void main(void)
{
gl_FragColor = texture2D(uSampler, vec2(vTexCoord.s,1.0-vTexCoord.t));
}
`;
let camera;
function initGL()
{
let canvas = document.getElementById("Canvas3D");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
try
{
gl = canvas.getContext("webgl2") || canvas.getContext("experimental-webgl2");
}
catch(e)
{
alert("Your browser don't support WebGL");
}
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
shaderProgram = new ShaderProgram(gl,vertexShaderSource,fragmentShaderSource);
shaderProgram.use();
camera = {
Init(shaderProgram)
{
this.shaderProgram = shaderProgram;
this.pos = vec3.create();
this.up = vec3.create();
this.front = vec3.create();
this.right = vec3.create();
this.WorldUp = vec3.create();
this.pos = [51.656294013839215, 36.9293564686086, -28.23351748054847];
this.front = [-0.7667674915573891, -0.6401096994849556, 0.04824092159224934];
this.up = [-0.6388465891741784, 0.7682835235935235, 0.040192821190338686];
this.WorldUp = [0,1,0];
this.yaw = 176.39999999999998;
this.pitch = -39.8;
this.speed = 25;
this.sensivity = 0.1;
this.zoom = 100;
this.view = mat4.create();
this.projection = mat4.create();
mat4.perspective(this.projection, Math.PI/180.0*this.zoom, gl.viewportWidth/gl.viewportHeight, 0.1, 200);
this.shaderProgram.gl.uniformMatrix4fv(this.shaderProgram.projection, false, this.projection);
this.updateCameraVectors();
this.updateView();
},
updateView: function()
{
let tmp = vec3.create();
vec3.add(tmp, this.pos, this.front);
mat4.lookAt(this.view, this.pos, tmp, this.up);
this.shaderProgram.gl.uniformMatrix4fv(this.shaderProgram.view, false, this.view);
},
processKeyboard(deltaTime)
{
let velocity = this.speed * deltaTime;
let tmp = vec3.create();
if (pressedKeys[KeyCodes.W] || pressedKeys[KeyCodes.Up]) {
vec3.scale(tmp, this.front, velocity);
vec3.add(this.pos,this.pos,tmp);
}
if (pressedKeys[KeyCodes.S] || pressedKeys[KeyCodes.Down]) {
velocity *=(-1);
vec3.scale(tmp, this.front, velocity);
vec3.add(this.pos,this.pos,tmp);
}
if (pressedKeys[KeyCodes.A] || pressedKeys[KeyCodes.Left]) {
velocity *=(-1);
vec3.scale(tmp, this.right, velocity);
vec3.add(this.pos,this.pos,tmp);
}
if (pressedKeys[KeyCodes.D] || pressedKeys[KeyCodes.Right]) {
vec3.scale(tmp, this.right, velocity);
vec3.add(this.pos,this.pos,tmp);
}
},
processMouseMovement(xoffset, yoffset)
{
this.yaw += (xoffset*this.sensivity);
this.pitch += (yoffset*this.sensivity);
if (this.pitch > 89.0)
this.pitch = 89.0;
if (this.pitch < -89.0)
this.pitch = -89.0;
this.updateCameraVectors();
},
updateCameraVectors()
{
let ToRads = Math.PI/180;
let yaw = this.yaw;
let pitch = this.pitch;
this.front[0] = Math.cos(ToRads*yaw) * Math.cos(ToRads*pitch);
this.front[1] = Math.sin(ToRads*pitch);
this.front[2] = Math.sin(ToRads*yaw) * Math.cos(ToRads*pitch);
vec3.normalize(this.front, this.front);
vec3.cross(this.right, this.front, this.WorldUp);
vec3.normalize(this.right, this.right);
vec3.cross(this.up, this.right, this.front);
vec3.normalize(this.up, this.up);
}
};
function load_binary_resource(url) {
let req = new XMLHttpRequest();
req.open('GET', url, false);
req.overrideMimeType('text/plain; charset=x-user-defined'); // No unicode data
req.send(null);
if (req.status != 200) return '';
return req.responseText;
}
let file = load_binary_resource('../models/rhino/Tris.md2');
let res = MD2_converter(file);
let texture = initTexture("../models/rhino/rhino.png");
let verts = [];
let inds = [];
let uvs = [];
for (let i =0;i<res.frame[0].vertices.length;i++)
{
let vert = res.frame[0].vertices[i];
verts.push(vert[0]);
verts.push(vert[1]);
verts.push(vert[2]);
}
for (let i =0;i<res.triag.length;i++)
{
let triag = res.triag[i];
inds.push(triag.vertex[0]);
inds.push(triag.vertex[1]);
inds.push(triag.vertex[2]);
uvs.push(res.st[triag.st[0]].s);
uvs.push(res.st[triag.st[0]].t);
uvs.push(res.st[triag.st[1]].s);
uvs.push(res.st[triag.st[1]].t);
uvs.push(res.st[triag.st[2]].s);
uvs.push(res.st[triag.st[2]].t);
}
test = new Drawable(shaderProgram, verts, inds, uvs, texture);
camera.Init(shaderProgram);
requestAnimationFrame(gameCycle);
}
function recalculateFPS(gotTime)
{
deltaTime = (gotTime - lastTime)/1000;
lastTime = gotTime;
}
function gameCycle(gotTime)
{
recalculateFPS(gotTime);
gl.clearColor(0,0,0,1);
gl.clear(gl.COLOR_BUFFER_BIT);
camera.processKeyboard(deltaTime);
camera.updateView();
test.draw();
requestAnimationFrame(gameCycle);
}
window.onload=function()
{
initGL();
};
document.onkeydown = function(e)
{
pressedKeys[e.keyCode]=true;
};
document.onkeyup = function(e)
{
pressedKeys[e.keyCode]=false;
};
document.body.onmousedown = function(event)
{
mouseDown = true;
lastX = event.clientX;
lastY = event.clientY;
};
document.body.onmouseup = function(event)
{
mouseDown = false;
};
document.body.onmouseout = function(event)
{
mouseDown = false;
};
document.body.onmousemove = function (e)
{
if (!mouseDown)
return;
let xpos = e.clientX;
let ypos = e.clientY;
if(firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
let xoffset = xpos - lastX;
let yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.processMouseMovement(xoffset,yoffset);
}
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body scroll="no" style="overflow: hidden">
<canvas id="Canvas3D"></canvas>
<script src="../js/glmatrix/dist/gl-matrix.js"></script>
<script src="../js/MD2Import.js"></script>
<script src="../js/ShaderProgram.js"></script>
<script src="../js/Textures.js"></script>
<script src="../js/Drawable.js"></script>
<script src="../js/main.js"></script>
</body>
</html>
Drawable.js
class Drawable {
constructor(shaderProgram, vertices, indices, texCoords, texture) {
this.gl = shaderProgram.gl;
let gl = shaderProgram.gl;
this.shaderProgram = shaderProgram;
shaderProgram.aTexCoord = gl.getAttribLocation(shaderProgram.shaderProgram, "aTexCoord");
shaderProgram.aPos = gl.getAttribLocation(shaderProgram.shaderProgram, "aPos");
this.VAO = gl.createVertexArray();
this.texture = texture;
gl.bindVertexArray(this.VAO);
this.vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.vertexAttribPointer(shaderProgram.aPos, 3, gl.FLOAT, false, 12, 0);
gl.enableVertexAttribArray(shaderProgram.aPos);
this.EBO = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.EBO);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
this.VBOTexCoords = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.VBOTexCoords);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords),gl.STATIC_DRAW);
gl.vertexAttribPointer(shaderProgram.aTexCoord, 2, gl.FLOAT, false, 8, 0);
gl.enableVertexAttribArray(shaderProgram.aTexCoord);
gl.bindVertexArray(null);
this.pos = vec3.create();
this.rot = vec3.create(); // в Градусах
this.scale = vec3.create();
this.pos = [0,0,0];
this.rot = [0,0,0];
this.scale=[1,1,1];
this.vertCount = indices.length;
this.model = mat4.create();
}
updateModel() {
mat4.identity(this.model);
mat4.translate(this.model, this.model, this.pos);
mat4.rotateX(this.model, this.model, Math.PI / 180 * this.rot[0]);
mat4.rotateY(this.model, this.model, Math.PI / 180 * this.rot[1]);
mat4.rotateZ(this.model, this.model, Math.PI / 180 * this.rot[2]);
mat4.scale(this.model, this.model, this.scale);
}
translate(transX, transY, transZ)
{
this.pos[0] += transX;
this.pos[1] += transY;
this.pos[2] += transZ;
}
rotate(rotX, rotY, rotZ)
{
this.rot[0] += rotX;
this.rot[1] += rotY;
this.rot[2] += rotZ;
}
draw() {
this.updateModel();
let gl =this.gl;
gl.uniformMatrix4fv(this.shaderProgram.model, false, this.model);
gl.bindVertexArray(this.VAO);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.uniform1i(gl.getUniformLocation(shaderProgram.shaderProgram, "uSampler"), 0);
//gl.drawArrays(gl.TRIANGLES, 0, this.vertCount);
gl.drawElements(gl.TRIANGLES, this.vertCount,gl.UNSIGNED_SHORT,0);
}
}
Tried using gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true), result:
By the way, it shouldn't work, because I already flipped the textures in the shader:
gl_FragColor = texture2D(uSampler, vec2(vTexCoord.s,1.0 - vTexCoord.t));
rhino.png (textures)
The md2 file: http://dropmefiles.com/zhQFU
The problem solved when I deleted the index buffer and placed the vertices and uvs in one huge buffer. What it could be?
The solution was to delete index buffer and place everything in the vertex buffer. It worked for me
I'm pretty experienced with Javascript but haven't delved much into its advanced graphics capabilities (canvas, webGL, three.js, etc). I want to create a distortion effect kind of like this one, except I'd like to apply it to text instead of an image. Basically I want to have some text that looks like plain HTML at first glance but when the user mouses over it, the text should bend/warp/smear in response.
So far I've found two SO posts that are similar but not exactly what I want: the first is too simple, as I want to warp and bend the text, not just shift it down the page. The second is more interesting, as I have a hunch I'll need to use a library like Three.js to achieve this effect, but I want something 2d, not 3d, and I want to actually warp the "shape" of the text, not just spin it around an axis.
I'm wondering how to create this effect, whether there is a name for the specific effect I want (have had trouble finding good examples online), any good examples, advice, anything really. Thanks in advance!
Many possibilities.
Here is an example of a simple WEBGL 2D texture drawn onto a standard 2D canvas. There is a bit of boilerplate for mouse,canvas,webGL so you will have to pick it apart yourself.
The FX is in the Fragment Shader. Rather than move the texture coords I just mapped a 2D vector field over the image (pretty much randomly made it up as i went) The vectors offset the pixel lookup from the texture. The amount controlled by mouse up and down controls the amount of the FX and the mouse from left to right moves the Phase setting.
Moving mouse to the top of the image reduces the effect amount. Bottom right is at max.
.
The function at the bottom webGLRender sets the fragment shader values and renders the webGl then 2D context drawImage to render to display canvas. The Fragment shader is above that.
As the webGL image is rendered via ctx.draw2D it is easy to resize making the webGL render total display resolution independent. If you have performance issues (image in the mega pixel * 4 + range) you can reduce the input image size
WebGL can not render images that are not from the same domain (tained) unlike 2D canvas webGL requires access to the pixel data to draw textures and thus tained images will make it throw security errors. I have used a 2d canvas rather than an image as I could not find an image the would not taint the canvas.
// boiler plate
const U = undefined;const RESIZE_DEBOUNCE_TIME = 100;
var w,h,cw,ch,canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () { var c,cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c;}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function"){ resizeCount += 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}
}
function debounceResize(){ resizeCount -= 1; if(resizeCount <= 0){ onResize();}}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; mouse.updateBounds(); }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3],
active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}
e.preventDefault();
}
m.updateBounds = function(){
if(m.active){
m.bounds = m.element.getBoundingClientRect();
}
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
m.active = true;
m.updateBounds();
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
m.active = false;
}
}
return mouse;
})();
resizeCanvas();
mouse.start(canvas,true);
window.addEventListener("resize",resizeCanvas);
function display(){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if(webGL !== undefined){
webGLRender();
}
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
var globalTime = new Date().valueOf(); // global to this
// creates vertex and fragment shaders
function createProgramFromScripts( gl, ids) {
var shaders = [];
for (var i = 0; i < ids.length; i += 1) {
var script = shadersSource[ids[i]];
if (script !== undefined) {
var shader = gl.createShader(gl[script.type]);
gl.shaderSource(shader, script.source);
gl.compileShader(shader);
shaders.push(shader);
}else{
throw new ReferenceError("*** Error: unknown script ID : " + ids[i]);
}
}
var program = gl.createProgram();
shaders.forEach((shader) => { gl.attachShader(program, shader); });
gl.linkProgram(program);
return program;
}
// setup simple 2D webGL image processor
var webGL;
function startWebGL(image) {
// Get A WebGL context
webGL = document.createElement("canvas");
webGL.width = image.width;
webGL.height = image.height;
webGL.gl = webGL.getContext("webgl");
var gl = webGL.gl;
var program = createProgramFromScripts(gl, ["VertexShader", "FragmentShader"]);
gl.useProgram(program);
var positionLocation = gl.getAttribLocation(program, "a_position");
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0, 0.0,1.0, 0.0,0.0, 1.0,0.0, 1.0,1.0, 0.0,1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
// lookup uniforms for frag shader
var locs = {}
locs.timer = gl.getUniformLocation(program, "time"); // the time used to control waves
locs.phase = gl.getUniformLocation(program, "phase"); // Sort of phase, moves to attractors around
locs.amount = gl.getUniformLocation(program, "amount"); // Mix amount of effect and flat image
webGL.locs = locs;
gl.uniform2f(resolutionLocation, webGL.width, webGL.height);
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
setRectangle(gl, 0, 0, image.width, image.height);
}
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
function randomInt(range) {
return Math.floor(Math.random() * range);
}
var shadersSource = {
VertexShader : {
type : "VERTEX_SHADER",
source : `
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
void main() {
vec2 zeroToOne = a_position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
v_texCoord = a_texCoord;
}`
},
FragmentShader : {
type : "FRAGMENT_SHADER",
source : `
precision mediump float;
uniform sampler2D u_image;
uniform float time;
uniform float phase;
uniform float amount;
varying vec2 v_texCoord;
vec2 offset;
float dist;
float edge;
float v;
vec2 pos1 = vec2(0.5 + sin(phase * 0.03)*1.3, 0.5 + cos(phase * 0.032)*1.3);
vec2 pos2 = vec2(0.5 + cos(phase * 0.013)*1.3,0.5 + cos(phase*0.012)*1.3);
void main() {
dist = distance(pos1,v_texCoord) * distance(pos2,v_texCoord);
edge = 1. - distance(vec2(0.5,0.5),v_texCoord) / 0.707;
v = time * dist * 0.0001 * edge * phase;
offset = vec2(
v_texCoord.x + sin(v+time) * 0.1 * edge * amount,
v_texCoord.y + cos(v+time) * 0.1 * edge * amount
);
//offset = smoothstep(v_texCoord.x,offset.x,abs(0.5-v_textCoord.x) );
gl_FragColor = texture2D(u_image, offset);
}`
}
}
var md = 0;
var mr = 0;
var mdy = 0;
var mry = 0;
function webGLRender(){
var gl = webGL.gl;
md += (mouse.x / canvas.width - mr) * 0.16;
md *= 0.18;
mr += md;
mdy += (mouse.y - mry) * 0.16;
mdy *= 0.18;
mry += mdy;
gl.uniform1f(webGL.locs.timer, globalTime/100);
gl.uniform1f(webGL.locs.phase, mr * 400);
gl.uniform1f(webGL.locs.amount, ((mry/canvas.height)) * 9);
gl.drawArrays(gl.TRIANGLES, 0, 6);
ctx.drawImage(webGL,0,0, canvas.width, canvas.height);
}
var image = document.createElement("canvas");
image.width = 1024;
image.height = 512;
image.ctx = image.getContext("2d");
image.ctx.font = "192px Arial";
image.ctx.textAlign = "center";
image.ctx.textBaseline = "middle";
image.ctx.lineJoin = "round";
image.ctx.lineWidth = 32;
image.ctx.strokeStyle = "red";
image.ctx.fillStyle = "black";
image.ctx.strokeText("WOBBLE",512,256);
image.ctx.lineWidth = 16;
image.ctx.strokeStyle = "white";
image.ctx.strokeText("WOBBLE",512,256);
image.ctx.fillText("WOBBLE",512,256);
image.ctx.font = "32px Arial";
image.ctx.fillText("Mouse position on image controls wobble",512,32);
image.ctx.fillText("Using WebGL and 2D Canvas",512,512-32);
startWebGL(image);
/*var image = new Image(); // load image
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1"; // MUST BE SAME DOMAIN!!!
image.onload = function() {
startWebGL(image);
}*/