将 three.js 生成的画布保存到服务器 - 没有文件被保存
Posted
技术标签:
【中文标题】将 three.js 生成的画布保存到服务器 - 没有文件被保存【英文标题】:Saving a three.js generated canvas to server - no file being saved 【发布时间】:2021-10-29 18:49:19 【问题描述】:我正在尝试将图像保存到使用以下 three.js 脚本创建的服务器..
actualCode(THREE);
function actualCode(THREE)
//Variables for rendering
const renderer = new THREE.WebGLRenderer(
antialias: true
);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(30, 400.0 / 400, 1, 1000);
//Object variables
let texture;
let paintedMug;
//Preload image, then trigger rendering
const loader = new THREE.TextureLoader();
texture = loader.load("images/djmug2.jpg", function (_tex)
// /*Debugging:*/ setTimeout(() => document.body.appendChild(texture.image), 100);
init();
//views 17.5=front | 355=side | 139.6=back
renderImageSolo(17.5);
);
function init()
//Init scene and camera
camera.position.set(0, 1.3, 11);
camera.lookAt(scene.position);
renderer.setSize(400, 400);
//Set an ambient light
const light = new THREE.AmbientLight(0xffffff); // soft white light
scene.add(light);
//Draw white mug
const muggeom = new THREE.CylinderGeometry(1.5, 1.5, 3.5, 240, 1);
const mugmaterial = new THREE.MeshStandardMaterial(
color: "#fff",
);
const mug = new THREE.Mesh(muggeom, mugmaterial);
//Draw painting on mug with slightly larger radius
const paintgeom = new THREE.CylinderGeometry(1.5001, 1.5001, 3.3, 240, 1, true);
const paintmaterial = new THREE.MeshStandardMaterial(
map: texture,
);
const paint = new THREE.Mesh(paintgeom, paintmaterial);
//Define a group as mug + paint
paintedMug = new THREE.Group();
paintedMug.add(mug);
paintedMug.add(paint);
//Add group to scene
scene.add(paintedMug);
function renderImageSolo(angle)
//Init just like main renderer / scene, will use same camera
const solo_renderer = new THREE.WebGLRenderer(
antialias: true
);
solo_renderer.setSize(renderer.domElement.width, renderer.domElement.height);
solo_renderer.domElement.style.marginTop = "0em"; //Space out canvas
solo_renderer.domElement.id = "canvas"; //give canvas id
document.body.appendChild(solo_renderer.domElement);
const solo_scene = new THREE.Scene();
//Set an ambient light
const light = new THREE.AmbientLight(0xffffff); // soft white light
solo_scene.add(light);
//Draw painting alone
const paintgeom = new THREE.CylinderGeometry(1.5, 1.5, 3.3, 240, 1, true);
const paintmaterial = new THREE.MeshStandardMaterial(
map: texture,
);
const paint = new THREE.Mesh(paintgeom, paintmaterial);
//Add paint to scene
solo_scene.add(paint);
//Rotate paint by angle
paint.rotation.y = angle
//Draw result with green screen bg
solo_scene.background = new THREE.Color(0x04F404);
//Draw result with trans bg (not working showing as black atm)
//solo_scene.background = new THREE.WebGLRenderer( alpha: true );
solo_renderer.render(solo_scene, camera);
saveit();
然后我尝试使用 ajax 保存生成的图像,如下所示..
function saveit()
const canvas = document.getElementById('canvas');
var photo = canvas.toDataURL('image/jpeg');
$.ajax(
method: 'POST',
url: 'photo_upload.php',
data:
photo: photo
);
photo_upload.php 内容..
$data = $_POST['photo'];
list($type, $data) = explode(';', $data);
list(, $data) = explode(',', $data);
$data = base64_decode($data);
mkdir($_SERVER['DOCUMENT_ROOT'] . "/photos");
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/photos/".time().'.png', $data);
die;
但没有任何内容被保存,服务器上的 /photos 仍然为空,另外,如果我右键单击并“保存图像”,保存的图像只是一个黑色方块,而不是屏幕上显示的内容,这是一个单独的问题。
【问题讨论】:
【参考方案1】:用现代javascript重写并测试保存到PHP服务器的代码:
-
只保留js的相关部分并使用
fetch
添加保存功能
import * as THREE from 'https://cdn.skypack.dev/three';
document.addEventListener("DOMContentLoaded", _e =>
//Create a div to receive results
const messDiv = document.createElement('div');
messDiv.classList.add('message');
document.body.appendChild(messDiv);
//Object variables
let texture;
//Preload image, then trigger rendering
const loader = new THREE.TextureLoader();
//Example with image hosted from Imgur:
messDiv.textContent = "Loading texture...";
texture = loader.load("https://i.imgur.com/TQZrUSP.jpeg", function(_tex)
console.log("texture loaded");
// /*Debugging:*/ setTimeout(() => document.body.appendChild(texture.image), 100);
renderImageSolo(60);
);
function renderImageSolo(angle)
messDiv.textContent = "Rendering 3D projection...";
//Init just main renderer / scene
const solo_renderer = new THREE.WebGLRenderer(
antialias: true,
preserveDrawingBuffer: true // <-- avoid plain black image
);
solo_renderer.setSize(400, 400);
document.body.appendChild(solo_renderer.domElement);
const solo_scene = new THREE.Scene();
//Init camera
const camera = new THREE.PerspectiveCamera(30, 400.0 / 400, 1, 1000);
camera.position.set(0, 1.3, 11);
camera.lookAt(solo_scene.position);
//Set an ambient light
const light = new THREE.AmbientLight(0xffffff); // soft white light
solo_scene.add(light);
//Draw painting alone
const paintgeom = new THREE.CylinderGeometry(1.5, 1.5, 3.3, 240, 1, true);
const paintmaterial = new THREE.MeshStandardMaterial(
//color: "#ddd",
map: texture,
);
const paint = new THREE.Mesh(paintgeom, paintmaterial);
//Add paint to scene
solo_scene.add(paint);
//Rotate paint by angle
paint.rotation.y = angle
//Draw result
solo_scene.background = new THREE.Color(0xffffff);
solo_renderer.render(solo_scene, camera);
//Save result
saveImage(solo_renderer.domElement, "photo.jpeg")
//Save canvas as image by posting it to special url on server
function saveImage(canvas, filename)
messDiv.textContent = "Uploading result...";
canvas.toBlob(imgBlob => //Specifying image/jpeg, otherwise you'd get a png
const fileform = new FormData();
fileform.append('filename', filename);
fileform.append('data', imgBlob);
fetch('./photo_upload.php',
method: 'POST',
body: fileform,
)
.then(response =>
return response.json();
)
.then(data =>
if (data.error) //Show server errors
messDiv.classList.add('error');
messDiv.textContent = data.error;
else //Show success message
messDiv.classList.add('success');
messDiv.textContent = data.message;
)
.catch(err => //Handle js errors
console.log(err);
messDiv.classList.add('error');
messDiv.textContent = err.message;
);
, 'image/jpeg'); //<- image type for canvas.toBlob (defaults to png)
);
-
编写代码保存在 PHP 服务器上
<?php
//photo_upload.php
try
header('Content-type: application/json');
//get file name
$filename = $_POST['filename'];
if (!$filename)
die(json_encode([
'error' => "Could not read filename from request"
]));
//get image data
$img = $_FILES['data'];
if (!$filename)
die(json_encode([
'error' => "No image data in request"
]));
//Create save dir
$savePath = $_SERVER['DOCUMENT_ROOT'] . "/photos/";
if (!file_exists($savePath))
if (!mkdir($savePath))
die(json_encode([
'error' => "Could not create dir $savePath"
]));
//Save file
$savePath .= $filename;
if (!move_uploaded_file($img['tmp_name'], $savePath))
echo json_encode([
'error' => "Could not write to $savePath"
]);
else
$bytes = filesize($savePath);
echo json_encode([
'message' => "Image uploaded and saved to $savePath ($bytes bytes)"
]);
catch (Exception $err)
echo json_encode([
'error' => $err->getMessage()
]);
-
让消息更易读的小 CSS
body
font-family: Arial, Helvetica, sans-serif;
.message
text-align: center;
padding: 1em;
font-style: italic;
color: dimgray;
.message.success
font-style: normal;
font-weight: bold;
color: forestgreen;
.message.error
font-style: normal;
font-family: 'Courier New', Courier, monospace;
white-space: pre-wrap;
color: darkred;
2021-09-07 - 我已编辑代码以使用 js FormData
和 PHP $_FILES
以提高效率和可读性
【讨论】:
这太好了,谢谢!只有一件事,我如何抑制/隐藏/摆脱消息?例如消息 =>“图像上传并保存到 $savePath($bytes 字节)”我不想在对话框上单击“确定”。提前致谢。 我会让你自己搜索一下,学习一些东西。我可以将您指向 JavaScript 端,假设显示带有alert
函数的弹出消息...
我昨天尝试了 3 个小时,并尽我所能进行了重新研究,每次我取出一行(或注释掉一行)时,我都会弹出一个错误来替换警报。在过去的几年里,我确实自己进行了一些搜索,并且也学习了一些 Javascript/PHP 和相关的知识,但我仍然很新,并且 100% 通过反复试验自学成才。在寻求帮助之前,我实际上会尽我最大的努力(那个时间也允许)自己解决问题。再次感谢您的所有帮助,非常感谢。
我想我编辑了错误的文件并试图从 file-upload.php 中删除错误,而我应该查看的 js 脚本上的警报却出现了!对于被错误的文件包裹起来并错过了现在显而易见的事情,我深表歉意!
不过要小心,因为我尽量保持简单,并且没有将服务器失败消息与成功消息分开,因此隐藏警报可能会导致您没有注意到一些错误。您最好将alert
替换为console.log
,这样您至少可以在浏览器的开发工具控制台中轻松读取服务器结果【参考方案2】:
你应该可以通过像这样创建渲染器来解决这个问题:
const solo_renderer = new THREE.WebGLRenderer(
antialias: true,
preserveDrawingBuffer: true // FIX
);
我还建议您研究说明如何保存画布屏幕截图的现有资源。试试看:
Three.js: How can I make a 2D SnapShot of a Scene as a JPG Image?
【讨论】:
感谢您的回答,preserveDrawingBuffer: true 已修复我之前显示为黑色方块的第二个问题。但是,您提供的链接中的大多数答案都是关于如何将文件保存在本地而不是服务器,这是我最苦恼的部分。以上是关于将 three.js 生成的画布保存到服务器 - 没有文件被保存的主要内容,如果未能解决你的问题,请参考以下文章
Three.js地球开发—4.Three.js渲染场景保存成贴图