使用three.js可以方便的讓我們在網頁中做出各種不同的3D效果。如果希望2D繪圖內容,建議使用canvas來進行。但很多小伙伴不清楚到底如何為我們繪制和導入的圖形添加陰影效果,更是不清楚到底如何導入我們已經制作好的3dmax資源。所以這篇教程將簡要介紹如何將我們用3dmax制作好的資源導入進來,以及如何為我們導入的資源,包括所有自己繪制的圖形添加陰影。也有很多小伙伴表示根本記不住這些八股文一般的代碼。其實,每次需要編寫代碼的時候參考官方案例即可,不必背誦代碼。如果編的多,那自然就記住了。如果編的少,我們也沒有必要付出大把時間背誦這些我們很少使用的代碼。
首先,先介紹如何導入3dmax的資源。這里注意,經過我自己的測試,如果直接從本地打開文件的方式打開編寫的網頁,谷歌、IE等瀏覽器將無法顯示我們自己加載的資源,原因是由於本地打開文件后是file協議,所以瀏覽器會因為安全性問題阻止我們加載本地資源。而火狐瀏覽器卻可以正常打開。所以建議大家調試時使用火狐瀏覽器,或者使用tomcat、apache等先建立一個本地服務器,通過域名來訪問自己編寫的網頁。不推薦修改瀏覽器的安全性設置。
我們先用3dmax制作一個圖形,這里選擇其自帶的茶壺。用3dmax制作茶壺的教程網上實在太多,所以這里不再贅述,請不會的小伙伴搜索教程即可,幾步即可搞定。 當然,制作好了之后不要忘記導出。我們需要將其導出成為一個mtl文件和一個obj文件。這一步操作大多制作茶壺的教程也都有,同樣是點點鼠標就行。至於材質等,我們這里不多考慮,畢竟學習要從簡單開始。
導出如上圖的兩個文件之后,我們就可以參考官方的代碼導入我們自己的素材了。
首先,我們除了three.js文件之外,還需要引入個三源文件。一個是OBJLoader.js,一個是MTLLoader.js,一個是DDSLoader.js。這些是官方提供的加載我們本地資源的庫文件,可以從官網下載。https://github.com/mrdoob/three.js/blob/master/examples/webgl_loader_obj_mtl.html 這個網址既是官方案例。我們需要的文件也可以在這里下載到。
以下代碼便是將素材導入的代碼,我們除了像官方那樣導入文件之外,還加入了陰影效果。
1 var onError = function ( xhr ) { }; 2 THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() ); 3 var mtlLoader = new THREE.MTLLoader(); 4 mtlLoader.setPath( './' ); //設置我們需要加載的mtl文件路徑 5 mtlLoader.load( 'lyn.mtl', function( material ) { //這里加載我們需要的文件名 6 material.preload(); 7 var objLoader = new THREE.OBJLoader(); 8 objLoader.setMaterials( material ); //材質,也可自定義 9 objLoader.setPath( './' ); //設置要加載的obj文件的路徑 10 objLoader.load( 'lyn.obj', function ( object ) { //加載obj文件 11 object.position.z = 1; //這里設置我們的素材相對於原來的大小以及旋轉縮放等 12 object.position.y = -0.5; 13 object.scale.x = 0.2; 14 object.scale.y = 0.2; 15 object.scale.z = 0.2; 16 object1 = object; //這里是對素材設置陰影的操作 17 for(var k in object.children){ //由於我們的素材並不是看上去的一個整體,所以需要進行迭代 18 //對其中的所有孩子都設置接收陰影以及投射陰影 19 //才能看到陰影效果 20 object.children[k].castShadow = true; //設置該對象可以產生陰影 21 object.children[k].receiveShadow = true; //設置該對象可以接收陰影 22 } 23 scene.add( object1 ); 24 25 }, onProgress, onError ); 26 });
上述的代碼除了設置陰影以及調整大小之外,都是八股文,需要的時候復制粘貼即可,如果經常從事這方面開發,才建議檢查源代碼的實現。有時我們會發現,即便導入后,我們也無法看到素材。我們需要考慮以下幾方面問題。第一方面,我們是否將我們的3dmax素材做的太大或者太小。太大的話,我們只能看到素材的一部分,造成一種看不到的假象。太小,又會看不清楚或者無法顯示。這種問題就需要各位根據我們攝像機的視距等來調整了。還有一種問題,就是由於我們沒有為我們的素材設置材質,而且我們的代碼中沒有添加光源,導致只顯示黑漆漆的一片。所以,如果要看到這個素材,我們還需要添加光照。
以下是添加聚光燈光源的代碼,因為聚光燈光源可以聚焦,我們演示會方便一些。小伙伴們也可以自己動手嘗試其他光源。記住,我們需要的是點光源或者平行光等光源。環境光是無法生成陰影的。但如果希望周圍顯示的更加清楚,我們也可以同時添加點光源和環境光,只是環境光的光強需要弱一些,避免環境光過強影響陰影的正常顯示。
function SpotLight(){ light = new THREE.SpotLight( '#ffffff' ,1); light.castShadow = true; light.distance = 50; light.angle = 0.6; light.decay = 2; light.penumbra = 0.2; light.position.set( 3, 2, 1 ); light.shadow.camera.near = 1; light.shadow.camera.far = 3; light.shadow.camera.visible = true; light.shadow.mapSize.width = 1024; light.shadow.mapSize.height = 1024; light.target = sp; scene.add(light); }
我們還需要一個地板,將陰影投射到我們的地板上,這樣才能看到陰影。而之前我們講到過receiveShadow這個屬性。假設我們創建了一個添加了材質的圖形sp。我們需要使用sp.receiveShadow=true來讓其可以接收陰影。如果設置為false,會出現什么情況呢?
並沒有生成陰影。那如果我們設置為true,會是什么樣呢?
可以看到,已經生成了陰影。所以,如果我們要讓一個物體可以產生陰影,需要設置castShadow這個屬性為true,而生成了陰影,總需要投射到某個物體上,才能被觀察到。所以,接收投影需要將receiveShadow這個屬性設置為true。
完整的效果如下
以下是完整代碼。其中庫文件以及3dmax的素材文件這里不提供,需要自己生成或者自己下載。也可以只學習陰影的生成方法。代碼編寫略倉促,不過除了各種事件的控制等,其他方面應該還是比較清晰的。歡迎批評之爭。

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <style> 5 html, 6 body { 7 width: 100%; 8 height: 100%; 9 } 10 11 body { 12 margin: 0; 13 } 14 15 canvas { 16 width: 100%; 17 height: 100% 18 } 19 </style> 20 </head> 21 <body> 22 23 <script src="js/three.min.js"></script> 24 <script src="js/jquery-1.12.4.js"></script> 25 <script src="js/OBJLoader.js"></script> 26 <script src="js/MTLLoader.js"></script> 27 <script src="js/DDSLoader.js"></script> 28 <script> 29 var scene = new THREE.Scene(); 30 var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 2000); 31 camera.position.z = 6; 32 camera.position.y = 1; 33 camera.position.x = 2; 34 camera.lookAt(new THREE.Vector3(0, 0, 0)); 35 36 var other = new THREE.Object3D(); 37 other.add(camera); 38 scene.add(other); 39 40 var renderer = new THREE.WebGLRenderer(); 41 renderer.setSize(window.innerWidth, window.innerHeight); 42 document.body.appendChild(renderer.domElement); 43 44 var geometry = new THREE.BoxGeometry(1,1,1); 45 var material = new THREE.MeshPhongMaterial({ 46 color : '#2194ce', 47 specular : '#111111', 48 specular : 10 49 }); 50 var sp = new THREE.Mesh(geometry,material); 51 sp.position.z = -0.5; 52 53 var geometry = new THREE.ConeGeometry( 0.5, 1, 6 ); 54 var material2 = new THREE.MeshPhongMaterial({ 55 color : '#2194ce', 56 specular : '#ffffff', 57 shininess : 100 58 }); 59 var sp2 = new THREE.Mesh(geometry,material2); 60 sp2.position.x = -2.5; 61 sp2.position.z = -1; 62 63 var ball = new THREE.SphereGeometry( 0.5, 32, 32 ); 64 var material3 = new THREE.MeshPhongMaterial({ 65 color : '#2194ce', 66 specular : '#111111', 67 shininess : 100 68 }); 69 var myBall = new THREE.Mesh(ball,material3); 70 myBall.position.z = 1; 71 myBall.position.x = -1; 72 myBall.position.y = -1; 73 myBall.castShadow = true; 74 myBall.receiveShadow = true; 75 76 var light2 = new THREE.SpotLight( '#ffffff' ,1); 77 light2.castShadow = true; 78 light2.distance = 50; 79 light2.angle = 0.3; 80 light2.decay = 2; 81 light2.penumbra = 0.2; 82 light2.position.set( -2, 5, -2 ); 83 light2.shadow.camera.near = 1; 84 light2.shadow.camera.far = 3; 85 light2.shadow.camera.visible = true; 86 light2.shadow.mapSize.width = 1024; 87 light2.shadow.mapSize.height = 1024; 88 light2.target = sp; 89 scene.add(light2); 90 lightHelper2 = new THREE.SpotLightHelper(light2); 91 scene.add(lightHelper2); 92 93 renderer.shadowMap.enabled = true; 94 95 var matFloor = new THREE.MeshPhongMaterial( { color:0x808080 } ); 96 var geoFloor = new THREE.BoxGeometry( 200, 0.1, 200 ); 97 var mshFloor = new THREE.Mesh( geoFloor, matFloor ); 98 var ambient = new THREE.AmbientLight( 0x111111); 99 var lightHelper; 100 101 var light; 102 SpotLight(); 103 lightHelper = new THREE.SpotLightHelper( light ); 104 105 sp.castShadow = true; 106 sp.receiveShadow = true; 107 sp2.castShadow = true; 108 sp2.receiveShadow = true; 109 mshFloor.castShadow = true; 110 mshFloor.receiveShadow = true; 111 mshFloor.position.set( 0, -2, 0 ); 112 113 114 scene.add( mshFloor ); 115 scene.add(sp); 116 scene.add(sp2); 117 scene.add(myBall); 118 scene.add( light ); 119 scene.add(ambient); 120 scene.add(lightHelper); 121 // 0.9854 122 123 //聚光燈光源 124 function SpotLight(){ 125 light = new THREE.SpotLight( '#ffffff' ,1); 126 light.castShadow = true; 127 light.distance = 50; 128 light.angle = 0.6; 129 light.decay = 2; 130 light.penumbra = 0.2; 131 light.position.set( 3, 2, 1 ); 132 light.shadow.camera.near = 1; 133 light.shadow.camera.far = 3; 134 light.shadow.camera.visible = true; 135 light.shadow.mapSize.width = 1024; 136 light.shadow.mapSize.height = 1024; 137 light.target = sp; 138 scene.add(light); 139 } 140 141 //點光源 142 function PointLight(){ 143 light = new THREE.PointLight('#ffffff',1,50,2); 144 light.castShadow = true; 145 light.position.set( 3, 2, 1 ); 146 light.shadow.mapSize.width = 1024; 147 light.shadow.mapSize.height = 1024; 148 scene.add(light); 149 } 150 151 //平行光 152 function DirectLight(){ 153 light = new THREE.DirectionalLight('#ffffff',1); 154 light.castShadow = true; 155 light.position.set( 3, 2, 1 ); 156 light.decay = 2; 157 light.penumbra = 0.2; 158 light.shadow.mapSize.width = 1024; 159 light.shadow.mapSize.height = 1024; 160 scene.add(light); 161 } 162 163 var onProgress = function ( xhr ) { 164 if ( xhr.lengthComputable ) { 165 var percentComplete = xhr.loaded / xhr.total * 100; 166 console.log( Math.round(percentComplete, 2) + '% downloaded' ); 167 } 168 }; 169 170 var onError = function ( xhr ) { }; 171 THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() ); 172 var mtlLoader = new THREE.MTLLoader(); 173 mtlLoader.setPath( './' ); //設置我們需要加載的mtl文件路徑 174 mtlLoader.load( 'lyn.mtl', function( material ) { //這里加載我們需要的文件名 175 material.preload(); 176 var objLoader = new THREE.OBJLoader(); 177 objLoader.setMaterials( material ); //材質,也可自定義 178 objLoader.setPath( './' ); //設置要加載的obj文件的路徑 179 objLoader.load( 'lyn.obj', function ( object ) { //加載obj文件 180 object.position.z = 1; //這里設置我們的素材相對於原來的大小以及旋轉縮放等 181 object.position.y = -0.5; 182 object.scale.x = 0.2; 183 object.scale.y = 0.2; 184 object.scale.z = 0.2; 185 object1 = object; //這里是對素材設置陰影的操作 186 for(var k in object.children){ //由於我們的素材並不是看上去的一個整體,所以需要進行迭代 187 //對其中的所有孩子都設置接收陰影以及投射陰影 188 //才能看到陰影效果 189 object.children[k].castShadow = true; //設置該對象可以產生陰影 190 object.children[k].receiveShadow = true; //設置該對象可以接收陰影 191 } 192 scene.add( object1 ); 193 194 }, onProgress, onError ); 195 }); 196 197 198 var render = function() { 199 requestAnimationFrame(render); 200 lightHelper.update(); 201 202 other.rotation.y += 0.01; 203 sp2.rotation.x += 0.01; 204 205 renderer.render(scene, camera); 206 } 207 208 render(); 209 210 //設置場景不停旋轉 211 var tmp = 0; 212 var timer = setInterval(function(){ 213 if(tmp == 0){ 214 var route = (5 - light.position.y) / 50; 215 light.position.y += route; 216 if(route <= 0.001){ 217 tmp = 1; 218 } 219 }else{ 220 var route = (light.position.y - 1) / 50; 221 light.position.y -= route; 222 if(route <= 0.001){ 223 tmp = 0; 224 } 225 } 226 },15); 227 228 //設置圖中的立方體可以旋轉 229 var left = false; 230 var right = false; 231 var boxLeft = false; 232 var boxRight = false; 233 var boxUp = false; 234 var boxDown = false; 235 var object1 = ''; 236 setInterval(function(){ 237 if(left){ 238 object1.rotation.y -= 0.02; 239 }else if(right){ 240 object1.rotation.y += 0.02; 241 }else if(boxLeft){ 242 sp.rotation.y -= 0.02; 243 }else if(boxRight){ 244 sp.rotation.y += 0.02; 245 }else if(boxUp){ 246 sp.rotation.x -= 0.02; 247 }else if(boxDown){ 248 sp.rotation.x += 0.02; 249 } 250 },25); 251 252 document.onkeydown = function(ev){ 253 var ev = ev || event; 254 if(ev.keyCode == 65) 255 left = true; 256 else if(ev.keyCode == 68) 257 right = true; 258 else if(ev.keyCode == 37) 259 boxLeft = true; 260 else if(ev.keyCode == 38) 261 boxUp = true; 262 else if(ev.keyCode == 39) 263 boxRight = true; 264 else if(ev.keyCode == 40) 265 boxDown = true; 266 else if(ev.keyCode == 80){ 267 scene.remove(light); 268 PointLight(); 269 }else if(ev.keyCode == 83){ 270 scene.remove(light); 271 SpotLight(); 272 }else if(ev.keyCode == 17){ 273 scene.remove(light); 274 DirectLight(); 275 }else if(ev.keyCode == 90){ 276 if(light.intensity < 10) 277 light.intensity += 1; 278 }else if(ev.keyCode == 88){ 279 if(light.intensity > 0) 280 light.intensity -= 1; 281 }else if(ev.keyCode == 67){ 282 scene.remove(sp); 283 geometry = new THREE.BoxGeometry(1,1,1); 284 material = new THREE.MeshPhongMaterial({ 285 color : '#A44A32', 286 specular : '#ffffff', 287 specular : 100 288 }); 289 var sp = new THREE.Mesh(geometry,material); 290 sp.position.z = -0.5; 291 scene.add(sp); 292 }else if(ev.keyCode == 86){ 293 scene.remove(sp); 294 geometry = new THREE.BoxGeometry(1,1,1); 295 material = new THREE.MeshPhongMaterial({ 296 color : '#2194ce', 297 specular : '#111111', 298 specular : 100 299 }); 300 var sp = new THREE.Mesh(geometry,material); 301 sp.position.z = -0.5; 302 scene.add(sp); 303 } 304 } 305 306 document.onkeyup = function(ev){ 307 var ev = ev || event; 308 if(ev.keyCode == 65) 309 left = false; 310 else if(ev.keyCode == 68) 311 right = false; 312 else if(ev.keyCode == 37) 313 boxLeft = false; 314 else if(ev.keyCode == 38) 315 boxUp = false; 316 else if(ev.keyCode == 39) 317 boxRight = false; 318 else if(ev.keyCode == 40) 319 boxDown = false; 320 } 321 322 323 </script> 324 </body> 325 </html>