响应式 canvas 使用 webgl 和 meatballs.js
Responsive canvas using webgl and meatballs.js
我正在尝试将 this codepen 作为我个人网站的背景。我对 WebGL 没有真正的了解,所以请耐心等待。我临时添加了一个事件侦听器以在调整页面大小时更新 canvas 的宽度和高度。我可以说这是有效的,因为当气泡开始越界时,它们会继续前进并且不会从页面边缘反弹,所以我知道它有点像我想要的那样工作。定义片段着色器源时,它还定义了宽度和高度,我不确定之后如何更改这些变量。我尝试使用新的宽度和高度重新定义、重新编译和重新附加片段着色器源代码。这显然是行不通的,因为在创建 canvas 时,气泡不会呈现超过页面大小。我不确定我是否以正确的方式进行此操作,如果是这样,我做错了什么? All/any感谢帮助,谢谢。
我修改的代码:
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);
最简单的方法是将所有后台创建代码放在一个函数中,并在每次调整页面大小时调用它。
您还需要添加一些代码来停止之前的后台循环,并且您应该添加一些节流以防止一次创建太多后台。
这有点低效,但大多数用户不希望应用程序在调整大小时具有极高的响应能力,而且调整大小是一种不常见的操作。
我添加了一个代码片段,它似乎可以工作,但是我无法让我的更改在 codepen 中工作。我相信这是因为 codepen 以某种破坏代码的方式检测和修改代码(jsbin 具有类似的行为来防止无限循环,并将其沙盒化)。然而,我只在一个 .html 文件中测试了我的更改,它们似乎在那里工作,所以它们应该在您的网站上工作。
附带一提,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>
那个 codepen 有很多问题。
它正在修改 canvas 大小,而不是让 CSS 调整 canvas 大小。
在代码中 canvas 大小设置为
var width = canvas.width = window.innerWidth * 0.75;
var height = canvas.height = window.innerHeight * 0.75;
最好让浏览器调整 canvas
html, body {
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
然后询问浏览器 canvas 的大小并设置 canvas 的分辨率以匹配
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
它要求的东西比 window 大,所以它会得到一个滚动条,然后通过隐藏滚动条来隐藏这个事实。这没有道理。如果您不需要滚动条,请不要要求需要滚动条的内容。
html, body {
height: 100%;
/* removed overflow: hidden */
}
canvas {
width: 100%;
height: 100%;
display: block;
}
它使用的是模板字符串,但实际上并没有将它们用作模板
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ` + (width >> 0) + `.0;
const float HEIGHT = ` + (height >> 0) + `.0;
uniform vec3 metaballs[` + numMetaballs + `];
...
`;
可以说是
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ${width >> 0}.0;
const float HEIGHT = ${height >> 0}.0;
uniform vec3 metaballs[${numMetaballs}];
...
`;
对字符串使用反引号的要点是这样你就可以
使用模板功能 ${code}
硬编码宽度和高度
const float WIDTH = ${width >> 0}.0;
const float HEIGHT = ${height >> 0}.0;
可以说是
uniform float WIDTH;
uniform float HEIGHT;
所以可以设置它们
Metaballs 被错误拼写为 Meatballs(可能是故意的)
这是一个新版本。注意:只要调整 window 的大小,元球就会获得随机位置。如果您在调整 canvas 大小后注释掉对 updateMetaballs
的调用,那么它们将不会获得新的随机位置。哪个更好取决于你。它们弹跳的逻辑是这样的,即在您调整大小后离开屏幕的任何球都将留在屏幕之外。您可以修复它,使它们朝向屏幕,并且只会从内部反弹回来。当前的代码是这样的,在外部它们只会在它们所在的位置摆动。
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>
如果您想学习 WebGL,请考虑 these tutorials
我正在尝试将 this codepen 作为我个人网站的背景。我对 WebGL 没有真正的了解,所以请耐心等待。我临时添加了一个事件侦听器以在调整页面大小时更新 canvas 的宽度和高度。我可以说这是有效的,因为当气泡开始越界时,它们会继续前进并且不会从页面边缘反弹,所以我知道它有点像我想要的那样工作。定义片段着色器源时,它还定义了宽度和高度,我不确定之后如何更改这些变量。我尝试使用新的宽度和高度重新定义、重新编译和重新附加片段着色器源代码。这显然是行不通的,因为在创建 canvas 时,气泡不会呈现超过页面大小。我不确定我是否以正确的方式进行此操作,如果是这样,我做错了什么? All/any感谢帮助,谢谢。
我修改的代码:
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);
最简单的方法是将所有后台创建代码放在一个函数中,并在每次调整页面大小时调用它。
您还需要添加一些代码来停止之前的后台循环,并且您应该添加一些节流以防止一次创建太多后台。
这有点低效,但大多数用户不希望应用程序在调整大小时具有极高的响应能力,而且调整大小是一种不常见的操作。
我添加了一个代码片段,它似乎可以工作,但是我无法让我的更改在 codepen 中工作。我相信这是因为 codepen 以某种破坏代码的方式检测和修改代码(jsbin 具有类似的行为来防止无限循环,并将其沙盒化)。然而,我只在一个 .html 文件中测试了我的更改,它们似乎在那里工作,所以它们应该在您的网站上工作。
附带一提,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>
那个 codepen 有很多问题。
它正在修改 canvas 大小,而不是让 CSS 调整 canvas 大小。
在代码中 canvas 大小设置为
var width = canvas.width = window.innerWidth * 0.75; var height = canvas.height = window.innerHeight * 0.75;
最好让浏览器调整 canvas
html, body { height: 100%; overflow: hidden; } canvas { width: 100%; height: 100%; }
然后询问浏览器 canvas 的大小并设置 canvas 的分辨率以匹配
canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight;
它要求的东西比 window 大,所以它会得到一个滚动条,然后通过隐藏滚动条来隐藏这个事实。这没有道理。如果您不需要滚动条,请不要要求需要滚动条的内容。
html, body { height: 100%; /* removed overflow: hidden */ } canvas { width: 100%; height: 100%; display: block; }
它使用的是模板字符串,但实际上并没有将它们用作模板
var fragmentShaderSrc = ` precision highp float; const float WIDTH = ` + (width >> 0) + `.0; const float HEIGHT = ` + (height >> 0) + `.0; uniform vec3 metaballs[` + numMetaballs + `]; ... `;
可以说是
var fragmentShaderSrc = ` precision highp float; const float WIDTH = ${width >> 0}.0; const float HEIGHT = ${height >> 0}.0; uniform vec3 metaballs[${numMetaballs}]; ... `;
对字符串使用反引号的要点是这样你就可以 使用模板功能
${code}
硬编码宽度和高度
const float WIDTH = ${width >> 0}.0; const float HEIGHT = ${height >> 0}.0;
可以说是
uniform float WIDTH; uniform float HEIGHT;
所以可以设置它们
Metaballs 被错误拼写为 Meatballs(可能是故意的)
这是一个新版本。注意:只要调整 window 的大小,元球就会获得随机位置。如果您在调整 canvas 大小后注释掉对 updateMetaballs
的调用,那么它们将不会获得新的随机位置。哪个更好取决于你。它们弹跳的逻辑是这样的,即在您调整大小后离开屏幕的任何球都将留在屏幕之外。您可以修复它,使它们朝向屏幕,并且只会从内部反弹回来。当前的代码是这样的,在外部它们只会在它们所在的位置摆动。
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>
如果您想学习 WebGL,请考虑 these tutorials