Browse Source

Added 1 & 2 bytes options for oct encoding. Some clean ups. Added encoding reference info.

unknown 5 years ago
parent
commit
84cf2e0e61
2 changed files with 145 additions and 95 deletions
  1. 75 46
      examples/jsm/utils/GeometryCompressionUtils.js
  2. 70 49
      examples/webgl_geometry_compression.html

+ 75 - 46
examples/jsm/utils/GeometryCompressionUtils.js

@@ -1,15 +1,18 @@
-/**
- * @author YY
- */
 
 import * as THREE from "../../../build/three.module.js";
 
+
+/**
+ * @author LeonYuanYao @https://github.com/LeonYuanYao
+ * 
+ * Original Octahedron and Quantization encoding methods from @tsherif https://github.com/tsherif/mesh-quantization-example
+ */
 var GeometryCompressionUtils = {
 
     compressNormals: function (mesh, encodeMethod) {
 
         if (!mesh.geometry) {
-            console.error("Mesh must contain geometry property. ");
+            console.error("Mesh must contain geometry. ");
         }
 
         let normal = mesh.geometry.attributes.normal;
@@ -21,32 +24,33 @@ var GeometryCompressionUtils = {
         if (normal.isPacked) return;
 
         if (normal.itemSize != 3) {
-            console.error("normal.itemSize is not 3, which cannot be packed. ");
+            console.error("normal.itemSize is not 3, which cannot be encoded. ");
         }
 
         let array = normal.array;
         let count = normal.count;
 
         let result;
-        if (encodeMethod == "ANGLES") {
+        if (encodeMethod == "DEFAULT") {
 
-            result = new Uint16Array(count * 2);
+            result = new Uint8Array(count * 3);
 
             for (let idx = 0; idx < array.length; idx += 3) {
 
                 let encoded;
 
-                encoded = this.EncodingFuncs.uInt16Encode(array[idx], array[idx + 1], array[idx + 2]);
+                encoded = this.EncodingFuncs.defaultEncode(array[idx], array[idx + 1], array[idx + 2], 1);
 
-                result[idx / 3 * 2 + 0] = encoded[0];
-                result[idx / 3 * 2 + 1] = encoded[1];
+                result[idx + 0] = encoded[0];
+                result[idx + 1] = encoded[1];
+                result[idx + 2] = encoded[2];
 
             }
 
-            mesh.geometry.setAttribute('normal', new THREE.BufferAttribute(result, 2, true));
-            mesh.geometry.attributes.normal.bytes = result.length * 2;
+            mesh.geometry.setAttribute('normal', new THREE.BufferAttribute(result, 3, true));
+            mesh.geometry.attributes.normal.bytes = result.length * 1;
 
-        } else if (encodeMethod == "OCT") {
+        } else if (encodeMethod == "OCT1Byte") {
 
             result = new Int8Array(count * 2);
 
@@ -54,7 +58,7 @@ var GeometryCompressionUtils = {
 
                 let encoded;
 
-                encoded = this.EncodingFuncs.octEncodeBest(array[idx], array[idx + 1], array[idx + 2]);
+                encoded = this.EncodingFuncs.octEncodeBest(array[idx], array[idx + 1], array[idx + 2], 1);
 
                 result[idx / 3 * 2 + 0] = encoded[0];
                 result[idx / 3 * 2 + 1] = encoded[1];
@@ -64,30 +68,41 @@ var GeometryCompressionUtils = {
             mesh.geometry.setAttribute('normal', new THREE.BufferAttribute(result, 2, true));
             mesh.geometry.attributes.normal.bytes = result.length * 1;
 
-        } else if (encodeMethod == "DEFAULT") {
+        } else if (encodeMethod == "OCT2Byte") {
 
-            result = new Uint8Array(count * 3);
+            result = new Int16Array(count * 2);
 
             for (let idx = 0; idx < array.length; idx += 3) {
 
                 let encoded;
 
-                encoded = this.EncodingFuncs.defaultEncode(array[idx], array[idx + 1], array[idx + 2], 1);
+                encoded = this.EncodingFuncs.octEncodeBest(array[idx], array[idx + 1], array[idx + 2], 2);
 
-                // let decoded = GeometryEncodingUtils.defaultDecode(encoded);
+                result[idx / 3 * 2 + 0] = encoded[0];
+                result[idx / 3 * 2 + 1] = encoded[1];
 
-                // let angle = Math.acos(Math.min(array[idx] * decoded[0] + array[idx + 1]  * decoded[1] + array[idx + 2] * decoded[2], 1.0)) * 180 / Math.PI;
+            }
 
-                // Math.abs(array[idx + 2]) < 0.05 && console.log([array[idx], array[idx + 1], array[idx + 2]], encoded, decoded, angle)
+            mesh.geometry.setAttribute('normal', new THREE.BufferAttribute(result, 2, true));
+            mesh.geometry.attributes.normal.bytes = result.length * 2;
 
-                result[idx + 0] = encoded[0];
-                result[idx + 1] = encoded[1];
-                result[idx + 2] = encoded[2];
+        } else if (encodeMethod == "ANGLES") {
+
+            result = new Uint16Array(count * 2);
+
+            for (let idx = 0; idx < array.length; idx += 3) {
+
+                let encoded;
+
+                encoded = this.EncodingFuncs.anglesEncode(array[idx], array[idx + 1], array[idx + 2]);
+
+                result[idx / 3 * 2 + 0] = encoded[0];
+                result[idx / 3 * 2 + 1] = encoded[1];
 
             }
 
-            mesh.geometry.setAttribute('normal', new THREE.BufferAttribute(result, 3, true));
-            mesh.geometry.attributes.normal.bytes = result.length * 1;
+            mesh.geometry.setAttribute('normal', new THREE.BufferAttribute(result, 2, true));
+            mesh.geometry.attributes.normal.bytes = result.length * 2;
 
         } else {
 
@@ -107,7 +122,10 @@ var GeometryCompressionUtils = {
         if (encodeMethod == "ANGLES") {
             mesh.material.defines.USE_PACKED_NORMAL = 0;
         }
-        if (encodeMethod == "OCT") {
+        if (encodeMethod == "OCT1Byte") {
+            mesh.material.defines.USE_PACKED_NORMAL = 1;
+        }
+        if (encodeMethod == "OCT2Byte") {
             mesh.material.defines.USE_PACKED_NORMAL = 1;
         }
         if (encodeMethod == "DEFAULT") {
@@ -120,7 +138,7 @@ var GeometryCompressionUtils = {
     compressPositions: function (mesh) {
 
         if (!mesh.geometry) {
-            console.error("Mesh must contain geometry property. ");
+            console.error("Mesh must contain geometry. ");
         }
 
         let position = mesh.geometry.attributes.position;
@@ -186,10 +204,8 @@ var GeometryCompressionUtils = {
         let count = uvs.count;
 
         for (let i = 0; i < array.length; i++) {
-            // if (array[i] > 1.0) array[i] = array[i] - Math.floor(array[i]);
             range.min = Math.min(range.min, array[i]);
             range.max = Math.max(range.max, array[i]);
-            // console.log(array[i])
         }
 
         console.log(range)
@@ -262,7 +278,7 @@ var GeometryCompressionUtils = {
                 let tmpz = Math.round((z + 1) * 0.5 * 65535);
                 return new Uint16Array([tmpx, tmpy, tmpz]);
             } else {
-                console.error("number of bytes error! ");
+                console.error("number of bytes must be 1 or 2");
             }
         },
 
@@ -280,20 +296,20 @@ var GeometryCompressionUtils = {
                     ((array[2] / 65535) * 2.0) - 1.0,
                 ]
             } else {
-                console.error("number of bytes error! ");
+                console.error("number of bytes must be 1 or 2");
             }
 
         },
 
-        // for `Basic` encoding
-        uInt16Encode: function (x, y, z) {
+        // for `Angles` encoding
+        anglesEncode: function (x, y, z) {
             let normal0 = parseInt(0.5 * (1.0 + Math.atan2(y, x) / Math.PI) * 65535);
             let normal1 = parseInt(0.5 * (1.0 + z) * 65535);
             return new Uint16Array([normal0, normal1]);
         },
 
         // for `OCT` encoding
-        octEncodeBest: function (x, y, z) {
+        octEncodeBest: function (x, y, z, bytes) {
             var oct, dec, best, currentCos, bestCos;
 
             // Test various combinations of ceil and floor
@@ -329,9 +345,6 @@ var GeometryCompressionUtils = {
                 bestCos = currentCos;
             }
 
-            // var angle = Math.acos(bestCos) * 180 / Math.PI;
-            // angle > 1 && console.log(angle)
-
             return best;
 
             function octEncodeVec3(x0, y0, z0, xfunc, yfunc) {
@@ -355,18 +368,34 @@ var GeometryCompressionUtils = {
 
                 }
 
-                return new Int8Array([
-                    Math[xfunc](x * 127.5 + (x < 0 ? 1 : 0)),
-                    Math[yfunc](y * 127.5 + (y < 0 ? 1 : 0))
-                ]);
+                if (bytes == 1) {
+                    return new Int8Array([
+                        Math[xfunc](x * 127.5 + (x < 0 ? 1 : 0)),
+                        Math[yfunc](y * 127.5 + (y < 0 ? 1 : 0))
+                    ]);
+                }
+                if (bytes == 2) {
+                    return new Int16Array([
+                        Math[xfunc](x * 32767.5 + (x < 0 ? 1 : 0)),
+                        Math[yfunc](y * 32767.5 + (y < 0 ? 1 : 0))
+                    ]);
+                }
+
 
             }
 
             function octDecodeVec2(oct) {
                 var x = oct[0];
                 var y = oct[1];
-                x /= x < 0 ? 127 : 128;
-                y /= y < 0 ? 127 : 128;
+
+                if (bytes == 1) {
+                    x /= x < 0 ? 127 : 128;
+                    y /= y < 0 ? 127 : 128;
+                } else if (bytes == 2) {
+                    x /= x < 0 ? 32767 : 32768;
+                    y /= y < 0 ? 32767 : 32768;
+                }
+                
 
                 var z = 1 - Math.abs(x) - Math.abs(y);
 
@@ -450,7 +479,7 @@ var GeometryCompressionUtils = {
                 quantized: quantized,
                 decodeMat: decodeMat
             };
-        }, 
+        },
 
 
         quantizedEncodeUV: function (array, bytes) {
@@ -515,7 +544,7 @@ var GeometryCompressionUtils = {
 
 
 /**
- * PackedPhongMaterial inherited from THREE.MeshPhongMaterial
+ * `PackedPhongMaterial` inherited from THREE.MeshPhongMaterial
  * 
  * @param {*} parameters 
  */
@@ -528,7 +557,7 @@ function PackedPhongMaterial(parameters) {
         THREE.ShaderLib.phong.uniforms,
 
         {
-            quantizeMatPos: { value: null }, 
+            quantizeMatPos: { value: null },
             quantizeMatUV: { value: null }
         }
 

+ 70 - 49
examples/webgl_geometry_compression.html

@@ -11,6 +11,7 @@
 <body>
 	<div id="info">
 		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Geometry Compression Example<br />
+		Octahedron and Quantization encodings from <a>Tarek Sherif @tsherif</a>
 	</div>
 
 	<script type="module">
@@ -31,47 +32,46 @@
 
 		var lights = [];
 
+		// options
 		var data = {
-			wireframe: false,
-			defaultEncodeNormal: false,
-			anglesEncodeNormal: false,
-			octEncodeNormal: false,
-			quantizeEncodePos: false,
-			defaultEncodeUV: false,
-			lightsRotation: 1,
-			totalGPUMemory: "0 bytes"
+			"wireframe": false,
+			"texture": false,
+			"rotationSpeed": 0.1,
+
+			"quantizeEncodePos": false,
+
+			"defaultEncodeNormal": false,
+			"anglesEncodeNormal": false,
+			"oct1bytesEncode": false,
+			"oct2bytesEncode": false,
+
+			"defaultEncodeUV": false,
+
+			"totalGPUMemory": "0 bytes"
 		};
 		var memoryDisplay;
 
+		// geometry params
 		var radius = 100;
-		var detail = 5;
-		var uvScale = [];
+		var detail = 4;
 
 		// materials
-		var lineMaterial = new THREE.LineBasicMaterial({ color: 0xaaaaaa, transparent: true, opacity: 0.5 });
+		var lineMaterial = new THREE.LineBasicMaterial({ color: 0xaaaaaa, transparent: true, opacity: 0.8 });
 		var meshMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x111111 });
 
+		// texture
 		var texture = new THREE.TextureLoader().load("textures/uv_grid_opengl.jpg");
-		// var texture = new THREE.TextureLoader().load("textures/uv_grid_opengl_8k.jpg");
-		// var texture = new THREE.TextureLoader().load("textures/brick_diffuse.jpg");
-
 		texture.wrapS = THREE.RepeatWrapping;
 		texture.wrapT = THREE.RepeatWrapping;
 
-		meshMaterial.map = texture;
-
-
+		//
 		init();
 		animate();
 
+
 		function init() {
 
 			//
-
-			// for (let i = 0; i < 1000000; i++) {
-			// 	uvScale.push(Math.random() * 100);
-			// }
-
 			container = document.createElement('div');
 			document.body.appendChild(container);
 
@@ -81,14 +81,12 @@
 			container.appendChild(renderer.domElement);
 
 			//
-
 			scene = new THREE.Scene();
 
 			camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000000);
-
-			camera.position.x = 3 * radius;
-			camera.position.y = 3 * radius;
-			camera.position.z = 3 * radius;
+			camera.position.x = 2 * radius;
+			camera.position.y = 2 * radius;
+			camera.position.z = 2 * radius;
 
 			controls = new OrbitControls(camera, renderer.domElement);
 
@@ -112,11 +110,11 @@
 			var ballGeom = newGeometry();
 
 			var ballMesh = new THREE.Mesh(ballGeom, meshMaterial);
-			var ballLineSegments = new THREE.LineSegments(ballGeom, lineMaterial);
+			var ballLineSegments = new THREE.LineSegments(new THREE.WireframeGeometry(ballGeom), lineMaterial);
 			ballLineSegments.visible = data.wireframe;
 
-			scene.add(ballLineSegments);
 			scene.add(ballMesh);
+			scene.add(ballLineSegments);
 
 			//
 
@@ -129,12 +127,6 @@
 				// var geom = new THREE.OctahedronBufferGeometry( radius, detail );
 				// var geom = new THREE.BoxBufferGeometry(radius, radius, radius, detail, detail, detail);
 
-				// for (let i = 0; i < geom.attributes.uv.array.length; i += 2) {
-				// 	if (i % 13 == 0){
-				// 		geom.attributes.uv.array[i] = geom.attributes.uv.array[i] * uvScale[i];
-				// 	}
-				// }
-
 				return geom;
 			}
 
@@ -151,15 +143,29 @@
 				ballLineSegments.visible = data.wireframe;
 			}
 
-			var folder = gui.addFolder('THREE.IcosahedronBufferGeometry');
+			var folder = gui.addFolder('Scene');
 			folder.open();
 			folder.add(data, 'wireframe', false).onChange(updateLineSegments);
+			folder.add(data, 'texture', false).onChange(generateGeometry);
+			folder.add(data, 'rotationSpeed', 0, 0.5, 0.1);
+
+			folder = gui.addFolder('Position Compression');
+			folder.open();
+			folder.add(data, 'quantizeEncodePos', false).onChange(generateGeometry);
+
+			folder = gui.addFolder('Normal Compression');
+			folder.open();
 			folder.add(data, 'defaultEncodeNormal', false).onChange(generateGeometry);
 			folder.add(data, 'anglesEncodeNormal', false).onChange(generateGeometry);
-			folder.add(data, 'octEncodeNormal', false).onChange(generateGeometry);
-			folder.add(data, 'quantizeEncodePos', false).onChange(generateGeometry);
+			folder.add(data, 'oct1bytesEncode', false).onChange(generateGeometry);
+			folder.add(data, 'oct2bytesEncode', false).onChange(generateGeometry);
+
+			folder = gui.addFolder('UV Compression');
+			folder.open();
 			folder.add(data, 'defaultEncodeUV', false).onChange(generateGeometry);
-			folder.add(data, 'lightsRotation', 0, 10, 1);
+
+			folder = gui.addFolder('Memory Info');
+			folder.open();
 			memoryDisplay = folder.add(data, 'totalGPUMemory', "0 bytes");
 			computeGPUMemory(ballMesh);
 
@@ -191,7 +197,7 @@
 		function updateLightsPossition() {
 			lights.forEach(light => {
 				var direction = light.position.clone();
-				direction.applyAxisAngle(new THREE.Vector3(1, 1, 0), data.lightsRotation / 180 * Math.PI);
+				direction.applyAxisAngle(new THREE.Vector3(1, 1, 0), data.rotationSpeed / 180 * Math.PI);
 				light.position.add(direction.sub(light.position));
 			});
 		}
@@ -228,19 +234,34 @@
 
 			lineSegments.geometry = new THREE.WireframeGeometry(geometry);
 			mesh.geometry = geometry;
-			mesh.material = meshMaterial;
+			mesh.material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x111111 });
+			mesh.material.map = data.texture ? texture : null;
+
+			var normalEncode = "";
+			if (data.oct1bytesEncode) {
+				/**
+				 * Usually it is not recommended to use 1-byte octahedron normals encoding unless you want to extremely reduce the memory usage
+				 * As it makes vertex data not aligned to a 4 byte boundary which may harm some WebGL implementations and the normal distortion is visible sometimes
+				 * Please refer to @zeux 's comments in https://github.com/mrdoob/three.js/pull/18208
+				 */
+				normalEncode = "OCT1Byte";
+
+			} else if (data.oct2bytesEncode) {
+
+				normalEncode = "OCT2Byte";
+
+			} else if (data.anglesEncodeNormal) {
+
+				normalEncode = "ANGLES";
 
-			var method = "";
-			if (data.anglesEncodeNormal) {
-				method = "ANGLES";
-			} else if (data.octEncodeNormal) {
-				method = "OCT";
 			} else if (data.defaultEncodeNormal) {
-				method = "DEFAULT";
+
+				normalEncode = "DEFAULT";
+
 			}
 
-			if (method != "") {
-				GeometryCompressionUtils.compressNormals(mesh, method);
+			if (normalEncode != "") {
+				GeometryCompressionUtils.compressNormals(mesh, normalEncode);
 			}
 
 			if (data.quantizeEncodePos) {