In general, it is not possible to make a version with a gradient, and there is no desire to continue it, maybe someone will come up with a solution to this problem here is the code:
var createScene = function() {
var scene = new BABYLON.Scene(engine);
// Камера
var camera = new BABYLON.ArcRotateCamera("camera", -Math.PI/2.5, Math.PI/3, 50, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
// Освещение
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.7;
// DOM элементы (поле и кнопка)
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Введите URL изображения GIF';
input.style.position = 'fixed';
input.style.bottom = '40px';
input.style.right = '80px';
input.style.width = '250px';
input.style.zIndex = 1000;
const button = document.createElement('button');
button.innerHTML = 'Запуск';
button.style.position = 'fixed';
button.style.bottom = '40px';
button.style.right = '340px';
button.style.zIndex = 1000;
document.body.appendChild(input);
document.body.appendChild(button);
// переменная для меша
var mapMesh = null;
// переменные для обработки GIF
let gifFrames = [];
let currentFrameIdx = 0;
let lastFrameTime = 0;
const frameDuration = 300; // ms
function startProcessing(url) {
if (mapMesh) {
mapMesh.dispose();
mapMesh = null;
}
// Загружаем и обрабатываем изображение
loadGIF(url);
}
// Обработчик кнопки
button.onclick = () => {
const url = input.value.trim();
if (url) startProcessing(url);
};
// Обработка на Enter
input.onkeydown = (e) => {
if (e.key === 'Enter') {
const url = input.value.trim();
if (url) startProcessing(url);
}
};
// Функция загрузки GIF и разбор кадров
function loadGIF(url) {
gifFrames = [];
fetchGifFrames(url);
}
// Fetch и декодер GIF (работать только если API поддержки есть)
function fetchGifFrames(url) {
fetch(url)
.then(res => res.arrayBuffer())
.then(buf => {
const decoder = new ImageDecoder({ data: buf, type: 'image/gif' });
return decoder.completed.then(() => decoder.tracks.ready).then(() => decoder);
})
.then(async decoder => {
const frameCount = decoder.tracks.selectedTrack.frameCount;
let width = 0, height = 0;
for (let i = 0; i < frameCount; i++) {
const frameInfo = await decoder.decode({ frameIndex: i });
const imageBitmap = frameInfo.image;
if (i === 0) {
width = imageBitmap.codedWidth;
height = imageBitmap.codedHeight;
}
const tex = new BABYLON.DynamicTexture("gifFrame_" + i, { width, height }, scene, false);
tex.getContext().drawImage(imageBitmap, 0, 0);
tex.update();
gifFrames.push({ texture: tex, width, height });
}
});
}
// Здесь главный цикл обновления через registerBeforeRender
scene.registerBeforeRender(() => {
const now = Date.now();
if (gifFrames.length === 0 || !mapMesh) return;
if (now - lastFrameTime > frameDuration) {
currentFrameIdx = (currentFrameIdx + 1) % gifFrames.length;
updateVerticesByFrame(gifFrames[currentFrameIdx]);
lastFrameTime = now;
}
});
// Обновление высот вершин
function updateVerticesByFrame(frame) {
const { texture, width: texW, height: texH } = frame;
const ctx = texture.getContext();
const imageData = ctx.getImageData(0, 0, texW, texH);
const data = imageData.data;
for (let i = 0; i < vertices.length / 3; i++) {
const vx = vertices[i * 3]; // -size/2 ... +size/2
const vy = vertices[i * 3 + 1];
const u = Math.floor(((vx + 10) / 20) * texW);
const v = Math.floor(((vy + 10) / 20) * texH);
const pixelIdx = (v * texW + u) * 4;
const r = data[pixelIdx];
const g = data[pixelIdx + 1];
const b = data[pixelIdx + 2];
const brightness = (0.21 * r + 0.72 * g + 0.07 * b) / 255;
const heightVal = brightness * 10; // масштаб
vertices[i * 3 + 1] = heightVal;
}
ground.updateVerticesData(BABYLON.VertexBuffer.PositionKind, vertices);
}
// Создаем и запускаем
var vertices = null;
var ground = null;
// Изначально стартуем
// Можно сразу вызвать старт с каким-нибудь URL
startProcessing('https://i.postimg.cc/D0drgZv5/image.png');
// Вытаскиваем вершины после создания меша
scene.onReadyObservable.add(() => {
if (!ground) {
ground = BABYLON.MeshBuilder.CreateGround("ground", {
width: 20,
height: 20,
subdivisions: 50
});
vertices = ground.getVerticesData(BABYLON.VertexBuffer.PositionKind);
}
});
// ваши вспомогательные функции — пусть останутся без изменений
function convertToGrayscale(imageUrl, callback) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = "Anonymous";
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const gray = 0.21 * r + 0.72 * g + 0.07 * b;
data[i] = data[i + 1] = data[i + 2] = gray;
}
ctx.putImageData(imageData, 0, 0);
callback(canvas.toDataURL());
};
img.src = imageUrl;
}
function enhanceContrast(imageUrl, contrast, callback) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (let i = 0; i < data.length; i += 4) {
data[i] = factor * (data[i] - 128) + 128;
data[i + 1] = factor * (data[i + 1] - 128) + 128;
data[i + 2] = factor * (data[i + 2] - 128) + 128;
}
ctx.putImageData(imageData, 0, 0);
callback(canvas.toDataURL());
}
img.src = imageUrl;
}
function createTerracedHeightmap(imageUrl, steps, callback) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const gray = data[i];
const steppedValue = Math.round(gray / (255 / steps)) * (255 / steps);
data[i] = data[i + 1] = data[i + 2] = steppedValue;
}
ctx.putImageData(imageData, 0, 0);
callback(canvas.toDataURL());
};
img.src = imageUrl;
}
return scene;
};
Offer your own equalizer options… Maybe someone will have something interesting…