Уменьшите загрузку ЦП в приложении Threejs ⇐ Jquery
-
Anonymous
Уменьшите загрузку ЦП в приложении Threejs
Сейчас я работаю над проектом, в котором программирую Snake на Threejs (да, я знаю, что есть более простые методы). Кажется, все работает нормально, но когда змея достигает определенного размера, загрузка ЦП возрастает до 50 % и более, и вся вкладка браузера зависает.
Вот код: JSFiddle (В конце страницы есть кнопка запуска/перезапуска)
// Javascript импортировать * как ТРИ из «три»; импортируйте { gsap } из "https://cdn.skypack.dev/gsap"; const WH = window.innerHeight, размер игры = 25, Mid = Math.floor(размер игры / 2), сцена = новый ТРИ.Сцена(), свет = новый THREE.AmbientLight(0x404040), камера = новая THREE.OrthographicCamera(-0,5, размер игры + 0,5, размер игры + 0,5, -0,5, 1, 100), рендерер = новый THREE.WebGLRenderer({canvas: $("#threejs")[0]}), игра = { змея: { тело: [], длина: 0, д: "+х" }, еда: {}, бег: правда }, SnakeEvent = новое событие («змея»); класс CreateSnake { конструктор (x, y, d) { это.х = х; это.у = у; это.з = 0; this.d = d; this.box = новый THREE.Mesh( новый THREE.BoxGeometry(1, 1, 1), новый THREE.MeshNormalMaterial() ); }; getCoords () { вернуть [это.х, это.y, это.z]; }; получить направление () { верните это.д; }; } класс CreateFood { конструктор () { пусть c = validFoodLocations(); this.x = c[0]; this.y = c[1]; это.з = 0; this.box = новый THREE.Mesh( новый THREE.BoxGeometry(1, 1, 1), новый THREE.MeshBasicMaterial({color: 0xff0000}) ); }; getCoords () { вернуть [это.x, это,y, это,z] }; } функция инициализации () { $("#threejs").trigger("фокус"); game.snake.body.push(new CreateSnake(mid, middle, "+x")); game.snake.body[0].box.position.set(mid, middle, 0); добавитьЕду(); Scene.add(light, game.snake.body[0].box); Scene.background = новый THREE.Color(0x1C3144); камера.позиция.setZ(10); камера.lookAt(0, 0, 0); renderer.setPixelRatio(1/1); renderer.setSize(ч, вт); renderer.render(сцена, камера); addEventListener("resize", () => {renderer.setSize(window.innerHeight, window.innerHeight);}); addEventListener("keydown", keyDown); game.gameloop = setInterval(gameloop, 100); анимировать(); }; addEventListener("змея", init); $("#restart").on("click", () => sendEvent(snakeEvent)); функция игрового цикла () { перемещениеЗмея(); если (compareCoords()) { добавитьBodyPart(); Scene.remove(game.food.box); добавитьЕду(); }; проверкаНаСмерть(); }; функция moveSnake () { пусть предыдущая = "", x, y; game.snake.body.forEach((part, i) => { если (я === 0) { х = часть.х; y = партия.y; предыдущая = часть.d; переключатель (game.snake.d) { случай "+х": часть.х += 1; перерыв; случай "-х": часть.х -= 1; перерыв; случай "+y": часть.y += 1; перерыв; случай "-y": партия.у -= 1; перерыв; по умолчанию: часть.х += 1; перерыв; }; gsap.fromTo( часть.коробка.позиция, {x: x, y: y, продолжительность: 0,1}, {x: x + (part.x - x), y: y + (part.y - y), продолжительность: 0,1} ); part.d = game.snake.d; } еще { х = часть.х; y = партия.y; пусть d = предыдущая предыдущая = часть.d; часть.д = д; переключатель (г) { случай "+х": часть.х += 1; перерыв; случай "-х": часть.х -= 1; перерыв; случай "+y": часть.y += 1; перерыв; случай "-y": партия.у -= 1; перерыв; по умолчанию: часть.х += 1; перерыв; }; gsap.fromTo( часть.коробка.позиция, {x: x, y: y, продолжительность: 0,1}, {x: x + (part.x - x), y: y + (part.y - y), продолжительность: 0,1} ); }; }); }; функция CompareCoords () { пусть s = game.snake.body[0], е = игра.еда; if (s.x === f.x && s.y === f.y && s.z === f.z) { вернуть истину; } еще { вернуть ложь; }; }; функция addBodyPart () { пусть b = game.snake.body, l = b[b.длина - 1], с = l.getCoords(), d = l.getDirection(); setTimeout(() => { b.push(new CreateSnake(c[0], c[1], 1, d)); b[b.length - 1].box.position.set(c[0], c[1], 0); Scene.add(b[b.length - 1].box); игра.змея.длина++; }, 100); }; функция addFood () { game.food = новый CreateFood(); пусть f = game.food; f.box.position.x = f.x; f.box.position.y = f.y; сцена.добавить(f.box); }; функция validFoodLocations () { пусть х, у; while (!(x > 0 && x < gamesize && notInSnakeX(x))) { x = Math.floor(Math.random() * размер игры); }; while (!(y > 0 && y < gamesize && notInSnakeY(y))) { y = Math.floor(Math.random() * размер игры); }; вернуть [х, у]; }; функция notInSnakeX (x) { пусть рез = правда; game.snake.body.forEach(element => { если (element.x === x) { рез = ложь; }; }); вернуть разрешение; }; функция notInSnakeY (y) { пусть рез = правда; game.snake.body.forEach(element => { если (element.y === y) { рез = ложь; }; }); вернуть разрешение; }; функциональная клавишаВниз (е) { пусть t, d = game.snake.d; переключатель (e.code) { корпус «КлючА»: т = "-х"; перерыв; корпус «KeyW»: т = "+у"; перерыв; корпус «KeyD»: т = "+х"; перерыв; корпус «Ключи»: т = "-у"; перерыв; по умолчанию: д = д; }; if (t !== undefined && !(t.charAt(1) === d.charAt(1) && t.charAt(0) !== d.charAt(0))) { game.snake.d = т; }; }; функция анимации () { если (game.running) { requestAnimationFrame (анимировать); renderer.render(сцена, камера); }; }; функция умереть () { game.snake = { тело: [], длина: 0, д: "+х" }; игра.еда = {}; game.running = ложь; ClearInterval(game.gameloop); удалить game.gameloop; }; функция checkForDeath () { пусть b = game.snake.body, ч = б[0]; if (h.x < 0 || h.x > gamesize || h.y < 0 || h.y > gamesize || checkForTouch(b, h)) { умереть(); }; }; функция checkForTouch (arr, v) { пусть рез = ложь; arr.forEach((element, i) => { if (i !== 0 && element.x === v.x && element.y === v.y) { рез = правда; }; }); вернуть разрешение; }; Дополнительная информация: я тестировал его на Opera Gx и Edge. Авария происходит, когда у змеи имеется 31 «Часть тела».
Я пробовал много чего, например, разбивал все на разные файлы или удалял некоторые функции рендеринга, но это не помогло. Я также попытался изменить сетку с THREE.MeshNormalMaterial() на THREE.MeshBasicMateria(), и тогда я смог получить до 41 части змеи. (В jsfiddle я использовал NormalMaterial, поэтому любому, кто попытается воссоздать ошибку, не придется долго играть.) Я понятия не имею, что является причиной этого, и любая помощь была бы полезна.
ИЗМЕНИТЬ
Как ответил @Axiome, моя проблема заключалась в том, что ЦП каждый раз отправляет одну и ту же сетку в графический процессор. Я посмотрел видео об инстансном рендеринге и узнал, что одни и те же сетки каждый раз должны рассчитываться графическим процессором, чего нельзя сказать о InstancedMesh. Мой вопрос и проблема решены, поэтому я пометил ответ как принятый. НО Я хотел бы указать всем, у кого есть подобная проблема, что Instanced Rendering и, следовательно, InstancedMesh требует огромных усилий и, как правило, сопряжен с некоторыми трудностями. Для всех нуждающихся: это видео спасло мне жизнь
Сейчас я работаю над проектом, в котором программирую Snake на Threejs (да, я знаю, что есть более простые методы). Кажется, все работает нормально, но когда змея достигает определенного размера, загрузка ЦП возрастает до 50 % и более, и вся вкладка браузера зависает.
Вот код: JSFiddle (В конце страницы есть кнопка запуска/перезапуска)
// Javascript импортировать * как ТРИ из «три»; импортируйте { gsap } из "https://cdn.skypack.dev/gsap"; const WH = window.innerHeight, размер игры = 25, Mid = Math.floor(размер игры / 2), сцена = новый ТРИ.Сцена(), свет = новый THREE.AmbientLight(0x404040), камера = новая THREE.OrthographicCamera(-0,5, размер игры + 0,5, размер игры + 0,5, -0,5, 1, 100), рендерер = новый THREE.WebGLRenderer({canvas: $("#threejs")[0]}), игра = { змея: { тело: [], длина: 0, д: "+х" }, еда: {}, бег: правда }, SnakeEvent = новое событие («змея»); класс CreateSnake { конструктор (x, y, d) { это.х = х; это.у = у; это.з = 0; this.d = d; this.box = новый THREE.Mesh( новый THREE.BoxGeometry(1, 1, 1), новый THREE.MeshNormalMaterial() ); }; getCoords () { вернуть [это.х, это.y, это.z]; }; получить направление () { верните это.д; }; } класс CreateFood { конструктор () { пусть c = validFoodLocations(); this.x = c[0]; this.y = c[1]; это.з = 0; this.box = новый THREE.Mesh( новый THREE.BoxGeometry(1, 1, 1), новый THREE.MeshBasicMaterial({color: 0xff0000}) ); }; getCoords () { вернуть [это.x, это,y, это,z] }; } функция инициализации () { $("#threejs").trigger("фокус"); game.snake.body.push(new CreateSnake(mid, middle, "+x")); game.snake.body[0].box.position.set(mid, middle, 0); добавитьЕду(); Scene.add(light, game.snake.body[0].box); Scene.background = новый THREE.Color(0x1C3144); камера.позиция.setZ(10); камера.lookAt(0, 0, 0); renderer.setPixelRatio(1/1); renderer.setSize(ч, вт); renderer.render(сцена, камера); addEventListener("resize", () => {renderer.setSize(window.innerHeight, window.innerHeight);}); addEventListener("keydown", keyDown); game.gameloop = setInterval(gameloop, 100); анимировать(); }; addEventListener("змея", init); $("#restart").on("click", () => sendEvent(snakeEvent)); функция игрового цикла () { перемещениеЗмея(); если (compareCoords()) { добавитьBodyPart(); Scene.remove(game.food.box); добавитьЕду(); }; проверкаНаСмерть(); }; функция moveSnake () { пусть предыдущая = "", x, y; game.snake.body.forEach((part, i) => { если (я === 0) { х = часть.х; y = партия.y; предыдущая = часть.d; переключатель (game.snake.d) { случай "+х": часть.х += 1; перерыв; случай "-х": часть.х -= 1; перерыв; случай "+y": часть.y += 1; перерыв; случай "-y": партия.у -= 1; перерыв; по умолчанию: часть.х += 1; перерыв; }; gsap.fromTo( часть.коробка.позиция, {x: x, y: y, продолжительность: 0,1}, {x: x + (part.x - x), y: y + (part.y - y), продолжительность: 0,1} ); part.d = game.snake.d; } еще { х = часть.х; y = партия.y; пусть d = предыдущая предыдущая = часть.d; часть.д = д; переключатель (г) { случай "+х": часть.х += 1; перерыв; случай "-х": часть.х -= 1; перерыв; случай "+y": часть.y += 1; перерыв; случай "-y": партия.у -= 1; перерыв; по умолчанию: часть.х += 1; перерыв; }; gsap.fromTo( часть.коробка.позиция, {x: x, y: y, продолжительность: 0,1}, {x: x + (part.x - x), y: y + (part.y - y), продолжительность: 0,1} ); }; }); }; функция CompareCoords () { пусть s = game.snake.body[0], е = игра.еда; if (s.x === f.x && s.y === f.y && s.z === f.z) { вернуть истину; } еще { вернуть ложь; }; }; функция addBodyPart () { пусть b = game.snake.body, l = b[b.длина - 1], с = l.getCoords(), d = l.getDirection(); setTimeout(() => { b.push(new CreateSnake(c[0], c[1], 1, d)); b[b.length - 1].box.position.set(c[0], c[1], 0); Scene.add(b[b.length - 1].box); игра.змея.длина++; }, 100); }; функция addFood () { game.food = новый CreateFood(); пусть f = game.food; f.box.position.x = f.x; f.box.position.y = f.y; сцена.добавить(f.box); }; функция validFoodLocations () { пусть х, у; while (!(x > 0 && x < gamesize && notInSnakeX(x))) { x = Math.floor(Math.random() * размер игры); }; while (!(y > 0 && y < gamesize && notInSnakeY(y))) { y = Math.floor(Math.random() * размер игры); }; вернуть [х, у]; }; функция notInSnakeX (x) { пусть рез = правда; game.snake.body.forEach(element => { если (element.x === x) { рез = ложь; }; }); вернуть разрешение; }; функция notInSnakeY (y) { пусть рез = правда; game.snake.body.forEach(element => { если (element.y === y) { рез = ложь; }; }); вернуть разрешение; }; функциональная клавишаВниз (е) { пусть t, d = game.snake.d; переключатель (e.code) { корпус «КлючА»: т = "-х"; перерыв; корпус «KeyW»: т = "+у"; перерыв; корпус «KeyD»: т = "+х"; перерыв; корпус «Ключи»: т = "-у"; перерыв; по умолчанию: д = д; }; if (t !== undefined && !(t.charAt(1) === d.charAt(1) && t.charAt(0) !== d.charAt(0))) { game.snake.d = т; }; }; функция анимации () { если (game.running) { requestAnimationFrame (анимировать); renderer.render(сцена, камера); }; }; функция умереть () { game.snake = { тело: [], длина: 0, д: "+х" }; игра.еда = {}; game.running = ложь; ClearInterval(game.gameloop); удалить game.gameloop; }; функция checkForDeath () { пусть b = game.snake.body, ч = б[0]; if (h.x < 0 || h.x > gamesize || h.y < 0 || h.y > gamesize || checkForTouch(b, h)) { умереть(); }; }; функция checkForTouch (arr, v) { пусть рез = ложь; arr.forEach((element, i) => { if (i !== 0 && element.x === v.x && element.y === v.y) { рез = правда; }; }); вернуть разрешение; }; Дополнительная информация: я тестировал его на Opera Gx и Edge. Авария происходит, когда у змеи имеется 31 «Часть тела».
Я пробовал много чего, например, разбивал все на разные файлы или удалял некоторые функции рендеринга, но это не помогло. Я также попытался изменить сетку с THREE.MeshNormalMaterial() на THREE.MeshBasicMateria(), и тогда я смог получить до 41 части змеи. (В jsfiddle я использовал NormalMaterial, поэтому любому, кто попытается воссоздать ошибку, не придется долго играть.) Я понятия не имею, что является причиной этого, и любая помощь была бы полезна.
ИЗМЕНИТЬ
Как ответил @Axiome, моя проблема заключалась в том, что ЦП каждый раз отправляет одну и ту же сетку в графический процессор. Я посмотрел видео об инстансном рендеринге и узнал, что одни и те же сетки каждый раз должны рассчитываться графическим процессором, чего нельзя сказать о InstancedMesh. Мой вопрос и проблема решены, поэтому я пометил ответ как принятый. НО Я хотел бы указать всем, у кого есть подобная проблема, что Instanced Rendering и, следовательно, InstancedMesh требует огромных усилий и, как правило, сопряжен с некоторыми трудностями. Для всех нуждающихся: это видео спасло мне жизнь
Мобильная версия