mirror of
https://git.mirrors.martin98.com/https://github.com/google/draco
synced 2025-04-22 22:00:15 +08:00

The latest version of Draco brings many new enhancements to improve the development experience: * Stable API release * Support for npm Javascript package management * Javascript based encoder * Generalized metadata for meshes and point clouds * Now supporting material properties included along with encoded file * Improved compression rates: * 15% better compression on smaller models * 40% better compression of normals * Performance improvements (~10% faster encoding, decoding) * Reduced GPU memory usage: * Option to store decoded quantized attributes * Support for triangle strip connectivity on decoded meshes * iOS 9 Javascript decoder * Bitstream specification now available
301 lines
12 KiB
HTML
301 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<title>three.js webgl - loaders - Draco loader</title>
|
|
<style>
|
|
body {
|
|
font-family: Monospace;
|
|
background-color: #000;
|
|
color: #fff;
|
|
margin: 0px;
|
|
overflow: hidden;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="page-wrapper">
|
|
<h1>Open a draco compressed file (.drc):</h1>
|
|
<div>
|
|
<button id="switchDecoder" onclick="switchDecoder()">Switch Decoder</button>
|
|
<input type="file" id="fileInput">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<pre id="decoderType"><pre>
|
|
</div>
|
|
<div>
|
|
<pre id="fileDisplayArea"><pre>
|
|
</div>
|
|
<script src="https://cdn.rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script>
|
|
<script src="DRACOLoader.js"></script>
|
|
<script src="geometry_helper.js"></script>
|
|
<script>
|
|
'use strict';
|
|
|
|
// Global Draco decoder type.
|
|
var dracoDecoderType = {};
|
|
var dracoLoader;
|
|
var currentDecoder;
|
|
|
|
loadDracoDecoder();
|
|
|
|
// This function loads a JavaScript file and adds it to the page. "path"
|
|
// is the path to the JavaScript file. "onLoadFunc" is the function to be
|
|
// called when the JavaScript file has been loaded.
|
|
function loadJavaScriptFile(path, onLoadFunc) {
|
|
var head = document.getElementsByTagName('head')[0];
|
|
var element = document.createElement('script');
|
|
element.id = "decoder_script";
|
|
element.type = 'text/javascript';
|
|
element.src = path;
|
|
if (onLoadFunc !== null)
|
|
element.onload = onLoadFunc;
|
|
|
|
var previous_decoder_script = document.getElementById("decoder_script");
|
|
if (previous_decoder_script !== null) {
|
|
previous_decoder_script.parentNode.removeChild(previous_decoder_script);
|
|
}
|
|
head.appendChild(element);
|
|
}
|
|
|
|
function loadWebAssemblyDecoder() {
|
|
dracoDecoderType['wasmBinaryFile'] = '../draco_decoder.wasm';
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', '../draco_decoder.wasm', true);
|
|
xhr.responseType = 'arraybuffer';
|
|
xhr.onload = function() {
|
|
// draco_wasm_wrapper.js must be loaded before DracoDecoderModule is
|
|
// created. The object passed into DracoDecoderModule() must contain a
|
|
// property with the name of wasmBinary and the value must be an
|
|
// ArrayBuffer containing the contents of the .wasm file.
|
|
dracoDecoderType['wasmBinary'] = xhr.response;
|
|
createDracoDecoder();
|
|
};
|
|
xhr.send(null)
|
|
}
|
|
|
|
function switchDecoder() {
|
|
var showDecoder = document.getElementById('decoderType');
|
|
dracoDecoderType = {};
|
|
if (currentDecoder === "wasm") {
|
|
currentDecoder = "js";
|
|
loadJavaScriptFile('../draco_decoder.js', createDracoDecoder);
|
|
showDecoder.innerText = "Now using Javascript Decoder.";
|
|
} else {
|
|
currentDecoder = "wasm";
|
|
loadJavaScriptFile('../draco_wasm_wrapper.js', loadWebAssemblyDecoder);
|
|
showDecoder.innerText = "Now using WebAssembly Decoder.";
|
|
}
|
|
}
|
|
|
|
// This function will test if the browser has support for WebAssembly. If
|
|
// it does it will download the WebAssembly Draco decoder, if not it will
|
|
// download the asmjs Draco decoder.
|
|
// TODO: Investigate moving the Draco decoder loading code
|
|
// over to DRACOLoader.js.
|
|
function loadDracoDecoder() {
|
|
var showDecoder = document.getElementById('decoderType');
|
|
if (typeof WebAssembly !== 'object') {
|
|
// No WebAssembly support
|
|
currentDecoder = "js";
|
|
loadJavaScriptFile('../draco_decoder.js', createDracoDecoder);
|
|
var switchButton = document.getElementById('switchDecoder');
|
|
showDecoder.parentNode.removeChild(showDecoder);
|
|
switchButton.parentNode.removeChild(switchButton);
|
|
} else {
|
|
currentDecoder = "wasm";
|
|
loadJavaScriptFile('../draco_wasm_wrapper.js', loadWebAssemblyDecoder);
|
|
showDecoder.innerText = "Now using WebAssembly Decoder.";
|
|
}
|
|
}
|
|
|
|
function createDracoDecoder() {
|
|
dracoLoader = new THREE.DRACOLoader();
|
|
dracoLoader.setDracoDecoderType(dracoDecoderType);
|
|
}
|
|
|
|
var camera, cameraTarget, scene, renderer;
|
|
|
|
function init() {
|
|
var container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
|
|
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 15);
|
|
camera.position.set(3, 0.15, 3);
|
|
cameraTarget = new THREE.Vector3(0, 0, 0);
|
|
|
|
scene = new THREE.Scene();
|
|
scene.fog = new THREE.Fog(0x72645b, 2, 15);
|
|
|
|
// Ground
|
|
var plane = new THREE.Mesh(
|
|
new THREE.PlaneBufferGeometry(40, 40),
|
|
new THREE.MeshPhongMaterial({color: 0x999999, specular: 0x101010}));
|
|
plane.rotation.x = -Math.PI/2;
|
|
plane.position.y = -0.5;
|
|
scene.add(plane);
|
|
plane.receiveShadow = true;
|
|
|
|
// Lights
|
|
scene.add(new THREE.HemisphereLight(0x443333, 0x111122));
|
|
addShadowedLight(1, 1, 1, 0xffffff, 1.35);
|
|
addShadowedLight(0.5, 1, -1, 0xffaa00, 1);
|
|
|
|
// renderer
|
|
renderer = new THREE.WebGLRenderer({antialias: true});
|
|
renderer.setClearColor(scene.fog.color);
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
container.appendChild(renderer.domElement);
|
|
window.addEventListener('resize', onWindowResize, false);
|
|
}
|
|
|
|
function addShadowedLight(x, y, z, color, intensity) {
|
|
var directionalLight = new THREE.DirectionalLight(color, intensity);
|
|
directionalLight.position.set(x, y, z);
|
|
scene.add(directionalLight);
|
|
}
|
|
|
|
function onWindowResize() {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
}
|
|
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
render();
|
|
}
|
|
|
|
function render() {
|
|
var timer = Date.now() * 0.0005;
|
|
camera.position.x = Math.sin(timer) * 2.5;
|
|
camera.position.z = Math.cos(timer) * 2.5;
|
|
camera.lookAt(cameraTarget);
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
window.onload = function() {
|
|
var fileInput = document.getElementById('fileInput');
|
|
var fileDisplayArea = document.getElementById('fileDisplayArea');
|
|
|
|
fileInput.onclick = function() {
|
|
this.value = '';
|
|
}
|
|
|
|
fileInput.addEventListener('change', function(e) {
|
|
var file = fileInput.files[0];
|
|
|
|
var reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
// Enable logging to console output.
|
|
dracoLoader.setVerbosity(1);
|
|
|
|
// To use triangle strips use:
|
|
// dracoLoader.setDrawMode(THREE.TriangleStripDrawMode);
|
|
dracoLoader.setDrawMode(THREE.TrianglesDrawMode);
|
|
|
|
// Skip dequantization of the position attribute. It will be done on the GPU.
|
|
dracoLoader.setSkipDequantization('position', true);
|
|
dracoLoader.decodeDracoFile(reader.result, function(bufferGeometry) {
|
|
if (dracoLoader.decode_time !== undefined) {
|
|
fileDisplayArea.innerText = 'Decode time = ' + dracoLoader.decode_time + '\n' +
|
|
'Import time = ' + dracoLoader.import_time;
|
|
}
|
|
var material = new THREE.MeshStandardMaterial({vertexColors: THREE.VertexColors});
|
|
// If the position attribute is quantized, modify the material to perform
|
|
// dequantization on the GPU.
|
|
if (bufferGeometry.attributes['position'].isQuantized) {
|
|
setDequantizationForMaterial(material, bufferGeometry);
|
|
}
|
|
|
|
var geometry;
|
|
// Point cloud does not have face indices.
|
|
if (bufferGeometry.index == null) {
|
|
geometry = new THREE.Points(bufferGeometry, material);
|
|
} else {
|
|
if (bufferGeometry.attributes.normal === undefined) {
|
|
var geometryHelper = new GeometryHelper();
|
|
geometryHelper.computeVertexNormals(bufferGeometry);
|
|
}
|
|
geometry = new THREE.Mesh(bufferGeometry, material);
|
|
geometry.drawMode = dracoLoader.drawMode;
|
|
}
|
|
// Compute range of the geometry coordinates for proper rendering.
|
|
bufferGeometry.computeBoundingBox();
|
|
if (bufferGeometry.attributes['position'].isQuantized) {
|
|
// If the geometry is quantized, transform the bounding box to the dequantized
|
|
// coordinates.
|
|
var posAttribute = bufferGeometry.attributes['position'];
|
|
var normConstant =
|
|
posAttribute.maxRange / (1 << posAttribute.numQuantizationBits);
|
|
var minPos = posAttribute.minValues;
|
|
bufferGeometry.boundingBox.max.x =
|
|
minPos[0] + bufferGeometry.boundingBox.max.x * normConstant;
|
|
bufferGeometry.boundingBox.max.y =
|
|
minPos[1] + bufferGeometry.boundingBox.max.y * normConstant;
|
|
bufferGeometry.boundingBox.max.z =
|
|
minPos[2] + bufferGeometry.boundingBox.max.z * normConstant;
|
|
bufferGeometry.boundingBox.min.x =
|
|
minPos[0] + bufferGeometry.boundingBox.min.x * normConstant;
|
|
bufferGeometry.boundingBox.min.y =
|
|
minPos[1] + bufferGeometry.boundingBox.min.y * normConstant;
|
|
bufferGeometry.boundingBox.min.z =
|
|
minPos[2] + bufferGeometry.boundingBox.min.z * normConstant;
|
|
}
|
|
var sizeX = bufferGeometry.boundingBox.max.x - bufferGeometry.boundingBox.min.x;
|
|
var sizeY = bufferGeometry.boundingBox.max.y - bufferGeometry.boundingBox.min.y;
|
|
var sizeZ = bufferGeometry.boundingBox.max.z - bufferGeometry.boundingBox.min.z;
|
|
var diagonalSize = Math.sqrt(sizeX * sizeX + sizeY * sizeY + sizeZ * sizeZ);
|
|
var scale = 1.0 / diagonalSize;
|
|
var midX =
|
|
(bufferGeometry.boundingBox.min.x + bufferGeometry.boundingBox.max.x) / 2;
|
|
var midY =
|
|
(bufferGeometry.boundingBox.min.y + bufferGeometry.boundingBox.max.y) / 2;
|
|
var midZ =
|
|
(bufferGeometry.boundingBox.min.z + bufferGeometry.boundingBox.max.z) / 2;
|
|
|
|
geometry.scale.multiplyScalar(scale);
|
|
geometry.position.x = -midX * scale;
|
|
geometry.position.y = -midY * scale;
|
|
geometry.position.z = -midZ * scale;
|
|
geometry.castShadow = true;
|
|
geometry.receiveShadow = true;
|
|
|
|
var selectedObject = scene.getObjectByName("my_mesh");
|
|
scene.remove(selectedObject);
|
|
geometry.name = "my_mesh";
|
|
scene.add(geometry);
|
|
});
|
|
}
|
|
reader.readAsArrayBuffer(file);
|
|
});
|
|
|
|
init();
|
|
animate();
|
|
}
|
|
|
|
function setDequantizationForMaterial(material, bufferGeometry) {
|
|
material.onBeforeCompile = function(shader) {
|
|
// Add uniform variables needed for dequantization.
|
|
var posAttribute = bufferGeometry.attributes['position'];
|
|
shader.uniforms.normConstant =
|
|
{ value: posAttribute.maxRange / (1 << posAttribute.numQuantizationBits) };
|
|
shader.uniforms.minPos = { value: posAttribute.minValues };
|
|
|
|
shader.vertexShader = 'uniform float maxRange;\n' +
|
|
'uniform float normConstant;\n' +
|
|
'uniform vec3 minPos;\n' +
|
|
shader.vertexShader;
|
|
shader.vertexShader = shader.vertexShader.replace(
|
|
'#include <begin_vertex>',
|
|
'vec3 transformed = minPos + position * normConstant;'
|
|
);
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|