I need a mesh of quads so I can have 200x200 vertices and UV coordinates from 0 to 1 in both X and Y.
I can't write this by hand, any way to generate such a mesh?
You want 200x200 vertices or 200x200 quads?
with indices
var positions = [];
var uvs = [];
var indices = [];
var quadsAcross = 200;
var quadsDown = 200;
for (var y = 0; y <= quadsDown; ++y) {
var v = y / quadsDown;
for (var x = 0; x <= quadsAcross; ++x) {
var u = x / quadsAcross;
positions.push(u, v);
uvs.push(u, v);
}
}
var rowSize = (quadsAcross + 1);
for (var y = 0; y < quadsDown; ++y) {
var rowOffset0 = (y + 0) * rowSize;
var rowOffset1 = (y + 1) * rowSize;
for (var x = 0; x < quadsAcross; ++x) {
var offset0 = rowOffset0 + x;
var offset1 = rowOffset1 + x;
indices.push(offset0, offset0 + 1, offset1);
indices.push(offset1, offset0 + 1, offset1 + 1);
}
}
var positions = [];
var uvs = [];
var indices = [];
var quadsAcross = 200;
var quadsDown = 200;
for (var y = 0; y <= quadsDown; ++y) {
var v = y / quadsDown;
for (var x = 0; x <= quadsAcross; ++x) {
var u = x / quadsAcross;
positions.push(u, v);
uvs.push(u, v);
}
}
var rowSize = (quadsAcross + 1);
for (var y = 0; y < quadsDown; ++y) {
var rowOffset0 = (y + 0) * rowSize;
var rowOffset1 = (y + 1) * rowSize;
for (var x = 0; x < quadsAcross; ++x) {
var offset0 = rowOffset0 + x;
var offset1 = rowOffset1 + x;
indices.push(offset0, offset0 + 1, offset1);
indices.push(offset1, offset0 + 1, offset1 + 1);
}
}
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: { numComponents: 2, data: positions},
uv: { numComponents: 2, data: uvs },
indices: indices,
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
var scale = 2 + (Math.sin(time) * 0.5 + 0.5) * 16;
var uniforms = {
matrix: [
scale, 0, 0, 0,
0, scale, 0, 0,
0, 0, 1, 0,
-1, -1, 0, 1,
],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, gl.LINE_STRIP, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
html, body {
width: 100%;
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vs" type="notjs">
attribute vec4 position;
attribute vec2 uv;
uniform mat4 matrix;
varying vec2 v_uv;
void main() {
gl_Position = matrix * position;
v_uv = uv;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec2 v_uv;
void main() {
gl_FragColor = vec4(v_uv, 0, 1);
}
</script>
<canvas id="c"></canvas>
without indices
var positions = [];
var uvs = [];
var quadsAcross = 200;
var quadsDown = 200;
for (var y = 0; y < quadsDown; ++y) {
var v0 = (y + 0) / quadsDown;
var v1 = (y + 1) / quadsDown;
for (var x = 0; x < quadsAcross; ++x) {
var u0 = (x + 0) / quadsAcross;
var u1 = (x + 1) / quadsAcross;
positions.push(u0, v0, u1, v0, u0, v1);
positions.push(u0, v1, u1, v0, u1, v1);
uvs.push(u0, v0, u1, v0, u0, v1);
uvs.push(u0, v1, u1, v0, u1, v1);
}
}
That's 200x200 quads which is 201x201 vertices. If you want 200x200 vertices change quadsAcross and quadsDown to 199
var positions = [];
var uvs = [];
var quadsAcross = 200;
var quadsDown = 200;
for (var y = 0; y < quadsDown; ++y) {
var v0 = (y + 0) / quadsDown;
var v1 = (y + 1) / quadsDown;
for (var x = 0; x < quadsAcross; ++x) {
var u0 = (x + 0) / quadsAcross;
var u1 = (x + 1) / quadsAcross;
positions.push(u0, v0, u1, v0, u0, v1);
positions.push(u0, v1, u1, v0, u1, v1);
uvs.push(u0, v0, u1, v0, u0, v1);
uvs.push(u0, v1, u1, v0, u1, v1);
}
}
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: { numComponents: 2, data: positions},
uv: { numComponents: 2, data: uvs },
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
var scale = 2 + (Math.sin(time) * 0.5 + 0.5) * 16;
var uniforms = {
matrix: [
scale, 0, 0, 0,
0, scale, 0, 0,
0, 0, 1, 0,
-1, -1, 0, 1,
],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, gl.LINE_STRIP, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
html, body {
width: 100%;
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vs" type="notjs">
attribute vec4 position;
attribute vec2 uv;
uniform mat4 matrix;
varying vec2 v_uv;
void main() {
gl_Position = matrix * position;
v_uv = uv;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec2 v_uv;
void main() {
gl_FragColor = vec4(v_uv, 0, 1);
}
</script>
<canvas id="c"></canvas>
You can use loop to create this vertices array. Simple:
for (var i = 0; i < 200; i++)
for (var j = 0; j < 200; j++)
{
// fill our array here
}
Related
I'm attempting to implement this codepen as a background of my personal website. I have no real knowledge of WebGL, so please bear with me. I temporarily added an event listener to update the width and height of the canvas when the page is resized. I can tell this works because when the bubbles start going out of bounds, they continue going and don't bounce off the edge of the page, so I know it somewhat works the way I want it to. When the fragment shader source is defined it also defines the width and height and I'm not sure how to change those variables after that. I tried redefining, recompiling, and reattaching the fragment shader source with the new widths and heights. This obviously doesn't work because the bubbles do not render past the size of the page when the canvas was created. I'm not sure if i'm even going about this the right way, if so what am I doing wrong? All/any help is appreciated, thank you.
The code I changed:
var canvas = document.createElement("canvas");
var width = canvas.width = window.innerWidth * 0.75;
var height = canvas.height = window.innerHeight * 0.75;
document.body.appendChild(canvas);
var gl = canvas.getContext('webgl');
var mouse = {x: 0, y: 0};
var numMetaballs = 30;
var metaballs = [];
var first = true
window.addEventListener('resize', function(){
width = canvas.width = window.innerWidth * 0.75;
height = canvas.height = window.innerHeight * 0.75;
shaderStuff()
})
function shaderStuff(){
if(!first) {
gl.detachShader(program, gl.getAttachedShaders(program)[1])
}
first = false
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ` + (width >> 0) + `.0;
const float HEIGHT = ` + (height >> 0) + `.0;
uniform vec3 metaballs[` + numMetaballs + `];
void main(){
float x = gl_FragCoord.x;
float y = gl_FragCoord.y;
float sum = 0.0;
for (int i = 0; i < ` + numMetaballs + `; i++) {
vec3 metaball = metaballs[i];
float dx = metaball.x - x;
float dy = metaball.y - y;
float radius = metaball.z;
sum += (radius * radius) / (dx * dx + dy * dy);
}
if (sum >= 0.99) {
gl_FragColor = vec4(mix(vec3(x / WIDTH, y / HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);
return;
}
gl_FragColor = vec4(0, 0, 0, 0);
}
`;
var fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);
gl.attachShader(program, fragmentShader);
}
for (var i = 0; i < numMetaballs; i++) {
var radius = Math.random() * 60 + 10;
metaballs.push({
x: Math.random() * (width - 2 * radius) + radius,
y: Math.random() * (height - 2 * radius) + radius,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
r: radius * 0.75
});
}
var vertexShaderSrc = `
attribute vec2 position;
void main() {
// position specifies only x and y.
// We set z to be 0.0, and w to be 1.0
gl_Position = vec4(position, 0.0, 1.0);
}
`;
var vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
shaderStuff()
gl.linkProgram(program);
gl.useProgram(program);
The whole project https://meatballsjs.000webhostapp.com/
The original https://codepen.io/TC5550/pen/WNNWoaO
The easiest way would be to put all of the background creation code in a function, and call it every time the page is resized.
You will also need to add some code to cause the previous background loops to stop, and you should add some throttling to prevent too many backgrounds to be created at once.
This is somewhat inefficient, but most users don't expect applications to be extremely responsive while they are being resized, and resizing is an infrequent operation.
I added a code snippet, which appears to work, however I could not get my changes to work in codepen. I believe this is because codepen instruments and modifies the code in a certain way that breaks it (jsbin has similar behavior to prevent infinite loops, and to sandbox it). However I tested my changes in just a .html file, and they seemed to work there, so they should work on your site.
On a side note, very cool use of WebGL!
var nextBackgroundId = 1;
var currentBackgroundId = 0;
setupBackground(currentBackgroundId);
window.addEventListener("resize", () => {
var ourBackgroundId = nextBackgroundId++;
currentBackgroundId = ourBackgroundId;
setTimeout(() => {
setupBackground(ourBackgroundId);
}, 100);
});
function setupBackground(ourBackgroundId) {
if (currentBackgroundId !== ourBackgroundId) {
return;
}
var prevCanvas = document.getElementById("blob-canvas");
if (prevCanvas) {
prevCanvas.remove();
}
var canvas = document.createElement("canvas");
canvas.id = "blob-canvas";
var mouse = { x: 0, y: 0 };
canvas.onmousemove = function (e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
}
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
document.body.appendChild(canvas);
var gl = canvas.getContext('webgl');
var numMetaballs = 30;
var metaballs = [];
for (var i = 0; i < numMetaballs; i++) {
var radius = Math.random() * 60 + 10;
metaballs.push({
x: Math.random() * (width - 2 * radius) + radius,
y: Math.random() * (height - 2 * radius) + radius,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
r: radius * 0.75
});
}
var vertexShaderSrc = `
attribute vec2 position;
void main() {
// position specifies only x and y.
// We set z to be 0.0, and w to be 1.0
gl_Position = vec4(position, 0.0, 1.0);
}
`;
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ` + (width >> 0) + `.0;
const float HEIGHT = ` + (height >> 0) + `.0;
uniform vec3 metaballs[` + numMetaballs + `];
void main(){
float x = gl_FragCoord.x;
float y = gl_FragCoord.y;
float sum = 0.0;
for (int i = 0; i < ` + numMetaballs + `; i++) {
vec3 metaball = metaballs[i];
float dx = metaball.x - x;
float dy = metaball.y - y;
float radius = metaball.z;
sum += (radius * radius) / (dx * dx + dy * dy);
}
if (sum >= 0.99) {
gl_FragColor = vec4(mix(vec3(x / WIDTH, y / HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);
return;
}
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
var vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);
var fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
var vertexData = new Float32Array([
-1.0, 1.0, // top left
-1.0, -1.0, // bottom left
1.0, 1.0, // top right
1.0, -1.0, // bottom right
]);
var vertexDataBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
var positionHandle = getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionHandle);
gl.vertexAttribPointer(positionHandle,
2, // position is a vec2
gl.FLOAT, // each component is a float
gl.FALSE, // don't normalize values
2 * 4, // two 4 byte float components per vertex
0 // offset into each span of vertex data
);
var metaballsHandle = getUniformLocation(program, 'metaballs');
loop();
function loop() {
if (currentBackgroundId !== ourBackgroundId) {
return;
}
for (var i = 0; i < numMetaballs; i++) {
var metaball = metaballs[i];
metaball.x += metaball.vx;
metaball.y += metaball.vy;
if (metaball.x < metaball.r || metaball.x > width - metaball.r) metaball.vx *= -1;
if (metaball.y < metaball.r || metaball.y > height - metaball.r) metaball.vy *= -1;
}
var dataToSendToGPU = new Float32Array(3 * numMetaballs);
for (var i = 0; i < numMetaballs; i++) {
var baseIndex = 3 * i;
var mb = metaballs[i];
dataToSendToGPU[baseIndex + 0] = mb.x;
dataToSendToGPU[baseIndex + 1] = mb.y;
dataToSendToGPU[baseIndex + 2] = mb.r;
}
gl.uniform3fv(metaballsHandle, dataToSendToGPU);
//Draw
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(loop);
}
function compileShader(shaderSource, shaderType) {
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
}
return shader;
}
function getUniformLocation(program, name) {
var uniformLocation = gl.getUniformLocation(program, name);
if (uniformLocation === -1) {
throw 'Can not find uniform ' + name + '.';
}
return uniformLocation;
}
function getAttribLocation(program, name) {
var attributeLocation = gl.getAttribLocation(program, name);
if (attributeLocation === -1) {
throw 'Can not find attribute ' + name + '.';
}
return attributeLocation;
}
}
body {
font-family: 'Alatsi', sans-serif;
margin: 0;
overflow: hidden;
background: black;
}
.container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
.title {
font-size: 10vw;
color: white;
}
canvas {
width: 100%;
}
<div class="container">
<span class="title">MEATBALLS</span>
</div>
There are a lot of issues with that codepen.
It's hacking the canvas size instead of letting CSS size the canvas.
In the code the canvas size is set with
var width = canvas.width = window.innerWidth * 0.75;
var height = canvas.height = window.innerHeight * 0.75;
It's arguably best to let the browser size the canvas
html, body {
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
and then ask the browser what size the canvas is and set the canvas's resolution to match
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
It's asking for things are larger than the window so it would get a scrollbar and then hiding the that fact by hiding the scrollbar. It makes no sense. If you don't want a scrollbar don't ask for content that requires a scrollbar.
html, body {
height: 100%;
/* removed overflow: hidden */
}
canvas {
width: 100%;
height: 100%;
display: block;
}
It's using template strings but not actually using them as templates
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ` + (width >> 0) + `.0;
const float HEIGHT = ` + (height >> 0) + `.0;
uniform vec3 metaballs[` + numMetaballs + `];
...
`;
should arguably be
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ${width >> 0}.0;
const float HEIGHT = ${height >> 0}.0;
uniform vec3 metaballs[${numMetaballs}];
...
`;
The main point of using backticks for strings is so you can
use the templating feature ${code}
It's hard coding the width and height
const float WIDTH = ${width >> 0}.0;
const float HEIGHT = ${height >> 0}.0;
should arguably be
uniform float WIDTH;
uniform float HEIGHT;
so they can be set
Metaballs is misspelled as Meatballs (maybe that was intentional)
Here's a new version. Note: anytime the window is resized the metaballs get a random position. If you comment out the call to updateMetaballs after resizing the canvas then they will not get new random positions. Which is better is up to you. The logic for how they bounce is such that any balls that are off the screen after you resize will stay off the screen. You could fix it so they'll head toward the screen and only bounce from the inside back in. The current code is such that on the outside they'll just wobble where they are.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var gl = canvas.getContext('webgl');
var mouse = {x: 0, y: 0};
var numMetaballs = 30;
var metaballs = [];
function updateMetaballs() {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
for (var i = 0; i < numMetaballs; i++) {
var radius = Math.random() * 60 + 10;
metaballs[i] = {
x: Math.random() * (width - 2 * radius) + radius,
y: Math.random() * (height - 2 * radius) + radius,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
r: radius * 0.75
};
}
}
updateMetaballs();
var vertexShaderSrc = `
attribute vec2 position;
void main() {
// position specifies only x and y.
// We set z to be 0.0, and w to be 1.0
gl_Position = vec4(position, 0.0, 1.0);
}
`;
var fragmentShaderSrc = `
precision highp float;
uniform float WIDTH;
uniform float HEIGHT;
#define NUM_METABALLS ${numMetaballs}
uniform vec3 metaballs[NUM_METABALLS];
void main(){
float x = gl_FragCoord.x;
float y = gl_FragCoord.y;
float sum = 0.0;
for (int i = 0; i < NUM_METABALLS; i++) {
vec3 metaball = metaballs[i];
float dx = metaball.x - x;
float dy = metaball.y - y;
float radius = metaball.z;
sum += (radius * radius) / (dx * dx + dy * dy);
}
if (sum >= 0.99) {
gl_FragColor = vec4(mix(vec3(x / WIDTH, y / HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);
return;
}
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
var vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);
var fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
var vertexData = new Float32Array([
-1.0, 1.0, // top left
-1.0, -1.0, // bottom left
1.0, 1.0, // top right
1.0, -1.0, // bottom right
]);
var vertexDataBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
var positionHandle = getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionHandle);
gl.vertexAttribPointer(positionHandle,
2, // position is a vec2
gl.FLOAT, // each component is a float
gl.FALSE, // don't normalize values
2 * 4, // two 4 byte float components per vertex
0 // offset into each span of vertex data
);
var metaballsHandle = getUniformLocation(program, 'metaballs');
var widthHandle = getUniformLocation(program, 'WIDTH');
var heightHandle = getUniformLocation(program, 'HEIGHT');
function resizeCanvasToDisplaySize(canvas) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
canvas.width = width;
canvas.height = height;
}
return needResize;
}
loop();
function loop() {
if (resizeCanvasToDisplaySize(canvas)) {
updateMetaballs();
}
const {width, height} = canvas;
gl.viewport(0, 0, canvas.width, canvas.height);
for (var i = 0; i < numMetaballs; i++) {
var metaball = metaballs[i];
metaball.x += metaball.vx;
metaball.y += metaball.vy;
if (metaball.x < metaball.r || metaball.x > width - metaball.r) metaball.vx *= -1;
if (metaball.y < metaball.r || metaball.y > height - metaball.r) metaball.vy *= -1;
}
var dataToSendToGPU = new Float32Array(3 * numMetaballs);
for (var i = 0; i < numMetaballs; i++) {
var baseIndex = 3 * i;
var mb = metaballs[i];
dataToSendToGPU[baseIndex + 0] = mb.x;
dataToSendToGPU[baseIndex + 1] = mb.y;
dataToSendToGPU[baseIndex + 2] = mb.r;
}
gl.uniform3fv(metaballsHandle, dataToSendToGPU);
gl.uniform1f(widthHandle, canvas.clientWidth);
gl.uniform1f(heightHandle, canvas.clientHeight);
//Draw
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(loop);
}
function compileShader(shaderSource, shaderType) {
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
}
return shader;
}
function getUniformLocation(program, name) {
var uniformLocation = gl.getUniformLocation(program, name);
if (uniformLocation === -1) {
throw 'Can not find uniform ' + name + '.';
}
return uniformLocation;
}
function getAttribLocation(program, name) {
var attributeLocation = gl.getAttribLocation(program, name);
if (attributeLocation === -1) {
throw 'Can not find attribute ' + name + '.';
}
return attributeLocation;
}
canvas.onmousemove = function(e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
}
html, body {
font-family: 'Alatsi', sans-serif;
margin: 0;
background: black;
height: 100%;
}
.container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.title {
font-size: 10vw;
color: white;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
<div class="container">
<span class="title">METABALLS</span>
</div>
If you want to learn WebGL consider these tutorials
I know that in theory you have to first find the coordinates on the height map like (x = width HM / width Terrain * x Terrain) and y coordinate (y = height HM / height Terrain * y Terrain) and after getting the location on the height map, we get the actual height by min_height + (colorVal / (max_color - min_color) * *max_height - min_height) thus returning a Z value for a particular segment.
But how can i actually import the height map and take its parameters? I'm writing in javascript with no additional libraries (three,babylon).
edit
Currently i'm hardcoding the z values based on x and y ranges:
Plane.prototype.modifyGeometry=function(x,y){
if((x>=0&&x<100)&&(y>=0&&y<100)){
return 25;
}
else if((x>=100&&x<150)&&(y>=100&&y<150)){
return 20;
}
else if((x>=150&&x<200)&&(y>=150&&y<200)){
return 15;
}
else if((x>=200&&x<250)&&(y>=200&&y<250)){
return 10;
}
else if((x>=250&&x<300)&&(y>=250&&y<300)){
return 5;
}
else{
return 0;
}
** edit **
i can get a flat grid (or with randomly generated heights), but as soon as i add the image data, i get a blank screen(no errors though). Here is the code (i changed it up a bit):
var gl;
var canvas;
var img = new Image();
// img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
var gridWidth;
var gridDepth;
var gridPoints = [];
var gridIndices = [];
var rowOff = 0;
var rowStride = gridWidth + 1;
var numVertices = (gridWidth * 2 * (gridDepth + 1)) + (gridDepth * 2 * (gridWidth + 1));
//creating plane
function generateHeightPoints() {
var ctx = document.createElement("canvas").getContext("2d"); //using 2d canvas to read image
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
gridWidth = imgData.width - 1;
gridDepth = imgData.height - 1;
for (var z = 0; z <= gridDepth; ++z) {
for (var x = 0; x <= gridWidth; ++x) {
var offset = (z * imgData.width + x) * 4;
var height = imgData.data[offset] * 10 / 255;
gridPoints.push(x, height, z);
}
}
}
function generateIndices() {
for (var z = 0; z<=gridDepth; ++z) {
rowOff = z*rowStride;
for(var x = 0; x<gridWidth; ++x) {
gridIndices.push(rowOff+x,rowOff+x+1);
}
}
for (var x = 0; x<=gridWidth; ++x) {
for(var z = 0; z<gridDepth; ++z) {
rowOff = z * rowStride;
gridIndices.push(rowOff+x,rowOff+x+rowStride);
}
}
}
//init
//program init
window.onload = function init()
{
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
generateHeightPoints();
generateIndices();
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints),
gl.STATIC_DRAW);
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices),
gl.STATIC_DRAW);
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
var matrixLoc = gl.getUniformLocation(program, 'matrix');
var m4 = twgl.m4;
var projection = m4.perspective(60 * Math.PI / 180, gl.canvas.clientWidth /
gl.canvas.clientHeight, 0.1, 100);
var cameraPosition = [-18, 15, -10];
var target = [gridWidth / 2, -10, gridDepth / 2];
var up = [0, 1, 0];
var camera = m4.lookAt(cameraPosition, target, up);
var view = m4.inverse(camera);
var mat = m4.multiply(projection, view);
gl.uniformMatrix4fv(matrixLoc, false, mat);
render();
}
function render() {
gl.drawElements(gl.LINES, numVertices, gl.UNSIGNED_SHORT, 0);
gl.drawElements(gl.LINES,numVertices,gl.UNSIGNED_SHORT,0);
requestAnimFrame(render);
}
You basically just make a grid of points and change the Z values.
First a flat grid
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision highp float;
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const matrixLoc = gl.getUniformLocation(program, 'matrix');
const gridWidth = 40;
const gridDepth = 40;
const gridPoints = [];
for (let z = 0; z <= gridDepth; ++z) {
for (let x = 0; x <= gridWidth; ++x) {
gridPoints.push(x, 0, z);
}
}
const gridIndices = [];
const rowStride = gridWidth + 1;
// x lines
for (let z = 0; z <= gridDepth; ++z) {
const rowOff = z * rowStride;
for (let x = 0; x < gridWidth; ++x) {
gridIndices.push(rowOff + x, rowOff + x + 1);
}
}
// z lines
for (let x = 0; x <= gridWidth; ++x) {
for (let z = 0; z < gridDepth; ++z) {
const rowOff = z * rowStride;
gridIndices.push(rowOff + x, rowOff + x + rowStride);
}
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), gl.STATIC_DRAW);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const m4 = twgl.m4;
const projection = m4.perspective(
60 * Math.PI / 180, // field of view
gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
0.1, // near
100, // far
);
const cameraPosition = [-gridWidth / 8, 10, -gridDepth / 8];
const target = [gridWidth / 2, -10, gridDepth / 2];
const up = [0, 1, 0];
const camera = m4.lookAt(cameraPosition, target, up);
const view = m4.inverse(camera);
const mat = m4.multiply(projection, view);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(
positionLoc, // location
3, // size
gl.FLOAT, // type
false, // normalize
0, // stride
0, // offset
);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLoc, false, mat);
const numVertices = (gridWidth * 2 * (gridDepth + 1)) +
(gridDepth * 2 * (gridWidth + 1));
gl.drawElements(
gl.LINES, // primitive type
numVertices, //
gl.UNSIGNED_SHORT, // type of indices
0, // offset
);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Grid with height map.
Here's a gray scale image we can use as a height map
Read it by loading a img, drawing to a 2D canvas, calling getImageData. Then just read the red values for height.
const img = new Image();
img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
function run() {
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision highp float;
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const matrixLoc = gl.getUniformLocation(program, 'matrix');
// use a canvas 2D to read the image
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const gridWidth = imgData.width - 1;
const gridDepth = imgData.height - 1;
const gridPoints = [];
for (let z = 0; z <= gridDepth; ++z) {
for (let x = 0; x <= gridWidth; ++x) {
const offset = (z * imgData.width + x) * 4;
// height 0 to 10
const height = imgData.data[offset] * 10 / 255;
gridPoints.push(x, height, z);
}
}
const gridIndices = [];
const rowStride = gridWidth + 1;
// x lines
for (let z = 0; z <= gridDepth; ++z) {
const rowOff = z * rowStride;
for (let x = 0; x < gridWidth; ++x) {
gridIndices.push(rowOff + x, rowOff + x + 1);
}
}
// z lines
for (let x = 0; x <= gridWidth; ++x) {
for (let z = 0; z < gridDepth; ++z) {
const rowOff = z * rowStride;
gridIndices.push(rowOff + x, rowOff + x + rowStride);
}
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), gl.STATIC_DRAW);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const m4 = twgl.m4;
const projection = m4.perspective(
60 * Math.PI / 180, // field of view
gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
0.1, // near
100, // far
);
const cameraPosition = [-10, 10, -10];
const target = [gridWidth / 2, -10, gridDepth / 2];
const up = [0, 1, 0];
const camera = m4.lookAt(cameraPosition, target, up);
const view = m4.inverse(camera);
const mat = m4.multiply(projection, view);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(
positionLoc, // location
3, // size
gl.FLOAT, // type
false, // normalize
0, // stride
0, // offset
);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLoc, false, mat);
const numVertices = (gridWidth * 2 * (gridDepth + 1)) +
(gridDepth * 2 * (gridWidth + 1));
gl.drawElements(
gl.LINES, // primitive type
numVertices, //
gl.UNSIGNED_SHORT, // type of indices
0, // offset
);
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Then instead of making a grid of lines make a grid of triangles. There's lots of ways to do that. You could put 2 triangle per grid square. This code put's 4. You also need to generate normals. I copied the code to generate normals from this article which is fairly generic normal generating code. Being a grid you could make a grid specific normal generator which would be faster since being a grid you know which vertices are shared.
This code is also using twgl because WebGL is too verbose but it should be clear how to do it in plain WebGL from reading the names of the twgl functions.
'use strict';
/* global twgl, m4, requestAnimationFrame, document */
const img = new Image();
img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
function run() {
// use a canvas 2D to read the image
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
function getHeight(offset) {
const v = imgData.data[offset * 4]; // x4 because RGBA
return v * 10 / 255; // 0 to 10
}
// first generate a grid of triangles, at least 2 or 4 for each cell
//
// 2 4
// +----+ +----+
// | /| |\ /|
// | / | | \/ |
// | / | | /\ |
// |/ | |/ \|
// +----+ +----+
//
const positions = [];
const texcoords = [];
const indices = [];
const cellsAcross = imgData.width - 1;
const cellsDeep = imgData.height - 1;
for (let z = 0; z < cellsDeep; ++z) {
for (let x = 0; x < cellsAcross; ++x) {
const base0 = z * imgData.width + x;
const base1 = base0 + imgData.width;
const h00 = getHeight(base0); const h01 = getHeight(base0 + 1);
const h10 = getHeight(base1);
const h11 = getHeight(base1 + 1);
const hm = (h00 + h01 + h10 + h11) / 4;
const x0 = x;
const x1 = x + 1;
const z0 = z;
const z1 = z + 1;
const ndx = positions.length / 3;
positions.push(
x0, h00, z0,
x1, h01, z0,
x0, h10, z1,
x1, h11, z1,
(x0 + x1) / 2, hm, (z0 + z1) / 2,
);
const u0 = x / cellsAcross;
const v0 = z / cellsDeep;
const u1 = (x + 1) / cellsAcross;
const v1 = (z + 1) / cellsDeep;
texcoords.push(
u0, v0,
u1, v0,
u0, v1,
u1, v1,
(u0 + u1) / 2, (v0 + v1) / 2,
);
//
// 0----1
// |\ /|
// | \/4|
// | /\ |
// |/ \|
// 2----3
indices.push(
ndx, ndx + 4, ndx + 1,
ndx, ndx + 2, ndx + 4,
ndx + 2, ndx + 3, ndx + 4,
ndx + 1, ndx + 4, ndx + 3,
);
}
}
const maxAngle = 2 * Math.PI / 180; // make them facetted
const arrays = generateNormals({
position: positions,
texcoord: texcoords,
indices,
}, maxAngle);
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texcoord;
uniform mat4 projection;
uniform mat4 modelView;
varying vec3 v_normal;
varying vec2 v_texcoord;
void main() {
gl_Position = projection * modelView * position;
v_normal = mat3(modelView) * normal;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec3 v_normal;
varying vec2 v_texcoord;
varying float v_modelId;
void main() {
vec3 lightDirection = normalize(vec3(1, 2, -3)); // arbitrary light direction
float l = dot(lightDirection, normalize(v_normal)) * .5 + .5;
gl_FragColor = vec4(vec3(0,1,0) * l, 1);
}
`;
// compile shader, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make some vertex data
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) {
time *= 0.001; // seconds
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
const fov = Math.PI * 0.25;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const near = 0.1;
const far = 100;
const projection = m4.perspective(fov, aspect, near, far);
const eye = [0, 10, 25];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
let modelView = m4.yRotate(view, time);
modelView = m4.translate(modelView, imgData.width / -2, 0, imgData.height / -2)
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniforms(programInfo, {
projection,
modelView,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function generateNormals(arrays, maxAngle) {
const positions = arrays.position;
const texcoords = arrays.texcoord;
// first compute the normal of each face
let getNextIndex = makeIndiceIterator(arrays);
const numFaceVerts = getNextIndex.numElements;
const numVerts = arrays.position.length;
const numFaces = numFaceVerts / 3;
const faceNormals = [];
// Compute the normal for every face.
// While doing that, create a new vertex for every face vertex
for (let i = 0; i < numFaces; ++i) {
const n1 = getNextIndex() * 3;
const n2 = getNextIndex() * 3;
const n3 = getNextIndex() * 3;
const v1 = positions.slice(n1, n1 + 3);
const v2 = positions.slice(n2, n2 + 3);
const v3 = positions.slice(n3, n3 + 3);
faceNormals.push(m4.normalize(m4.cross(m4.subtractVectors(v1, v2), m4.subtractVectors(v3, v2))));
}
let tempVerts = {};
let tempVertNdx = 0;
// this assumes vertex positions are an exact match
function getVertIndex(x, y, z) {
const vertId = x + "," + y + "," + z;
const ndx = tempVerts[vertId];
if (ndx !== undefined) {
return ndx;
}
const newNdx = tempVertNdx++;
tempVerts[vertId] = newNdx;
return newNdx;
}
// We need to figure out the shared vertices.
// It's not as simple as looking at the faces (triangles)
// because for example if we have a standard cylinder
//
//
// 3-4
// / \
// 2 5 Looking down a cylinder starting at S
// | | and going around to E, E and S are not
// 1 6 the same vertex in the data we have
// \ / as they don't share UV coords.
// S/E
//
// the vertices at the start and end do not share vertices
// since they have different UVs but if you don't consider
// them to share vertices they will get the wrong normals
const vertIndices = [];
for (let i = 0; i < numVerts; ++i) {
const offset = i * 3;
const vert = positions.slice(offset, offset + 3);
vertIndices.push(getVertIndex(vert));
}
// go through every vertex and record which faces it's on
const vertFaces = [];
getNextIndex.reset();
for (let i = 0; i < numFaces; ++i) {
for (let j = 0; j < 3; ++j) {
const ndx = getNextIndex();
const sharedNdx = vertIndices[ndx];
let faces = vertFaces[sharedNdx];
if (!faces) {
faces = [];
vertFaces[sharedNdx] = faces;
}
faces.push(i);
}
}
// now go through every face and compute the normals for each
// vertex of the face. Only include faces that aren't more than
// maxAngle different. Add the result to arrays of newPositions,
// newTexcoords and newNormals, discarding any vertices that
// are the same.
tempVerts = {};
tempVertNdx = 0;
const newPositions = [];
const newTexcoords = [];
const newNormals = [];
function getNewVertIndex(x, y, z, nx, ny, nz, u, v) {
const vertId =
x + "," + y + "," + z + "," +
nx + "," + ny + "," + nz + "," +
u + "," + v;
const ndx = tempVerts[vertId];
if (ndx !== undefined) {
return ndx;
}
const newNdx = tempVertNdx++;
tempVerts[vertId] = newNdx;
newPositions.push(x, y, z);
newNormals.push(nx, ny, nz);
newTexcoords.push(u, v);
return newNdx;
}
const newVertIndices = [];
getNextIndex.reset();
const maxAngleCos = Math.cos(maxAngle);
// for each face
for (let i = 0; i < numFaces; ++i) {
// get the normal for this face
const thisFaceNormal = faceNormals[i];
// for each vertex on the face
for (let j = 0; j < 3; ++j) {
const ndx = getNextIndex();
const sharedNdx = vertIndices[ndx];
const faces = vertFaces[sharedNdx];
const norm = [0, 0, 0];
faces.forEach(faceNdx => {
// is this face facing the same way
const otherFaceNormal = faceNormals[faceNdx];
const dot = m4.dot(thisFaceNormal, otherFaceNormal);
if (dot > maxAngleCos) {
m4.addVectors(norm, otherFaceNormal, norm);
}
});
m4.normalize(norm, norm);
const poffset = ndx * 3;
const toffset = ndx * 2;
newVertIndices.push(getNewVertIndex(
positions[poffset + 0], positions[poffset + 1], positions[poffset + 2],
norm[0], norm[1], norm[2],
texcoords[toffset + 0], texcoords[toffset + 1]));
}
}
return {
position: newPositions,
texcoord: newTexcoords,
normal: newNormals,
indices: newVertIndices,
};
}
function makeIndexedIndicesFn(arrays) {
const indices = arrays.indices;
let ndx = 0;
const fn = function() {
return indices[ndx++];
};
fn.reset = function() {
ndx = 0;
};
fn.numElements = indices.length;
return fn;
}
function makeUnindexedIndicesFn(arrays) {
let ndx = 0;
const fn = function() {
return ndx++;
};
fn.reset = function() {
ndx = 0;
}
fn.numElements = arrays.positions.length / 3;
return fn;
}
function makeIndiceIterator(arrays) {
return arrays.indices
? makeIndexedIndicesFn(arrays)
: makeUnindexedIndicesFn(arrays);
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/3d-math.js"></script>
<canvas></canvas>
I need to position on screen just below the 3d player the DIVSet in correct X,Y position, but I am not sure how to convert x,y,z into x,y coordinates.
this.player = player => {
const pos = interpolate(player.position, player.movementVector)
resetDivSets()
calcViewMatrix()
mat4Translate(viewMatrix, -pos.x * glScale, -3.1 * glScale, pos.y * glScale)
mat4RotateY(viewMatrix, playerRotation(player, player.drawMovementVector))
gl.uniformMatrix4fv(uVmatrix, false, viewMatrix)
gl.uniform3f(uAmbientColor, 1, 1, 1)
gl.drawElements(gl.TRIANGLES, builtSprites.player.ibCount, gl.UNSIGNED_SHORT, builtSprites.player.ibStart * 2)
// compute a clipspace position
var clipspace = WHAT TO HAVE HERE??;
// divide X and Y by W just like the GPU does.
clipspace[0] /= clipspace[3];
clipspace[1] /= clipspace[3];
// convert from clipspace to pixels
var pixelX = (clipspace[0] * 0.5 + 0.5) * gl.canvas.width;
var pixelY = (clipspace[1] * -0.5 + 0.5) * gl.canvas.height;
addDivSet("Player Name under player object", pixelX , pixelY);
This article covers it
Basically
// compute a clipspace position
var modelRelativePosition = [0, 0, 0, 1]; // NOTE!! w = 1
var clipspace = m4.transformVector(uVmatrix, modelRelativePosition);
// divide X and Y by W just like the GPU does.
clipspace[0] /= clipspace[3];
clipspace[1] /= clipspace[3];
// convert from clipspace to pixels
var pixelX = (clipspace[0] * 0.5 + 0.5) * gl.canvas.width;
var pixelY = (clipspace[1] * -0.5 + 0.5) * gl.canvas.height;
// position the div
div.style.left = Math.floor(pixelX) + "px";
div.style.top = Math.floor(pixelY) + "px";
where transformVector is something like
function transformVector(m, v, dst) {
dst = new Float32Array(4);
for (var i = 0; i < 4; ++i) {
dst[i] = 0.0;
for (var j = 0; j < 4; ++j) {
dst[i] += v[j] * m[j * 4 + i];
}
}
return dst;
}
And if you want to offset in 2d just add that at the bottom
div.style.left = Math.floor(pixelX + offsetX) + "px";
div.style.top = Math.floor(pixelY + offsetY) + "px";
const gl = document.createElement('canvas').getContext('webgl');
// use the identity matrix as a test
var uVmatrix = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
];
// compute a clipspace position
var modelRelativePosition = [0, 0, 0, 1]; // NOTE! w = 1
var clipspace = transformVector(uVmatrix, modelRelativePosition);
// divide X and Y by W just like the GPU does.
clipspace[0] /= clipspace[3];
clipspace[1] /= clipspace[3];
// convert from clipspace to pixels
var pixelX = (clipspace[0] * 0.5 + 0.5) * gl.canvas.width;
var pixelY = (clipspace[1] * -0.5 + 0.5) * gl.canvas.height;
// position the div
//div.style.left = Math.floor(pixelX) + "px";
//div.style.top = Math.floor(pixelY) + "px";
console.log(Math.floor(pixelX) + "px", Math.floor(pixelY) + "px");
function transformVector(m, v, dst) {
dst = new Float32Array(4);
for (var i = 0; i < 4; ++i) {
dst[i] = 0.0;
for (var j = 0; j < 4; ++j) {
dst[i] += v[j] * m[j * 4 + i];
}
}
return dst;
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I'm trying to create a uv-sphere in WebGL for a university project, but I'm having problems declaring the vertices indices correctly (I assume). I'm following this http://www.songho.ca/opengl/gl_sphere.html and I think my code is pretty similar to the one shown there. This is how I'm declaring my vertices/indices/normals/texture coordinates:
this.vertices = [];
this.indices = [];
this.normals = [];
this.texCoords = [];
let horizontalAng = 0;
let horizontalDiff = (2 * Math.PI) / this.horizontalDiv;
let verticalAng = Math.PI / 2;
let verticalDiff = - (Math.PI / this.verticalDiv);
for (var i = 0; i <= this.verticalDiv; i++) {
let cosVert = Math.cos(verticalAng);
let sinVert = Math.sin(verticalAng);
for (var j = 0; j <= this.horizontlDiv; j++) {
let cosHor = Math.cos(horizontalAng);
let sinHor = Math.sin(horizontalAng);
// z = (r * cos(verticalAng)) * cos(horizontalAng)
// x = (r * cos(verticalAng)) * sin(horizontalAng)
// y = r * sin(veritcalAng)
let x = cosVert * sinHor;
let y = sinVert;
let z = cosVert * cosHor;
this.vertices.push(x, y, z);
this.normals.push(x, y, z);
this.texCoords.push(j / this.horizontalDiv);
this.texCoords.push(i / this.verticalDiv);
horizontalAng += horizontalDiff;
}
verticalAng += verticalDiff;
}
for (var i = 0; i < this.verticalDiv; i++) {
k1 = i * (this.horizontalDiv + 1);
k2 = k1 + this.horizontalDiv + 1;
for (var j = 0; j < this.horizontalDiv; j++) {
if (i != 0) {
this.indices.push(k1);
this.indices.push(k2);
this.indices.push(k1 + 1);
}
if (i != (this.verticalDiv - 1)) {
this.indices.push(k1 + 1);
this.indices.push(k2);
this.indices.push(k2 + 1);
}
k1++;
k2++;
}
}
```
The code has several typos
It doesn't declare k1 or k2.
horizontalDiv is mis-spelled in places as horizontlDiv
class Foo {
constructor(horizontalDiv, verticalDiv) {
this.horizontalDiv = horizontalDiv;
this.verticalDiv = verticalDiv;
this.vertices = [];
this.indices = [];
this.normals = [];
this.texCoords = [];
let horizontalAng = 0;
let horizontalDiff = (2 * Math.PI) / this.horizontalDiv;
let verticalAng = Math.PI / 2;
let verticalDiff = -(Math.PI / this.verticalDiv);
for (var i = 0; i <= this.verticalDiv; i++) {
let cosVert = Math.cos(verticalAng);
let sinVert = Math.sin(verticalAng);
for (var j = 0; j <= this.horizontalDiv; j++) {
let cosHor = Math.cos(horizontalAng);
let sinHor = Math.sin(horizontalAng);
// z = (r * cos(verticalAng)) * cos(horizontalAng)
// x = (r * cos(verticalAng)) * sin(horizontalAng)
// y = r * sin(veritcalAng)
let x = cosVert * sinHor;
let y = sinVert;
let z = cosVert * cosHor;
this.vertices.push(x, y, z);
this.normals.push(x, y, z);
this.texCoords.push(j / this.horizontalDiv);
this.texCoords.push(i / this.verticalDiv);
horizontalAng += horizontalDiff;
}
verticalAng += verticalDiff;
}
for (var i = 0; i < this.verticalDiv; i++) {
let k1 = i * (this.horizontalDiv + 1);
let k2 = k1 + this.horizontalDiv + 1;
for (var j = 0; j < this.horizontalDiv; j++) {
if (i != 0) {
this.indices.push(k1);
this.indices.push(k2);
this.indices.push(k1 + 1);
}
if (i != (this.verticalDiv - 1)) {
this.indices.push(k1 + 1);
this.indices.push(k2);
this.indices.push(k2 + 1);
}
k1++;
k2++;
}
}
}
}
const f = new Foo(10, 10);
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texcoord;
uniform mat4 matrix;
varying vec4 v_color;
void main() {
gl_Position = matrix * position;
v_color = vec4(0, 0, 1, 1);
// comment in next line to show normals
//v_color = vec4(normal * .5 + .5, 1);
// comment in next line to show texcoords
//v_color = vec4(texcoord, 0, 1);
}
`;
const fs = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: f.vertices,
normal: f.normals,
texcoord: f.texCoords,
indices: f.indices,
});
let matrix = m4.perspective(Math.PI * 0.25, 2, 0.1, 100);
matrix = m4.translate(matrix, [0, 0, -5]);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniformXXX
twgl.setUniforms(programInfo, {
matrix,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
I'm a beginner at WebGL programming.
I've made a web application in Three.JS that draws a sin wave onto a canvas with occasional noise. After they've been drawn, I fade them away. The final effect looks something like this:
I'm trying to make the application in WebGL because of speed issues with Three.JS. I am able to draw one plain sin wave in WebGL but don't know how to achieve the same effect where I can draw a single wave, keep it in the buffer somehow, and fade it away.
This is what I currently have (in WebGL):
Also, here is the relevant code:
this.gl;
try {
this.gl = this.canvas.getContext('experimental-webgl',{antialias: false});
} catch (e) {
alert('WebGL not supported.');
}
//set position of vertices in clip coordinates
this.vtxShaderSrc = "\n\
attribute vec2 position;\n\
uniform vec2 viewport;\n\
\n\
void main(void){\n\
\n\
gl_Position = vec4((position/viewport)*2.0-1.0, 0.0, 1.0);\n\
}";
//fragment shader returns the color of pixel
this.fmtShaderSrc = "\n\
precision mediump float;\n\
\n\
\n\
\n\
void main(void){\n\
int r = 255;\n\
int g = 255;\n\
int b = 255;\n\
gl_FragColor = vec4(r/255,g/255,b/255,1.);\n\
}";
this.getShader = function(source, type){
var shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
return shader;
}
this.vtxShader = this.getShader(this.vtxShaderSrc, this.gl.VERTEX_SHADER);
this.fmtShader = this.getShader(this.fmtShaderSrc, this.gl.FRAGMENT_SHADER);
this.program = this.gl.createProgram();
//attach fragment and vertex shader to program
this.gl.attachShader(this.program, this.vtxShader);
this.gl.attachShader(this.program, this.fmtShader);
//link program to WebGL
this.gl.linkProgram(this.program);
//get position attribute and enable it in vertex shader
this._position = this.gl.getAttribLocation(this.program, 'position');
this.gl.enableVertexAttribArray(this._position);
//tell WebGL to use this program
this.gl.useProgram(this.program);
//create buffers
this.vertexBuffer = this.gl.createBuffer();
this.facesBuffer = this.gl.createBuffer();
this.lineVertices = [];
this.faceCount = [];
//bind them to WebGL
this.bindVertexBuffer = function(){
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.lineVertices), this.gl.STREAM_DRAW);
}
this.bindFacesBuffer = function(){
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.facesBuffer);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.faceCount), this.gl.STREAM_DRAW);
}
this.bindVertexBuffer();
this.bindFacesBuffer();
//set background color to black
this.gl.clearColor(0.0,0.0,0.0,1.0);
//draw on canvas
this.draw = function(){
this.gl.enable(this.gl.BLEND);
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.vertexAttribPointer(this._position, 2, this.gl.FLOAT, false, 8*2, 0);
var loc = this.gl.getUniformLocation(this.program, 'viewport');
this.gl.uniform2f(loc, this.canvas.width, this.canvas.height);
//draw only if number of lines is greater than 0
if(this.faceCount.length > 0){
this.gl.drawElements(this.gl.LINE_STRIP, this.faceCount.length/4, this.gl.UNSIGNED_SHORT, 0);
}
this.gl.disable(this.gl.BLEND);
}
//update vertices and faces so next call to this.draw() updates the wave
this.update = function(newPts){
this.lineVertices = newPts;
this.bindVertexBuffer();
var faces = [];
for(var i = 0; i < this.lineVertices.length; i++) faces.push(i);
this.faceCount = faces;
this.bindFacesBuffer();
}
Any help/pointers are appreciated. Thanks
It's hard to give an answer as there's an infinite number of ways to do it but basically WebGL is just a rasteration API so if you want something to fade out overtime you have to render it every frame and over time draw things you want to fade out with a more transparency.
In pseudo code
for each thing to draw
compute its age
draw more transparent the older it is
(optionally, delete it if it's too old)
Here's a canvas 2d version to keep it simple
var ctx = document.querySelector("canvas").getContext("2d")
var currentTime = 0; // in seconds
var ageLimit = 1; // 1 second
var birthDuration = 0.2; // 0.2 seconds
var birthTimer = 0;
var thingsToDraw = [];
function addThingToDraw() {
thingsToDraw.push({
timeOfBirth: currentTime,
x: Math.random(),
y: Math.random(),
});
}
function computeAge(thing) {
return currentTime - thing.timeOfBirth;
}
function removeOldThings() {
while(thingsToDraw.length > 0) {
var age = computeAge(thingsToDraw[0]);
if (age < ageLimit) {
break;
}
// remove thing that's too old
thingsToDraw.shift();
}
}
function drawThings() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
thingsToDraw.forEach(function(thing) {
var age = computeAge(thing);
var lerp = age / ageLimit;
var x = ctx.canvas.width * thing.x;
var y = ctx.canvas.height * thing.y;
var radius = 10 + lerp * 10; // 10 to 20
var color = makeCSSRGBAColor(0, 0, 0, 1. - lerp);
drawCircle(ctx, x, y, radius, color);
});
}
function drawCircle(ctx, x, y, radius, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
function makeCSSRGBAColor(r, g, b, a) {
return "rgba(" + r + "," + g + "," + b + "," + a + ")";
}
var then = 0;
function process(time) {
currentTime = time * 0.001;
var deltaTime = currentTime - then;
then = currentTime;
birthTimer -= deltaTime;
if (birthTimer <= 0) {
addThingToDraw();
birthTimer = birthDuration;
}
removeOldThings();
drawThings();
requestAnimationFrame(process);
}
requestAnimationFrame(process);
canvas { border: 1px solid black; }
<canvas></canvas>
WebGL is no different, just replace ctx.clearRect with gl.clear and drawCircle with some function that draws a circle.
Here's the WebGL version of the same program
var gl = document.querySelector("canvas").getContext("webgl")
var currentTime = 0; // in seconds
var ageLimit = 1; // 1 second
var birthDuration = 0.2; // 0.2 seconds
var birthTimer = 0;
var thingsToDraw = [];
var vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
var fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
var program = twgl.createProgramFromSources(gl, [vs, fs]);
var positionLocation = gl.getAttribLocation(program, "position");
var colorLocation = gl.getUniformLocation(program, "u_color");
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
// make a circle of triangles
var numAround = 60;
var verts = [];
for (var i = 0; i < numAround; ++i) {
addPoint(verts, i / numAround, 1);
addPoint(verts, (i + 1) / numAround, 1);
addPoint(verts, i / numAround, 0);
}
var numVerts = verts.length / 2;
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
function addPoint(verts, angleZeroToOne, radius) {
var angle = angleZeroToOne * Math.PI * 2;
verts.push(Math.cos(angle) * radius, Math.sin(angle) * radius);
}
function addThingToDraw() {
thingsToDraw.push({
timeOfBirth: currentTime,
x: Math.random(),
y: Math.random(),
});
}
function computeAge(thing) {
return currentTime - thing.timeOfBirth;
}
function removeOldThings() {
while(thingsToDraw.length > 0) {
var age = computeAge(thingsToDraw[0]);
if (age < ageLimit) {
break;
}
// remove thing that's too old
thingsToDraw.shift();
}
}
function drawThings() {
gl.clear(gl.CLEAR_BUFFER_BIT);
thingsToDraw.forEach(function(thing) {
var age = computeAge(thing);
var lerp = age / ageLimit;
var x = gl.canvas.width * thing.x;
var y = gl.canvas.height * thing.y;
var radius = 10 + lerp * 10; // 10 to 20
var color = [0, 0, 0, 1 - lerp];
drawCircle(gl, x, y, radius, color);
});
}
function drawCircle(gl, x, y, radius, color) {
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var matrix = [
2 / gl.canvas.width * radius, 0, 0, 0,
0, 2 / gl.canvas.height * radius, 0, 0,
0, 0, 1, 0,
x / gl.canvas.width * 2 - 1, y / gl.canvas.height * 2 - 1, 0, 1,
];
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLocation, false, matrix);
gl.uniform4fv(colorLocation, color);
gl.drawArrays(gl.TRIANGLES, 0, numVerts);
}
function ortho(width, height) {
return [
2 / (width), 0, 0, 0,
0, 2 / (height), 0, 0,
0, 0, 1, 0, 0,
(width) / (-width), (height) / (-height), -1, 1,
];
}
var then = 0;
function process(time) {
currentTime = time * 0.001;
var deltaTime = currentTime - then;
then = currentTime;
birthTimer -= deltaTime;
if (birthTimer <= 0) {
addThingToDraw();
birthTimer = birthDuration;
}
removeOldThings();
drawThings();
requestAnimationFrame(process);
}
requestAnimationFrame(process);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<canvas></canvas>
I didn't want to include a matrix library but you can read about matrices here and because almost everyone into issues when graduating from one shape/shader to 2 you probably want to read this about drawing multiple things