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);
}*/
Related
I would like to simply add a "sky" background in my scene so that it won't cover the objects but just being visible after them.
I've tried to, but I have no idea to do it without applying it over the other objects drawn in the scene.
Unfortunately I've to do all this without using any advanced library such as three.js and so on.
The background I'd like to use is in /Assets/sky.jpg
Here the js file:
var program0;
var program1;
var gl;
var shaderDir;
var baseDir;
var lastUpdateTime;
var boatModel;
var rockModel;
var rock2Model;
var oceanModel;
var object = [];
//attributes and uniforms
var positionAttributeLocation = Array();
var uvAttributeLocation = Array();
var matrixLocation = Array();
var textLocation = Array();
var normalAttributeLocation = Array();
var normalMatrixPositionHandle = Array();
var worldViewMatrixLocation = Array();
var worldViewMatrixLocation_transpose = Array();
var materialDiffColorHandle = Array();
var lightDirectionHandle = Array();
var lightColorHandle = Array();
var ambientLightcolorHandle = Array();
var specularColorHandle = Array();
var specShineHandle = Array();
var vaos = new Array();
var textures = new Array();
var modelStr = Array();
var modelTexture = Array();
//matrices
var viewMatrix;
var perspectiveMatrix;
//lights
//define directional light
var dirLightAlpha = -utils.degToRad(180);
var dirLightBeta = -utils.degToRad(100);
var directionalLight;
var directionalLightColor;
var ambientLight = [0.5, 0.5, 0.5];
var specularColor = [0.0, 0.0, 0.0];
var specShine = 0.0;
//camera
var cx = 0;
var cy = 0;
var cz = 1;
var camAngle = 0;
var camElev = 5;
//boat kinematics
var linearDir = 0;
var linearVel = 0;
var velX = 0;
var velZ = 0;
var maxLinearVel = 0.01;
var linearAcc = 0.0001;
var linearDrag = 0.005;
var turningDir = 0;
var angularVel = 0.0;
var maxAngularVel = 0.2;
var angularAcc = 0.01;
var angularDrag = 0.01;
modelStr[0] = 'Assets/Boat/Boat.obj';
modelStr[1] = 'Assets/Rocks/Rock1/rock1.obj';
modelStr[2] = 'Assets/Rocks/Rock2/Rock_1.obj';
modelStr[3] = 'Assets/ocean-obj/ocean.obj';
//modelStr[3] = 'Assets/ocean2/hdri-ca-sky.obj';
modelTexture[0] = 'Assets/Boat/textures/boat_diffuse.bmp';
modelTexture[1] = 'Assets/Rocks/Rock1/textures/rock_low_Base_Color.png';
modelTexture[2] = 'Assets/Rocks/Rock2/Rock_1_Tex/Rock_1_Base_Color.jpg';
modelTexture[3] = 'Assets/ocean-obj/woter.jpg';
//modelTexture[3] = 'Assets/ocean2/CA-Sky-2016-04-15-11-30-am.jpg';
modelTexture[4] = 'Assets/Sea/sea.jpg'
var nFrame = 0;
/***********************************************************************************************/
class Item {
x; y; z;
Rx; Ry; Rz;
S;
vertices;
normals;
indices;
texCoords;
materialColor;
constructor(x, y, z, Rx, Ry, Rz, S) {
this.x = x;
this.y = y;
this.z = z;
this.Rx = Rx;
this.Ry = Ry;
this.Rz = Rz;
this.S = S;
}
buildWorldMatrix() {
return utils.MakeWorld(this.x, this.y, this.z, this.Rx, this.Ry, this.Rz, this.S);
}
setAttr(objectVertices, objectNormals, objectIndices, objectTexCoords) {
this.vertices = objectVertices;
this.normals = objectNormals;
this.indices = objectIndices;
this.texCoords = objectTexCoords;
}
setMaterialColor(materialColorArray) {
this.materialColor = materialColorArray;
}
}
//objects
var rock = new Item(1.0, -0.5, -3.0, 0.0, 0.0, 0.0, 1.0 / 20.0);
var boat = new Item(0.0, -0.15, 0.0, 90.0, 0.0, 0.0, 1.0 / 1000.0);
var rock2 = new Item(-1.0, -0.4, -3, -30.0, 0.0, 0.0, 1.0 / 10.0);
var ocean = new Item(0.0, -0.02, 0.0, 90.0, 0.0, 0.0, 100.0);
function isPowerOf2(value) {
return (value & (value - 1)) == 0;
}
function main() {
utils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
setFloorCoord();
/* Load corresponding information from the models */
object[0] = boat;
object[1] = rock;
object[2] = rock2;
object[3] = ocean;
boat.setAttr(boatModel.vertices, boatModel.vertexNormals, boatModel.indices, boatModel.textures);
boat.setMaterialColor([1.0, 1.0, 1.0]); // set material color for boat
rock.setAttr(rockModel.vertices, rockModel.vertexNormals, rockModel.indices, rockModel.textures);
rock.setMaterialColor([1.0, 1.0, 1.0]); // set material color for rock
rock2.setAttr(rock2Model.vertices, rock2Model.vertexNormals, rock2Model.indices, rock2Model.textures);
rock2.setMaterialColor([1.0, 1.0, 1.0]);
ocean.setAttr(oceanModel.vertices, oceanModel.vertexNormals, oceanModel.indices, oceanModel.textures);
ocean.setMaterialColor([1.0, 1.0, 1.0]);
directionalLight = [Math.cos(dirLightAlpha) * Math.cos(dirLightBeta),
Math.sin(dirLightAlpha),
Math.cos(dirLightAlpha) * Math.sin(dirLightBeta)
];
directionalLightColor = [1.0, 1.0, 1.0];
/* Retrieve the position of the attributes and uniforms */
getShadersPos()
objectWorldMatrix = Array();
objectWorldMatrix[0] = boat.buildWorldMatrix(); //boat WorldMatrix
objectWorldMatrix[1] = rock.buildWorldMatrix(); //rock WorlMatrix
objectWorldMatrix[2] = rock2.buildWorldMatrix();
objectWorldMatrix[3] = ocean.buildWorldMatrix();
perspectiveMatrix = utils.MakePerspective(90, gl.canvas.width / gl.canvas.height, 0.1, 100.0);
viewMatrix = utils.MakeView(0.0, 1.0, 1.0, 15.0, 0.0);
setBuffers();
drawScene();
}
async function init() {
var path = window.location.pathname;
var page = path.split("/").pop();
baseDir = window.location.href.replace(page, '');
shaderDir = baseDir + "shaders/";
var canvas = document.getElementById("c");
lastUpdateTime = (new Date).getTime();
gl = canvas.getContext("webgl2");
if (!gl) {
document.write("GL context not opened");
return;
}
await utils.loadFiles([shaderDir + 'vs.glsl', shaderDir + 'fs.glsl'], function (shaderText) {
var vertexShader = utils.createShader(gl, gl.VERTEX_SHADER, shaderText[0]);
var fragmentShader = utils.createShader(gl, gl.FRAGMENT_SHADER, shaderText[1]);
program0 = utils.createProgram(gl, vertexShader, fragmentShader);
});
await utils.loadFiles([shaderDir + 'vs_unlit.glsl', shaderDir + 'fs_unlit.glsl'], function (shaderText) {
var vertexShader = utils.createShader(gl, gl.VERTEX_SHADER, shaderText[0]);
var fragmentShader = utils.createShader(gl, gl.FRAGMENT_SHADER, shaderText[1]);
program1 = utils.createProgram(gl, vertexShader, fragmentShader);
});
//###################################################################################
//This loads the obj model in the boatModel variable
var boatObjStr = await utils.get_objstr(baseDir + modelStr[0]);
boatModel = new OBJ.Mesh(boatObjStr);
//###################################################################################
//###################################################################################
//This loads the obj model in the rockModel variable
var rockObjStr = await utils.get_objstr(baseDir + modelStr[1]);
rockModel = new OBJ.Mesh(rockObjStr);
//###################################################################################
//###################################################################################
//This loads the obj model in the rockModel variable
var rock2ObjStr = await utils.get_objstr(baseDir + modelStr[2]);
rock2Model = new OBJ.Mesh(rock2ObjStr);
//###################################################################################
var oceanObjStr = await utils.get_objstr(baseDir + modelStr[3]);
oceanModel = new OBJ.Mesh(oceanObjStr);
initControls(canvas);
main();
}
function getShadersPos() {
positionAttributeLocation[0] = gl.getAttribLocation(program0, "a_position");
uvAttributeLocation[0] = gl.getAttribLocation(program0, "a_uv");
matrixLocation[0] = gl.getUniformLocation(program0, "matrix");
worldViewMatrixLocation[0] = gl.getUniformLocation(program0, "worldviewmatrix");
worldViewMatrixLocation_transpose[0] = gl.getUniformLocation(program0, "worldviewmatrix_t");
textLocation[0] = gl.getUniformLocation(program0, "u_texture");
normalAttributeLocation[0] = gl.getAttribLocation(program0, "inNormal");
normalMatrixPositionHandle[0] = gl.getUniformLocation(program0, 'nMatrix');
materialDiffColorHandle[0] = gl.getUniformLocation(program0, 'mDiffColor');
lightDirectionHandle[0] = gl.getUniformLocation(program0, 'lightDirection');
lightColorHandle[0] = gl.getUniformLocation(program0, 'lightColor');
ambientLightcolorHandle[0] = gl.getUniformLocation(program0, 'ambientLightcolor');
specularColorHandle[0] = gl.getUniformLocation(program0, 'specularColor');
specShineHandle[0] = gl.getUniformLocation(program0, 'SpecShine');
}
function setBuffers() {
for (let i = 0; i < object.length; i++) {
vaos[i] = gl.createVertexArray();
gl.bindVertexArray(vaos[i])
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object[i].vertices), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionAttributeLocation[0]);
gl.vertexAttribPointer(positionAttributeLocation[0], 3, gl.FLOAT, false, 0, 0);
var uvBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object[i].texCoords), gl.STATIC_DRAW);
gl.enableVertexAttribArray(uvAttributeLocation[0]);
gl.vertexAttribPointer(uvAttributeLocation[0], 2, gl.FLOAT, false, 0, 0);
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object[i].indices), gl.STATIC_DRAW);
var normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object[i].normals), gl.STATIC_DRAW);
gl.enableVertexAttribArray(normalAttributeLocation[0]);
gl.vertexAttribPointer(normalAttributeLocation[0], 3, gl.FLOAT, false, 0, 0);
textures[i] = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textures[i]);
image = new Image();
image.crossOrigin = "anonymous";
image.src = baseDir + modelTexture[i];
image.onload = function (texture, image) {
return function () {
gl.activeTexture(gl.TEXTURE0)
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// Check if the image is a power of 2 in both dimensions.
if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
// Yes, it's a power of 2. Generate mips.
gl.generateMipmap(gl.TEXTURE_2D);
} else {
// No, it's not a power of 2. Turn off mips and set wrapping to clamp to edge
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.LINEAR);
}
};
}(textures[i], image);
}
}
function drawObjects() {
for (let i = 0; i < object.length; ++i) {
gl.useProgram(program0);
var viewWorldMatrix = utils.multiplyMatrices(viewMatrix, objectWorldMatrix[i]);
var projectionMatrix = utils.multiplyMatrices(perspectiveMatrix, viewWorldMatrix);
gl.uniformMatrix4fv(matrixLocation[0], gl.FALSE, utils.transposeMatrix(projectionMatrix));
gl.uniformMatrix4fv(worldViewMatrixLocation_transpose[0], gl.FALSE, utils.transposeMatrix(utils.invertMatrix(utils.transposeMatrix(viewWorldMatrix))));
gl.uniformMatrix4fv(worldViewMatrixLocation[0], gl.FALSE, utils.transposeMatrix(viewWorldMatrix));
gl.uniformMatrix4fv(normalMatrixPositionHandle[0], gl.FALSE, utils.transposeMatrix(utils.invertMatrix(utils.transposeMatrix(objectWorldMatrix[i]))));
gl.uniform3fv(materialDiffColorHandle[0], object[i].materialColor);
gl.uniform3fv(lightColorHandle[0], directionalLightColor);
gl.uniform3fv(lightDirectionHandle[0], directionalLight);
gl.uniform3fv(ambientLightcolorHandle[0], ambientLight);
gl.uniform3fv(specularColorHandle[0], specularColor);
gl.uniform1f(specShineHandle[0], specShine);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textures[i]);
gl.uniform1i(textLocation[0], textures[i]);
gl.bindVertexArray(vaos[i]);
gl.drawElements(gl.TRIANGLES, object[i].indices.length, gl.UNSIGNED_SHORT, 0);
}
}
var counter = 0;
function animate(item) {
var currentTime = (new Date).getTime();
if (lastUpdateTime != null) {
boatDynamic(currentTime);
var deltaC = (30 * (currentTime - lastUpdateTime)) / 1000.0;
//item.z += deltaC/100;
//item.Rz += deltaC;
}
/* depending on which object we want to animate we change the worldmatrix of the object */
//objectWorldMatrix[0] = utils.MakeWorld(0.0, item.y, item.z, item.Rx, item.Ry, item.Rz, item.S);
counter += 0.005;
//item.z = counter % 2;
//item.y = counter;
//(0, -1, 2, 45, 0)
//item.z -= 0.002;
viewMatrix = utils.MakeView(cx + item.x, cy + 1, 2 + item.z, camElev, 0);
//<---- la barca si muove verso la z negativa
//item.y += 0.002;
objectWorldMatrix[0] = item.buildWorldMatrix();
//objectWorldMatrix[1] = rock.buildWorldMatrix();
//objectWorldMatrix[2] = rock2.buildWorldMatrix();
lastUpdateTime = currentTime;
}
function drawScene() {
animate(boat);
gl.clearColor(0.85, 0.85, 0.85, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
// DRAW THE OBJECTS IN THE SCENE
drawObjects();
window.requestAnimationFrame(drawScene);
}
//controls
var keys = [];
var vz = 0.0;
var rvy = 0.0;
var keyFunctionDown = function (e) {
if (!keys[e.keyCode]) {
keys[e.keyCode] = true;
switch (e.keyCode) {
case 37: //LEFT ARROW KEY DOWN
turningDir = - 1;
break;
case 39: //RIGHT ARROW KEY DOWN
turningDir = + 1;
break;
case 38: //UP ARROW KEY DOWN
linearDir = + 1;
break;
case 40: //DOWN ARROW KEY DOWN
linearDir = - 1;
break;
//camera controls
case 87:
camElev += 5;
console.log(camElev)
break;
case 83:
camElev -= 5;
console.log(camElev)
break;
}
}
}
var keyFunctionUp = function (e) {
if (keys[e.keyCode]) {
keys[e.keyCode] = false;
switch (e.keyCode) {
case 37: //LEFT ARROW KEY UP
turningDir = 0;
break;
case 39: //RIGHT ARROW KEY UP
turningDir = 0;
break;
case 38: //UP ARROW KEY UP
linearDir = 0;
break;
case 40: //DOWN ARROW KEY DOWN
linearDir = 0;
break;
}
}
}
function initControls(canvas) {
window.addEventListener("keyup", keyFunctionUp, false);
window.addEventListener("keydown", keyFunctionDown, false);
}
function boatDynamic(currentTime) {
//console.log(linearVel);
//boat turning
angularVel += turningDir * angularAcc;
if (Math.abs(angularVel) >= maxAngularVel)
angularVel = Math.sign(angularVel) * maxAngularVel;
//angular velocity degradation
angularVel = angularVel * (1 - angularDrag);
boat.Rx += angularVel;
//boat speed
linearVel += linearDir * linearAcc;
if (Math.abs(linearVel) >= maxLinearVel)
linearVel = Math.sign(linearVel) * maxLinearVel;
//linear vel degradation
linearVel = linearVel * (1 - linearDrag)
//linear velocity axis decomposition
velX = - linearVel * Math.cos(utils.degToRad(boat.Rx));
velZ = - linearVel * Math.sin(utils.degToRad(boat.Rx));
boat.x += velX;
boat.z += velZ;
//simple boat "wobbling" around its y axis, must be implemented better
if (Math.random() > 0.8) {
boat.Ry += Math.sin(utils.degToRad(currentTime)) / 8;
}
}
function dirLightChange(value, type) {
if (type == 'alpha')
dirLightAlpha = -utils.degToRad(value);
else
dirLightBeta = -utils.degToRad(value);
directionalLight = [Math.cos(dirLightAlpha) * Math.cos(dirLightBeta),
Math.sin(dirLightAlpha),
Math.cos(dirLightAlpha) * Math.sin(dirLightBeta)
];
drawObjects();
}
function onColorChange(value, type) {
let result = HEX2RGB(value);
var r = result[0] / 255.0;
var g = result[1] / 255.0;
var b = result[2] / 255.0;
if (type == 'ambient')
ambientLight = [r, g, b];
else if (type == 'directional')
directionalLightColor = [r, g, b];
else if (type == 'material')
boat.setMaterialColor([r, g, b]);
else
specularColor = [r, g, b];
drawObjects();
}
function onSpecShineChange(value) {
specShine = value;
drawObjects();
}
window.onload = init;
Here the repo with the entire project: repo
method 1 easiest
clear the depth buffer
turn off the depth test
draw a plane with your sky texture
turn on the depth test
draw your objects
method 2 (slightly more efficient)
clear the depth buffer
set depth func to LESS
turn on the depth test
draw your opaque objects
set depth func to LEQUAL
draw a plane with your sky texture at Z = 1
draw your transparent objects
Note: drawing a plane at Z = 1 is easiest with a custom shader that just does that.
Example: https://stackoverflow.com/a/52508687/128511
Many old outdated 3D engines try to use the system that draws everything else. In other words they only implement one thing, a loop that draws all objects with a single projection matrix and view matrix, so they have to compute a model matrix that positions the plane so that it just happens to show up at -Z in the current view and the current projection. That's silly IMO.
Another thing slightly less outdated 3D engines do is let each object the draw use a different projection and view matrix. In that case projections and view matrices for the planes can be set to something like (assuming the plane has vertices at Z = 0
view matrix = identity
projection matrix = [
1, 0, 0, 0
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 1, 1,
];
Will move Z to 1.
You then probably want to set the first 1 (width) and second 1 (height) as shown in this answer (same as above)
If it was me though I'd do what modern engines do and use a different shader like the one in the first link above just for drawing an image.
Note: you might also want to look into skyboxes
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 });
}
}
});
Getting more errors again this time, except this time it's on the webgl side instead of the mathy algorithm side.
My previous post was just about drawing a simple 2d recursive tree. The thing I'm trying to do now is to draw a tree at the location of the mouseclick, and with red lines if left click, blue if right. I fixed my previous problem and was able to get the tree to show up in my previous build of the program. However, now a tree doesn't even show up when I click on the canvas. When I console log the array where the points are stored however, all the points seem to be there. I think I'm missing something, but I don't know webgl enough to know what that may be.
I have made a working program that can draw different colored points depending on mouse click, at the mouse position, but I'm still not experienced enough to figure out what I have in that program that is allowing it to work and why this current program isn't able to display anything.
my current program:
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'precision mediump float;\n' +
'uniform vec4 u_FragColor;\n' +
'void main() {\n' +
' gl_FragColor = u_FragColor;\n' +
'}\n';
var m = 0;
function main() {
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var uniform1i = 0;
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;
}
// // Get the storage location of a_Position
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;
}
// Get the storage location of u_FragColor
var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
if (!u_FragColor) {
console.log('Failed to get the storage location of u_FragColor');
return;
}
// Register function (event handler) to be called on a mouse press
canvas.onmousedown = function (ev) { click(ev, gl, canvas, a_Position, u_FragColor) };
// Specify the color for clearing <canvas>
gl.clearColor(1.0, 1.0, 1.0, 1.0);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.LINES, 0, 1, m);
}
function click(ev, gl, canvas, a_Position, u_FragColor) {
var x = ev.clientX; // x coordinate of a mouse pointer
var y = ev.clientY; // y coordinate of a mouse pointer
var rect = ev.target.getBoundingClientRect();
x = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);
if (ev.button == 0) {
var depth = 4;
gl.uniform4f(u_FragColor, 1.0, 0, 0, 1.0);// Red
//red tree, 4 steps, length 50, halved each step
// Write the positions of vertices to a vertex shader
var n = initVertexBuffers(gl, x, y);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
m = n;
}
if (ev.button == 2) {
var depth = 6;
//blue tree, 6 steps, length 40, halved each step
gl.uniform4f(u_FragColor, 0, 0, 1.0, 1.0);// Blue
// Write the positions of vertices to a vertex shader
var n = initVertexBuffers(gl, x, y);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
m = n;
}
}
function initVertexBuffers(gl, x, y) {
let start = [];
let points = createPoints(x, y, 0.4, 4, Math.PI / 2, start);
console.log(points);
var vertices = new Float32Array(points);
let n = points.length / 2; // The number of vertices
// 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, vertices, 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 n;
}
//var points = [x, y];
//var angle = 0;
//var prevPoints = [];
//var prevPointIndex = 0;
function createPoints(x, y, length, depth, angle, points) {
if (depth > 0) {
//draws line
let x2 = x + length * Math.cos(angle);
let y2 = y + length * Math.sin(angle);
points.push(x, y, x2, y2);
//draw left branch;
createPoints(x2, y2, length / 2, depth - 1, angle + Math.PI / 4, points);
//goes back
//points.push(x2, y2);
//draw right branch
createPoints(x2, y2, length / 2, depth - 1, angle - Math.PI / 4, points);
//goes back
//points.push(x2, y2);
//console.log(points);
return points;
}
return;
}
my bets are that I'm missing something in the main or click functions, but I'm posting everything because I'm not 100% sure.
The code you posted only calls gl.drawXXX one time in main so it's never going to draw anything ever again.
You have things set up so when the mouse is pressed click will be called but click never calls gl.drawXXX
Further, every time click is called you make a new buffer with a new set of points. That is not the normal way to use WebGL. The normal way is to setup your points once (once per thing you want to draw) and then use matrices to change position, orientation, scale.
I suggest you read some other tutorials on WebGL. This one kind of does what you're doing now but it follows up with how to change position, orientation, and scale, followed by how to do it with matrices for more flexibility. It also covers drawing multiple things
In any case to fix your code as is you need to draw after changing the vertices
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'precision mediump float;\n' +
'uniform vec4 u_FragColor;\n' +
'void main() {\n' +
' gl_FragColor = u_FragColor;\n' +
'}\n';
var m = 0;
function main() {
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var uniform1i = 0;
var gl = canvas.getContext('webgl');
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;
}
// // Get the storage location of a_Position
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;
}
// Get the storage location of u_FragColor
var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
if (!u_FragColor) {
console.log('Failed to get the storage location of u_FragColor');
return;
}
// Register function (event handler) to be called on a mouse press
canvas.onmousedown = function (ev) { click(ev, gl, canvas, a_Position, u_FragColor) };
}
function click(ev, gl, canvas, a_Position, u_FragColor) {
var x = ev.clientX; // x coordinate of a mouse pointer
var y = ev.clientY; // y coordinate of a mouse pointer
var rect = ev.target.getBoundingClientRect();
x = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);
if (ev.button == 0) {
var depth = 4;
gl.uniform4f(u_FragColor, 1.0, 0, 0, 1.0);// Red
//red tree, 4 steps, length 50, halved each step
// Write the positions of vertices to a vertex shader
var n = initVertexBuffers(gl, x, y);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
m = n;
}
if (ev.button == 2) {
var depth = 6;
//blue tree, 6 steps, length 40, halved each step
gl.uniform4f(u_FragColor, 0, 0, 1.0, 1.0);// Blue
// Write the positions of vertices to a vertex shader
var n = initVertexBuffers(gl, x, y);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
m = n;
}
// Specify the color for clearing <canvas>
gl.clearColor(1.0, 1.0, 1.0, 1.0);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.LINES, 0, m);
}
function initVertexBuffers(gl, x, y) {
let start = [];
let points = createPoints(x, y, 0.4, 4, Math.PI / 2, start);
console.log(points);
var vertices = new Float32Array(points);
let n = points.length / 2; // The number of vertices
// 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, vertices, 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 n;
}
//var points = [x, y];
//var angle = 0;
//var prevPoints = [];
//var prevPointIndex = 0;
function createPoints(x, y, length, depth, angle, points) {
if (depth > 0) {
//draws line
let x2 = x + length * Math.cos(angle);
let y2 = y + length * Math.sin(angle);
points.push(x, y, x2, y2);
//draw left branch;
createPoints(x2, y2, length / 2, depth - 1, angle + Math.PI / 4, points);
//goes back
//points.push(x2, y2);
//draw right branch
createPoints(x2, y2, length / 2, depth - 1, angle - Math.PI / 4, points);
//goes back
//points.push(x2, y2);
//console.log(points);
return points;
}
return;
}
main();
//----
function initShaders(gl, vSrc, fSrc) {
const program = twgl.createProgram(gl, [vSrc, fSrc]);
gl.program = program; // THIS IS EXTREMELY BAD AND WRONG CODE!!!
gl.useProgram(program); // THIS IS ALSO WRONG!
return program;
}
canvas { border: 1px solid black; }
<canvas id="webgl"></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
There are many issues with the code
gl.program is not a thing. Whatever book or tutorial you're learning from that is a horrible, bad, and wrong thing to do. WebGL programs usually have multiple shader programs so a function like initShaders needs to return the created program so that you can call it multiple times with different vertex shader source and fragment shader source to make multiple programs. It should not hack one created program on to the webgl context where it doesn't belong.
Note: even though I implemented initShaders since you didn't supply it it's clear from your code it was doing gl.program = gl.createProgram. That's nonsense code. It's common to have multiple programs.
calling gl.useProgram in initShaders is also arguably wrong. (It's clear again since it's not called anywhere in your code that your version of initShaders was doing that. Again it makes no sense since in a normal WebGL page you'd have multiple shaders so you'd need to call gl.useProgram multiple times.
The tutorial you're reading looks old as it's concatenating strings for GLSL. The easiest way to make GLSL is to use multi-line template literals
var VSHADER_SOURCE = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
`;
so much easier
It's calling some function getWebGLContext. No need for anything like that. Just use canvas.getContext('webgl')
the call to gl.drawArrays has m in the wrong place. The correct way to call gl.drawArrays is gl.drawArrays(primitiveType, offset, vertexCount). Offset is almost ways 0. There is no 4th parameter.
Please consider reading some better tutorials like the ones linked above.
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
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.