如何使用Three.js为3D模型构建Color Customizer应用
In this tutorial you’ll learn how to create a customizer app that lets you change the colors of a 3D model of a chair using Three.js.
在本教程中,您將學習如何創建定制器應用程序,該應用程序可讓您使用Three.js更改椅子的3D模型的顏色。
See the demo in action: 3D Model Color Customizer App with Three.js
觀看實際的演示:具有Three.js的3D模型顏色定制程序
快速介紹(A quick introduction)
This tool is built inspired by the Vans shoe customizer, and uses the amazing JavaScript 3D library Three.js.
該工具的靈感來自Vans鞋子定制器,并使用了令人驚嘆JavaScript 3D庫Three.js 。
For this tutorial, I’ll assume you are comfortable with JavaScript, HTML and CSS.
對于本教程,我假設您對JavaScript,HTML和CSS感到滿意。
I’m going to do something a little bit different here in the interest of actually teaching you, and not making you copy/paste parts that aren’t all that relevant to this tutorial,?we’re going to start with all of the CSS in place. The CSS really is just for the dressing around the app, it focusses on the UI only. That being said, each time we paste some HTML, I’ll explain quickly what the CSS does. Let’s get started.
為了實際教您,我將在這里做一些不同的事情,而不是讓您復制/粘貼與本教程無關的部分,我們將從所有CSS開始到位。 CSS確實僅是圍繞應用程序的外觀,它僅關注UI。 話雖如此,每次我們粘貼一些HTML時,我都會快速解釋CSS的作用。 讓我們開始吧。
第1部分:3D模型 (Part 1: The 3D model)
If you want to skip this part entirely, feel free to do so, but it may pay to read it just so you have a deeper understanding of how everything works.
如果您想完全跳過這一部分,請隨時這樣做,但是閱讀它可能有好處,因為這樣您就可以更深入地了解一切。
This isn’t a 3D modelling tutorial, but I will explain how the model is set up in Blender, and if you’d like to create something of your own, change a free model you found somewhere online, or instruct someone you’re commissioning. Here’s some information about how our chairs 3D model is authored.
這不是3D建模教程,但是我將解釋如何在Blender中設置模型,如果您要創建自己的東西,請更改在網上某個地方找到的免費模型,或指示某人調試。 這是有關我們的椅子3D模型是如何創作的一些信息。
The 3D model for this tutorial is hosted and included within the JavaScript, so don’t worry about downloading or having to do any of this unless you’d like to look further into using Blender, and learning how to create your own model.
本教程的3D模型已托管并包含在JavaScript中,因此,除非您想進一步使用Blender并學習如何創建自己的模型,否則不必擔心下載或執行任何此類操作。
規模 (Scale)
The scale is set to approximately what it would be in the real world; I don’t know if this is important, but it feels like the right thing to do, so why not?
該比例設置為大約與現實世界中的比例相同。 我不知道這是否重要,但這聽起來像是對的事,那為什么不呢?
分層和命名約定 (Layering and naming conventions)
This part?is important: each element of the object you want to customize independently needs to be its own object in the 3D scene, and each item needs to have a unique name. Here we have back, base, cushions, legs and supports. Note that if you have say, three items all called supports, Blender is going to name them as supports,?supports.001, supports.002. That doesn’t matter, because in our JavaScript we’ll be using?includes(“supports”)?to find all of those objects that contain the string?supports in it.
這部分很重要:要獨立定制的對象的每個元素在3D場景中都必須是其自己的對象,并且每個項目都必須具有唯一的名稱。 在這里,我們有靠背,底座,靠墊,腿和支撐。 請注意,如果您說了三個項目,都稱為supports ,Blender會將其命名為supports , supports.001,supports.002 。 沒關系,因為在我們JavaScript中,我們將使用include(“ supports”)查找其中包含字符串支持的所有那些對象。
放置 (Placement)
The model should be placed at the world origin, ideally with its feet on the floor. It should ideally be facing the right way, but this can easily be rotated via JavaScript, no harm, no foul.
該模型應放置在世界原點,最好將其腳放在地板上。 理想情況下,它應該面向正確的方向,但是可以很容易地通過JavaScript進行旋轉,沒有危害,沒有犯規。
設置出口 (Setting up for export)
Before exporting, you want to use Blender’s Smart UV unwrap option. Without going too much into detail, this makes textures keep its aspect ratio in tact as it wraps around the different shapes in your model without stretching in weird ways (I’d advise reading up on this option only if you’re making your own model).
導出之前,您要使用Blender的Smart UV unwrap選項。 無需過多介紹細節,這可以使紋理保持縱橫比不變,因為它可以包裹模型中的不同形狀而不會怪異地伸展(建議您僅在制作自己的模型時閱讀此選項)。
You want to be sure to select all of your objects, and apply your transformations. For instance, if you changed the scale or transformed it in any way, you’re telling Blender that this is the new 100% scale, instead of it still being 32.445% scale if you scaled it down a bit.
您要確保選擇所有對象,然后應用轉換。 例如,如果您更改了縮放比例或以任何方式對其進行了轉換,就告訴Bl??ender這是新的100%縮放比例,而不是將其縮小一點仍為32.445%縮放比例。
文件格式 (File Format)
Apparently Three.js supports a bunch of 3D object file formats, but the one it recommends is glTF (.glb). Blender supports this format as an export option, so no worries there.
顯然Three.js支持多種3D對象文件格式,但它推薦的格式是glTF(.glb)。 Blender支持將此格式作為導出選項,因此不用擔心。
第2部分:設置環境 (Part 2: Setting up our environment)
Go ahead and fork this pen, or start your own one and copy the CSS from this pen. This is a blank pen with just the CSS we’re going to be using in this tutorial.
快來拿起這支筆,或者開始自己的一支并從這支筆復制CSS。 這只是一支空白筆,僅包含我們將在本教程中使用CSS。
See the Pen 3D Chair Customizer Tutorial – Blank by Kyle Wetton (@kylewetton) on CodePen.
請參閱CodePen上的Pen 3D Chair Customizer教程–由Kyle Wetton( @kylewetton )撰寫的空白。
If you don’t choose to fork this, grab the HTML as well; it has the responsive meta tags and Google fonts included.
如果您不選擇分叉,也請獲取HTML; 它具有響應性元標記和Google字體。
We’re going to use three dependencies for this tutorial. I’ve included comments above each that describe what they do. Copy these into your HTML, right at the bottom:
在本教程中,我們將使用三個依賴項。 我在每個評論的上方都包含了描述其功能的評論。 將這些內容復制到HTML中的底部:
<!-- The main Three.js file --> <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.min.js'></script><!-- This brings in the ability to load custom 3D objects in the .gltf file format. Blender allows the ability to export to this format out the box --> <script src='https://cdn.jsdelivr.net/gh/mrdoob/Three.js@r92/examples/js/loaders/GLTFLoader.js'></script><!-- This is a simple to use extension for Three.js that activates all the rotating, dragging and zooming controls we need for both mouse and touch, there isn't a clear CDN for this that I can find --> <script src='https://threejs.org/examples/js/controls/OrbitControls.js'></script>Let’s include the canvas element. The entire 3D experience gets rendered into this element, all other HTML will be UI around this. Place the canvas at the bottom of your HTML, above your dependencies.
讓我們包括canvas元素。 整個3D體驗都將渲染到此元素中,所有其他HTML都將是與此相關的UI。 將畫布放在您的依賴項上方HTML底部。
<!-- The canvas element is used to draw the 3D scene --> <canvas id="c"></canvas>Now, we’re going to create a new Scene for Three.js. In your JavaScript, lets make a reference to this scene like so:
現在,我們將為Three.js創建一個新的場景。 在您JavaScript中,讓我們像這樣對這個場景進行引用:
// Init the scene const scene = new THREE.Scene();Below this, we’re going to reference our canvas element
在此之下,我們將引用我們的canvas元素
const canvas = document.querySelector('#c');Three.js requires a few things to run, and we will get to all of them. The first was scene, the second is a renderer. Let’s add this below our canvas reference. This creates a new WebGLRenderer, we’re passing our canvas to it, and we’ve opted in for antialiasing, this creates smoother edges around our 3D model.
Three.js需要一些操作才能運行,我們將全部完成。 第一個是場景,第二個是渲染器。 讓我們將其添加到我們的畫布參考下面。 這將創建一個新的WebGLRenderer,我們將畫布傳遞給它,并且我們選擇了抗鋸齒,這將在3D模型周圍創建更平滑的邊緣。
// Init the renderer const renderer = new THREE.WebGLRenderer({canvas, antialias: true});And now we’re going to append the renderer to the document body
現在,我們將渲染器附加到文檔主體
document.body.appendChild(renderer.domElement);The CSS for the canvas element is just stretching it to 100% height and width of the body, so your entire page has now turned black, because the entire canvas is now black!
canvas元素CSS只是將其拉伸到主體的高度和寬度的100%,因此您的整個頁面現在都變成了黑色,因為整個畫布現在都變成了黑色!
Our scene is black, we’re on the right track here.
我們的場景是黑色的,我們在正確的軌道上。
The next thing Three.js needs is an update loop, basically this is a function that runs on each frame draw and is really important to the way our app will work. We’ve called our update function animate(). Let’s add it below everything else in our JavaScript.
Three.js接下來需要做的是一個更新循環,基本上這是一個在每次繪制框架時都運行的函數,對于我們的應用程序的工作方式確實非常重要。 我們已將更新函數稱為animate() 。 讓我們將其添加到JavaScript的其他所有內容之下。
function animate() {renderer.render(scene, camera);requestAnimationFrame(animate); }animate();Note that we’re referencing a camera here, but we haven’t set one up yet. Let’s add one now.
請注意,我們此處引用的是攝像機,但尚未設置。 現在添加一個。
At the top of your JavaScript, we’ll add a variable called cameraFar. When we add our camera to our scene, it’s going to be added at position 0,0,0. Which is where our chair is sitting! so cameraFar is the variable that tells our camera how far off this mark to move, so that we can see our chair.
在您JavaScript的頂部,我們將添加一個名為cameraFar的變量。 當我們將攝像機添加到場景中時,它將被添加到位置0,0,0。 我們的椅子坐在哪兒! 所以cameraFar是一個變量,它告訴我們的相機該標記移動了多遠,以便我們可以看到椅子。
var cameraFar = 5;Now, above our function animate() {….} lets add a camera.
現在,在我們的函數animate(){…。}上方,添加一個攝像機。
// Add a camera var camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = cameraFar; camera.position.x = 0;This is a perspective camera, with the field of view of 50, the size of the whole window/canvas, and some default clipping planes. The planes determine how near or far the camera should be before the object isn’t rendered. It’s not something we need to pay attention to in our app.
這是一臺透視相機,視野為50,整個窗口/畫布的大小以及一些默認的裁剪平面。 這些平面確定了在不渲染對象之前相機應該接近或遠近。 這不是我們在應用程序中需要注意的事情。
Our scene is still black, let’s set a background color.
我們的場景仍然是黑色的,讓我們設置背景色。
At the top, above our scene reference, add a background color variable called BACKGROUND_COLOR.
在場景引用上方的頂部,添加一個名為Background_COLOR的背景顏色變量。
const BACKGROUND_COLOR = 0xf1f1f1;Notice how we used 0x instead of # in our hex? These are hexadecimal numbers, and the only thing you need to remember about that is that its not a string the way you’d handle a standard #hex variable in JavaScript. It’s an integer and it starts with 0x.
注意我們在十六進制中如何使用0x而不是#? 這些是十六進制數字,關于此,您唯一需要記住的是它不是字符串,而不是您在JavaScript中處理標準#hex變量的方式。 這是一個整數,以0x開頭。
Below our scene reference, let’s update the scenes background color, and add some fog of the same color off in the distance, this is going to help hide the edges of the floor once we add that in.
在場景參考下方,讓我們更新場景的背景色,并在遠處添加一些相同顏色的霧,這將有助于在添加后隱藏地板的邊緣。
const BACKGROUND_COLOR = 0xf1f1f1;// Init the scene const scene = new THREE.Scene();// Set background scene.background = new THREE.Color(BACKGROUND_COLOR ); scene.fog = new THREE.Fog(BACKGROUND_COLOR, 20, 100);Now it’s an empty world. It’s hard to tell that though, because there’s nothing in there, nothing casting shadows. We have a blank scene. Now it’s time to load in our model.
現在是一個空的世界。 但是,很難說出來,因為那里什么也沒有,也沒有投下陰影。 我們有一個空白的場景。 現在是時候加載我們的模型了。
第3部分:加載模型 (Part 3: Loading the model)
We’re going to add the function that loads in models, this is provided by our second dependency we added in our HTML.
我們將添加在模型中加載的函數,這是由我們在HTML中添加的第二個依賴項提供的。
Before we do that though, let’s reference the model, we’ll be using this variable quite a bit. Add this at the top of your JavaScript, above your BACKGROUND_COLOR. Let’s also add a path to the model. I’ve hosted it for us, it’s about 1Mb in size.
在進行此操作之前,讓我們先參考模型,我們將大量使用此變量。 將此添加到您JavaScript頂部,BACKGROUND_COLOR上方。 我們還為模型添加路徑。 我已經為我們托管了它,它的大小約為1Mb。
var theModel; const MODEL_PATH = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/chair.glb";Now we can create a new loader, and use the load method. This sets theModel as our 3D models entire scene. We’re also going to set the size for this app, the right size seems to be about twice as big as it’s loaded. Thirdly, we’re going to offset the y position by -1 to bring it down a little bit, and finally we’re going to add the model to the scene.
現在,我們可以創建一個新的loader ,并使用load方法。 這theModel設置為我們整個場景的3D模型。 我們還將設置該應用程序的大小,正確的大小似乎是已加載大小的兩倍。 第三,我們將y位置偏移-1以將其降低一點,最后我們將模型添加到場景中。
The first parameter is the model’s filepath, the second is a function that runs once the resource is loaded, the third is undefined for now but can be used for a second function that runs while the resource is loading, and the final parameter handles errors.
第一個參數是模型的文件路徑,第二個參數是在加載資源后運行的函數,第三個參數目前尚未定義,但可用于第二個在資源加載時運行的函數,最后一個參數用于處理錯誤。
Add this below our camera.
將此添加到我們的相機下方。
// Init the object loader var loader = new THREE.GLTFLoader();loader.load(MODEL_PATH, function(gltf) {theModel = gltf.scene;// Set the models initial scale theModel.scale.set(2,2,2);// Offset the y position a bittheModel.position.y = -1;// Add the model to the scenescene.add(theModel);}, undefined, function(error) {console.error(error) });At this point you should be seeing a stretched, black, pixelated chair. As awful as it looks, this is right so far. So don’t worry!
此時,您應該看到一張拉伸的黑色像素化椅子。 看起來很糟糕,到目前為止,這是正確的。 所以不用擔心!
Along with a camera, we need lights. The background isn’t affected by lights, but if we added a floor right now, it would also be black (dark). There are a number of lights available for Three.js, and a number of options to tweak all of them. We’re going to add two: a hemisphere light, and a directional light. The settings are also sorted for our app, and they include position and intensity. This is something to play around with if you ever adopt these methods in your own app, but for now, lets use the ones I’ve included. Add these lights below your loader.
除了相機,我們還需要燈光。 背景不受燈光的影響,但是如果我們現在添加一個地板,它也將是黑色(深色)。 Three.js有許多可用的燈,還有許多選項可以對其進行調整。 我們將添加兩個:半球燈和定向燈。 設置也針對我們的應用進行了排序,其中包括位置和強度。 如果您曾經在自己的應用程序中采用這些方法,則可以嘗試使用這些方法,但是現在,讓我們使用我包含的方法。 將這些燈添加到裝載機下方。
// Add lights var hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.61 );hemiLight.position.set( 0, 50, 0 ); // Add hemisphere light to scene scene.add( hemiLight );var dirLight = new THREE.DirectionalLight( 0xffffff, 0.54 );dirLight.position.set( -8, 12, 8 );dirLight.castShadow = true;dirLight.shadow.mapSize = new THREE.Vector2(1024, 1024); // Add directional Light to scene scene.add( dirLight );Your chair looks marginally better! Before we continue, here’s our JavaScript so far:
您的椅子看起來稍微好一點! 在繼續之前,這里是到目前為止JavaScript:
var cameraFar = 5; var theModel;const MODEL_PATH = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/chair.glb";const BACKGROUND_COLOR = 0xf1f1f1; // Init the scene const scene = new THREE.Scene(); // Set background scene.background = new THREE.Color(BACKGROUND_COLOR ); scene.fog = new THREE.Fog(BACKGROUND_COLOR, 20, 100);const canvas = document.querySelector('#c');// Init the renderer const renderer = new THREE.WebGLRenderer({canvas, antialias: true});document.body.appendChild(renderer.domElement);// Add a camerra var camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = cameraFar; camera.position.x = 0;// Init the object loader var loader = new THREE.GLTFLoader();loader.load(MODEL_PATH, function(gltf) {theModel = gltf.scene;// Set the models initial scale theModel.scale.set(2,2,2);// Offset the y position a bittheModel.position.y = -1;// Add the model to the scenescene.add(theModel);}, undefined, function(error) {console.error(error) });// Add lights var hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.61 );hemiLight.position.set( 0, 50, 0 ); // Add hemisphere light to scene scene.add( hemiLight );var dirLight = new THREE.DirectionalLight( 0xffffff, 0.54 );dirLight.position.set( -8, 12, 8 );dirLight.castShadow = true;dirLight.shadow.mapSize = new THREE.Vector2(1024, 1024); // Add directional Light to scene scene.add( dirLight );function animate() {renderer.render(scene, camera);requestAnimationFrame(animate); }animate();Here’s what we should be looking at right now:
這是我們現在應該查看的內容:
Let’s fix the pixelation and the stretching. Three.js needs to update the canvas size when it shifts, and it needs to set its internal resolution not only to the dimensions of the canvas, but also the device pixel ratio of the screen (which is much higher on phones).
讓我們修復像素化和拉伸。 Three.js需要在移動時更新畫布的大小,并且不僅需要將其內部分辨率設置為畫布的尺寸,還需要設置屏幕的設備像素比率(在手機上要高得多)。
Lets head to the bottom of our JavaScript, below where we call animate(), and add this function. This function basically listens to both, the canvas size and the window size, and returns a boolean depending on whether the two sizes are the same or not. We will use that function inside the animate function to determine whether to re-render the scene. This function is also going to take into account the device pixel ratio to be sure that the canvas is sharp on mobile phones too.
讓我們進入JavaScript的底部,在此處調用animate() ,并添加此函數。 此函數基本上偵聽畫布大小和窗口大小,并根據兩個大小是否相同返回布爾值。 我們將在animate函數中使用該函數來確定是否重新渲染場景。 此功能還將考慮設備像素比率,以確保畫布在手機上也很清晰。
Add this function at the bottom of your JavaScript.
在JavaScript底部添加此函數。
function resizeRendererToDisplaySize(renderer) {const canvas = renderer.domElement;var width = window.innerWidth;var height = window.innerHeight;var canvasPixelWidth = canvas.width / window.devicePixelRatio;var canvasPixelHeight = canvas.height / window.devicePixelRatio;const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height;if (needResize) {renderer.setSize(width, height, false);}return needResize; }Now update your animate function to look like this:
現在更新您的動畫函數,如下所示:
function animate() {renderer.render(scene, camera);requestAnimationFrame(animate);if (resizeRendererToDisplaySize(renderer)) {const canvas = renderer.domElement;camera.aspect = canvas.clientWidth / canvas.clientHeight;camera.updateProjectionMatrix();} }Instantly, our chair is looking so much better!
立刻,我們的椅子看起來好多了!
I need to mention a couple things before we continue:
在繼續之前,我需要提及幾件事:
- The chair is backwards, this is my bad. We’re going to simply rotate the model on its Y position 椅子向后,這是我的壞事。 我們將簡單地將模型旋轉到其Y位置
- The supports are black? but the rest is white? This is because the model has some material information that has been imported with it that I had set up in Blender. This doesn’t matter, because we’re going to add a function that lets us define textures in our app, and add them to different areas of the chair when the model loads. So, if you have a wood texture and a denim texture (spoiler: we will), we will have the ability to set these on load without the user having to choose them. So the materials on the chair right now don’t matter all that much. 支架是黑色的? 但是剩下的是白色的? 這是因為模型具有一些我在Blender中設置的材料信息。 沒關系,因為我們要添加一個函數,使我們可以在應用程序中定義紋理,并在加載模型時將其添加到椅子的不同區域。 因此,如果您有木質紋理和牛仔布紋理(擾流板:我們會),我們將能夠在負載下設置它們,而無需用戶選擇。 因此,現在椅子上的材料并不重要。
Humour me quickly, head to the loader function, and remember where we set the scale to (2,2,2)? Lets add this under it:
Swift為我幽默,使用加載程序功能,還記得我們將比例尺設置為(2,2,2)的位置嗎? 讓我們在下面添加它:
// Set the models initial scale theModel.scale.set(2,2,2);theModel.rotation.y = Math.PI;Yeah, much better, sorry about that. One more thing: Three.js doesn’t have support for degrees as far as I know (?), everyone appears to be using Math.PI. This equals 180 degrees, so if you want something angled at a 45 degree angle, you’d use Math.PI / 4.
是的,更好,對此感到抱歉。 還有一件事:就我所知(?)而言,Three.js不支持學位,每個人似乎都在使用Math.PI。 這等于180度,因此,如果您想要以45度角傾斜的物體,則可以使用Math.PI / 4。
Okay, we’re getting there! We need a floor though, without a floor there can’t really be any shadows right?
好的,我們到了! 但是,我們需要一個地板,沒有地板,就不可能有陰影嗎?
Add a floor, what we’re doing here is creating a new plane (a two-dimensional shape, or a three-dimensional shape with no height).
添加一個地板,我們在這里要做的是創建一個新的平面(一個二維形狀,或者一個沒有高度的三維形狀)。
Add this below our lights…
將此添加到我們的燈光下...
// Floor var floorGeometry = new THREE.PlaneGeometry(5000, 5000, 1, 1); var floorMaterial = new THREE.MeshPhongMaterial({color: 0xff0000,shininess: 0 });var floor = new THREE.Mesh(floorGeometry, floorMaterial); floor.rotation.x = -0.5 * Math.PI; floor.receiveShadow = true; floor.position.y = -1; scene.add(floor);Let’s take a look at whats happening here.
讓我們看看這里發生了什么。
First, we made a geometry, we won’t be needing to make another geometry in Three.js in this tutorial, but you can make all sorts.
首先,我們制作了一個幾何圖形,在本教程中,我們不需要在Three.js中制作另一個幾何圖形,但是您可以進行各種修改。
Secondly, notice how we also made a new MeshPhongMaterial and set a couple options. It’s color, and it’s shininess. Check out some of Three.js other materials later on. Phong is great because you can adjust its reflectiveness and specular highlights. There is also MeshStandardMaterial which has support for more advanced texture aspects such as metallic and ambient occlusion, and there is also the MeshBasicMaterial, which doesn’t support shadows. We will just be creating Phong materials in this tutorial.
其次,請注意我們如何制作新的MeshPhongMaterial并設置幾個選項。 它是顏色,它是發光的。 稍后再查看Three.js其他材料。 Phong非常好,因為您可以調整其反射率和鏡面高光。 另外還有MeshStandardMaterial ,它支持更高級的紋理方面,例如金屬和環境光遮擋,還有MeshBasicMaterial ,它不支持陰影。 在本教程中,我們將僅創建Phong材料。
We created a variable called floor and merged the geometry and material into a Mesh.
我們創建了一個名為floor的變量,并將幾何圖形和材質合并為一個Mesh。
We set the floor’s rotation to be flat, opted in for the ability to receive shadows, moved it down the same way we moved the chair down, and then added it to the scene.
我們將地板的旋轉設置為平坦,選擇接收陰影的功能,將其向下移動的方式與將椅子向下移動的方式相同,然后將其添加到場景中。
We should now be looking at this:
現在,我們應該看一下:
We will leave it red for now, but, where are the shadows? There’s a couple of things we need to do for that. First, under our const renderer, lets include a couple of options:
我們暫時將其保留為紅色,但是陰影在哪里? 為此,我們需要做幾件事。 首先,在我們的const renderer下,讓我們包括幾個選項:
// Init the renderer const renderer = new THREE.WebGLRenderer({canvas, antialias: true});renderer.shadowMap.enabled = true; renderer.setPixelRatio(window.devicePixelRatio);We’ve set the pixel ratio to whatever the device’s pixel ratio is, not relevant to shadows, but while we’re there, let’s do that. We’ve also enabled shadowMap, but there are still no shadows? That’s because the materials we have on our chair are the ones brought in from Blender, and we want to author some of them in our app.
我們已經將像素比率設置為設備的像素比率,與陰影無關,但是在此期間,讓我們開始吧。 我們還啟用了shadowMap ,但是仍然沒有陰影嗎? 這是因為我們在椅子上使用的材料是Blender帶來的,因此我們想在我們的應用程序中編寫其中的一些內容。
Our loader function includes the ability to traverse the 3D model. So, head to our loader function and add this in below the theModel = gltf.scene; line. For each object in our 3D model (legs, cushions, etc), we’re going to to enable to option to cast shadows, and to receive shadows. This traverse method will be used again later on.
我們的加載器功能包括遍歷3D模型的功能。 因此,轉到我們的加載器函數,并將其添加到theModel = gltf.scene;下面; 線。 對于3D模型中的每個對象(腿,墊子等),我們將啟用選項以投射陰影并接收陰影。 此遍歷方法將在以后再次使用。
Add this line below theModel = gltf.scene;
將此行添加到theModel = gltf.scene下;
theModel.traverse((o) => {if (o.isMesh) {o.castShadow = true;o.receiveShadow = true;}});It looks arguably worse than it did before, but at least theres a shadow on the floor! This is because our model still has materials brought in from Blender. We’re going to replace all of these materials with a basic, white PhongMaterial.
它看起來可能比以前更糟,但至少地板上有陰影! 這是因為我們的模型仍然具有從Blender引入的材料。 我們將用基本的白色PhongMaterial材料替換所有這些材料。
Lets create another PhongMaterial and add it above our loader function:
讓我們創建另一個PhongMaterial并將其添加到我們的加載器函數上方:
// Initial material const INITIAL_MTL = new THREE.MeshPhongMaterial( { color: 0xf1f1f1, shininess: 10 } );This is a great starting material, it’s a slight off-white, and it’s only a little bit shiny. Cool!
這是很好的起始材料,略帶灰白色,只有一點點光澤。 涼!
We could just add this to our chair and be done with it, but some objects may need a specific color or texture on load, and we can’t just blanket the whole thing with the same base color, the way we’re going to do this is to add this array of objects under our initial material.
我們可以將其添加到椅子上并完成它,但是某些對象在加載時可能需要特定的顏色或紋理,并且我們不能只用相同的基色覆蓋整個對象,我們將采用這種方法這樣做是在我們的初始材質下添加此對象數組。
// Initial material const INITIAL_MTL = new THREE.MeshPhongMaterial( { color: 0xf1f1f1, shininess: 10 } );const INITIAL_MAP = [{childID: "back", mtl: INITIAL_MTL},{childID: "base", mtl: INITIAL_MTL},{childID: "cushions", mtl: INITIAL_MTL},{childID: "legs", mtl: INITIAL_MTL},{childID: "supports", mtl: INITIAL_MTL}, ];We’re going to traverse through our 3D model again and use the childID to find different parts of the chair, and apply the material to it (set in the mtl property). These childID’s match the names we gave each object in Blender, if you read that section, consider yourself informed!
我們將再次遍歷3D模型,并使用childID查找椅子的不同部分,然后將材料應用到該椅子上(在mtl屬性中設置)。 這些childID與我們在Blender中為每個對象指定的名稱相匹配,如果您閱讀了該部分,請認為您已被告知!
Below our loader function, let’s add a function that takes the the model, the part of the object (type), and the material, and sets the material. We’re also going to add a new property to this part called nameID so that we can reference it later.
在我們的加載器函數下方,讓我們添加一個函數,該函數采用模型,對象的一部分(類型)和材質,并設置材質。 我們還將向此部分添加一個名為nameID的新屬性,以便稍后引用。
// Function - Add the textures to the models function initColor(parent, type, mtl) {parent.traverse((o) => {if (o.isMesh) {if (o.name.includes(type)) {o.material = mtl;o.nameID = type; // Set a new property to identify this object}}}); }Now, inside our loader function, just before we add our model to the scene (scene.add(theModel);)
現在,在我們的加載器函數內部,就在將模型添加到場景之前( scene.add(theModel); )
Let’s run that function for each object in our INITIAL_MAP array:
讓我們為INITIAL_MAP數組中的每個對象運行該函數:
// Set initial texturesfor (let object of INITIAL_MAP) {initColor(theModel, object.childID, object.mtl);}Finally, head back to our floor, and change the color from red (0xff0000) to a light grey(0xeeeeee).
最后,回到地板,將顏色從紅色(0xff0000)更改為淺灰色(0xeeeeee)。
// Floor var floorGeometry = new THREE.PlaneGeometry(5000, 5000, 1, 1); var floorMaterial = new THREE.MeshPhongMaterial({color: 0xeeeeee, // <------- Hereshininess: 0 });It’s worth mentioning here that 0xeeeeee is different to our background color. I manually dialed this in until the floor with the lights shining on it matched the lighter background color. We’re now looking at this:
這里值得一提的是0xeeeeee與我們的背景色不同。 我手動撥入,直到地板上的燈光與較淺的背景色匹配為止。 我們現在在看這個:
See the Pen 3D Chair Customizer Tutorial – Part 1 by Kyle Wetton (@kylewetton) on CodePen.
請參閱CodePen上的Kyle Wetton( @kylewetton )編寫的Pen 3D Chair Customizer教程–第1部分。
Congratulations, we’ve got this far! If you got stuck anywhere, fork this pen or investigate it until you find the issue.
恭喜,我們已經做到了! 如果您在任何地方都被卡住,請叉這支筆或對其進行調查,直到發現問題為止。
第4部分:添加控件 (Part 4: Adding controls)
For real though this is a very small part, and is super easy thanks to our third dependency OrbitControls.js.
實際上,這只是很小的一部分,并且由于我們的第三個依賴OrbitControls.js而非常容易。
Above our animate function, we add this in our controls:
在動畫功能之上,我們將其添加到控件中:
// Add controls var controls = new THREE.OrbitControls( camera, renderer.domElement ); controls.maxPolarAngle = Math.PI / 2; controls.minPolarAngle = Math.PI / 3; controls.enableDamping = true; controls.enablePan = false; controls.dampingFactor = 0.1; controls.autoRotate = false; // Toggle this if you'd like the chair to automatically rotate controls.autoRotateSpeed = 0.2; // 30Inside the animate function, at the top, add:
在動畫功能的頂部,添加:
controls.update();So our controls variable is a new OrbitControls class. We’ve set a few options that you can change here if you’d like. These include the range in which the user is allowed to rotate around the chair (above and below). We’ve disabled panning to keep the chair centered, enabled dampening to give it weight, and included auto rotate ability if you choose to use them. This is currently set to false.
因此,我們的控件變量是一個新的OrbitControls類。 我們設置了一些選項,您可以根據需要在此處進行更改。 這些范圍包括允許用戶圍繞椅子(上方和下方)旋轉的范圍。 我們禁用了平移功能以保持椅子居中,啟用了減震功能使其具有重量,并且如果您選擇使用自動旋轉功能,則還具有自動旋轉功能。 當前設置為false。
Try click and drag your chair, you should be able to explore the model with full mouse and touch functionality!
嘗試單擊并拖動椅子,您應該能夠使用完整的鼠標和觸摸功能探索模型!
See the Pen Scrollable by Kyle Wetton (@kylewetton) on CodePen.
見筆滾動凱爾Wetton( @kylewetton上) CodePen 。
第5部分:更改顏色 (Part 5: Changing colors)
Our app currently doesn’t do anything, so this next part will focus on changing our colors. We’re going to add a bit more HTML. Afterwards, I’ll explain a bit about what the CSS is doing.
我們的應用程序當前不執行任何操作,因此下一部分將重點介紹更改顏色。 我們將添加更多HTML。 然后,我將解釋一下CSS的功能。
Add this below your canvas element:
將其添加到您的canvas元素下面:
<div class="controls"> <!-- This tray will be filled with colors via JS, and the ability to slide this panel will be added in with a lightweight slider script (no dependency used for this) --><div id="js-tray" class="tray"><div id="js-tray-slide" class="tray__slide"></div></div> </div>Basically, the .controls DIV is stuck to the bottom of the screen, the .tray is set to be 100% width of the body, but its child, .tray__slide is going to fill with swatches and can be as wide as it needs. We’re going to add the ability to slide this child to explore colors as one of the final steps of this tutorial.
基本上, .controls DIV粘貼在屏幕的底部, .tray設置為主體寬度的100%,但其子級.tray__slide將填充色板,并且可以根據需要的寬度來設置。 作為本教程的最后步驟,我們將添加使這個孩子滑動以探索顏色的功能。
Let’s start by adding in a couple colors. At the top of our JavaScript, lets add an array of five objects, each with a color property.
讓我們從添加幾種顏色開始。 在我們JavaScript頂部,讓我們添加五個對象的數組,每個對象都有一個color屬性。
const colors = [ {color: '66533C' }, {color: '173A2F' }, {color: '153944' }, {color: '27548D' }, {color: '438AAC' } ]Note that these neither have # or 0x to represent the hex. We will use these colors for both in functions. Also, it’s an object because we will be able to add other properties to this color, like shininess, or even a texture image (spoiler: we will, and we will).
請注意,這些都沒有#或0x代表十六進制。 我們將在函數中同時使用這些顏色。 同樣,它也是一個對象,因為我們將能夠為該顏色添加其他屬性,例如光澤度,甚至是紋理圖像(破壞者:我們會,而且將會)。
Lets make swatches out of these colors!
讓我們用這些顏色制作色板!
First, let’s reference our tray slider at the top of our JavaScript:
首先,讓我們在JavaScript頂部引用托盤滑塊:
const TRAY = document.getElementById('js-tray-slide');Right at the bottom of our JavaScript, lets add a new function called buildColors and immediately call it.
在我們JavaScript底部,讓我們添加一個名為buildColors的新函數,然后立即調用它。
// Function - Build Colors function buildColors(colors) {for (let [i, color] of colors.entries()) {let swatch = document.createElement('div');swatch.classList.add('tray__swatch');swatch.style.background = "#" + color.color;swatch.setAttribute('data-key', i);TRAY.append(swatch);} }buildColors(colors);We’re now creating swatches out of our colors array! Note that we set the data-key attribute to the swatch, we’re going to use this to look up our color and make them into materials.
我們現在要從顏色陣列中創建色板! 請注意,我們將data-key屬性設置為樣本,我們將使用它來查找顏色并將它們變成材質。
Below our new buildColors function, let’s add an event handler to our swatches:
在新的buildColors函數下方,讓我們向樣本添加事件處理程序:
// Swatches const swatches = document.querySelectorAll(".tray__swatch");for (const swatch of swatches) {swatch.addEventListener('click', selectSwatch); }Our click handler calls a function called selectSwatch. This function is going to build a new PhongMaterial out of the color and call another function to traverse through our 3d model, find the part it’s meant to change, and update it!
我們的點擊處理程序調用了一個名為selectSwatch的函數。 該函數將用顏色構建新的PhongMaterial,并調用另一個函數來遍歷我們的3d模型,找到要更改的部分并進行更新!
Below the event handlers we just added, add the selectSwatch function:
在我們剛剛添加的事件處理程序下方,添加selectSwatch函數:
function selectSwatch(e) {let color = colors[parseInt(e.target.dataset.key)];let new_mtl;new_mtl = new THREE.MeshPhongMaterial({color: parseInt('0x' + color.color),shininess: color.shininess ? color.shininess : 10});setMaterial(theModel, 'legs', new_mtl); }This function looks up our color by its data-key attribute, and creates a new material out of it.
此函數通過其data-key屬性查找我們的顏色,并以此創建新的材質。
This won’t work yet, we need to add the setMaterial function, (see the final line of the function we just added).
這還行不通,我們需要添加setMaterial函數(請參閱剛剛添加的函數的最后一行)。
Take note of this line: setMaterial(theModel, ‘legs’, new_mtl);. Currently we’re just passing ‘legs’ to this function, soon we will add the ability to change out the different sections we want to update. But first, lets add the zcode>setMaterial
注意這一行: setMaterial(theModel,'legs',new_mtl); 。 當前,我們只是將“腿”傳遞給此功能,不久我們將添加更改要更新的不同部分的功能。 但首先,讓我們添加zcode> setMaterial
function.
功能。
Below this function, add the setMaterial function:
在此函數下面,添加setMaterial函數:
function setMaterial(parent, type, mtl) {parent.traverse((o) => {if (o.isMesh && o.nameID != null) {if (o.nameID == type) {o.material = mtl;}}}); }This function is similar to our initColor function, but with a few differences. It checks for the nameID we added in the initColor, and if its the same as the parameter type, it adds the material to it.
此函數類似于我們的initColor函數,但有一些區別。 它檢查我們在initColor中添加的initColor ,如果它與參數類型相同,則向其中添加材質。
Our swatches can now create a new material, and change the color of the legs, give it a go! Here’s everything we have so far in a pen. Investigate it if you’re lost.
我們的色板現在可以創建新的材料,并改變腿的顏色,快去嘗試吧! 到目前為止,這是我們筆所能擁有的一切。 如果您迷路了,請進行調查。
See the Pen Swatches change the legs color! by Kyle Wetton (@kylewetton) on CodePen.
看到筆色板改變腿的顏色! 由Kyle Wetton( @kylewetton )在CodePen上編寫。
第6部分:選擇要更改的部分 (Part 6: Selecting the parts to change)
We can now change the color of the legs, which is awesome, but let’s add the ability to select the part our swatch should add its material to. Include this HTML just below the opening body tag, I’ll explain the CSS below.
現在,我們可以更改腿的顏色,這太棒了,但是讓我們添加選擇樣本應為其材料添加零件的功能。 將HTML包含在開始body標簽下面,我將在下面解釋CSS。
<!-- These toggle the the different parts of the chair that can be edited, note data-option is the key that links to the name of the part in the 3D file --> <div class="options"><div class="option --is-active" data-option="legs"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/legs.svg" alt=""/></div><div class="option" data-option="cushions"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/cushions.svg" alt=""/></div><div class="option" data-option="base"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/base.svg" alt=""/></div><div class="option" data-option="supports"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/supports.svg" alt=""/></div><div class="option" data-option="back"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/back.svg" alt=""/></div> </div>This is just a collection of buttons with custom icons in each. The .options DIV is stuck to the side of the screen via CSS (and shifts a bit with media queries). Each .option DIV is just a white square, that has a red border on it when a –is-active class is added to it. It also includes a data-option attribute that matches our nameID, so we can identify it. Lastly, the image element has a CSS property called pointer-events: none so that the event stays on the parent even if you click the image.
這只是每個按鈕中帶有自定義圖標的集合。 .options DIV通過CSS固定在屏幕的一側(并隨媒體查詢而移動一點)。 每個.option DIV只是一個白色正方形,當添加–is-active類時,上面帶有紅色邊框。 它還包括一個與我們的nameID匹配的data-option屬性,因此我們可以對其進行識別。 最后,image元素具有一個稱為pointer-eventsCSS屬性:none,因此即使您單擊該圖像,該事件也將保留在父級上。
Lets add another variable at the top of our JavaScript called activeOptions and by default let’s set it to ‘legs’:
讓我們在JavaScript的頂部添加另一個名為activeOptions的變量,默認情況下將其設置為'legs':
var activeOption = 'legs';Now head back to our selectSwatch function and update that hard-coded ‘legs’ parameter to activeOption
現在回到我們的selectSwatch函數,并將該硬編碼的“ legs”參數更新為activeOption
setMaterial(theModel, activeOption, new_mtl);Now all we need to do is create a event handler to change out activeOption when an option is clicked!
現在,我們需要做的就是創建一個事件處理程序,以在單擊選項時更改activeOption !
Let’s add this above our const swatches and selectSwatch function.
讓我們將其添加到const色板和selectSwatch函數之上。
// Select Option const options = document.querySelectorAll(".option");for (const option of options) {option.addEventListener('click',selectOption); }function selectOption(e) {let option = e.target;activeOption = e.target.dataset.option;for (const otherOption of options) {otherOption.classList.remove('--is-active');}option.classList.add('--is-active'); }We’ve added the selectOption function, which sets the activeOption to our event targets data-option value, and toggles the –is-active class. Thats it!
我們添加了selectOption函數,該函數將selectOption設置為事件目標數據選項值,并切換–is-active類。 而已!
Try it out
試試看
See the Pen Changing options by Kyle Wetton (@kylewetton) on CodePen.
見筆更改選項凱爾Wetton( @kylewetton )上CodePen 。
But why stop here? An object could look like anything, it can’t all be the same material. A chair with no wood or fabric? Lets expand our color selection a little bit. Update your color array to this:
但是為什么在這里停下來? 一個對象可能看起來像任何東西,它不可能都是相同的材料。 沒有木頭或織物的椅子? 讓我們擴展一下顏色選擇。 將顏色數組更新為:
const colors = [ {texture: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/wood.jpg',size: [2,2,2],shininess: 60 }, {texture: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/denim.jpg',size: [3, 3, 3],shininess: 0 }, {color: '66533C' }, {color: '173A2F' }, {color: '153944' }, {color: '27548D' }, {color: '438AAC' } ]The top two are now textures. We’ve got wood and denim. We also have two new properties, size and shininess. Size is how often to repeat a pattern, so the larger the number, the more dense the pattern is, or more simply put – the more it repeats.
現在,前兩個是紋理。 我們有木頭和牛仔布。 我們還有兩個新屬性,大小和光澤。 大小是重復圖案的頻率,因此數字越大,圖案越密集,或更簡單地放置-重復越多。
There are two function we need to update to add this ability. Firstly, lets head to the buildColors function and update to this
我們需要更新兩個功能以添加此功能。 首先,讓我們轉到buildColors函數并對此進行更新
// Function - Build Colorsfunction buildColors(colors) {for (let [i, color] of colors.entries()) {let swatch = document.createElement('div');swatch.classList.add('tray__swatch');if (color.texture){swatch.style.backgroundImage = "url(" + color.texture + ")"; } else{swatch.style.background = "#" + color.color;}swatch.setAttribute('data-key', i);TRAY.append(swatch);} }Now its checking to see if its a texture, if it is, it’s going to set the swatches background to be that texture, neat!
現在,它會檢查其紋理是否存在(如果有的話),它將色板背景設置為該紋理整齊!
Notice the gap between the 5th and 6th swatch? The final batch of colors, which I will provide, is grouped into color schemes of 5 colors per scheme. So each scheme will have that small divider in it, this is set in the CSS and will make more sense in the final product.注意到第5個和第6個樣本之間的差距了嗎? 我將提供的最后一批顏色分為每種方案5種顏色的配色方案。 因此,每個方案中都會有一個小的分隔符,它在CSS中設置,并且在最終產品中更有意義。The second function we’re going to update is the selectSwatch function. Update it to this:
我們要更新的第二個函數是selectSwatch函數。 將其更新為:
function selectSwatch(e) {let color = colors[parseInt(e.target.dataset.key)];let new_mtl;if (color.texture) {let txt = new THREE.TextureLoader().load(color.texture);txt.repeat.set( color.size[0], color.size[1], color.size[2]);txt.wrapS = THREE.RepeatWrapping;txt.wrapT = THREE.RepeatWrapping;new_mtl = new THREE.MeshPhongMaterial( {map: txt,shininess: color.shininess ? color.shininess : 10}); } else{new_mtl = new THREE.MeshPhongMaterial({color: parseInt('0x' + color.color),shininess: color.shininess ? color.shininess : 10});}setMaterial(theModel, activeOption, new_mtl); }To explain what’s going on here, this function will now check if it’s a texture. If it is, it’s going to create a new texture using the Three.js TextureLoader method, it’s going to set the texture repeat using our size values, and set the wrapping of it (this wrapping option seems to work best, I’ve tried the others, so lets go with it), then its going to set the PhongMaterials map property to the texture, and finally use the shininess value.
為了解釋這里發生了什么,該函數現在將檢查它是否是紋理。 如果是這樣,它將使用Three.js TextureLoader方法創建一個新紋理,它將使用我們的大小值設置紋理重復,并設置其包裹(此包裹選項似乎效果最好,我已經嘗試過其他,所以讓我們繼續吧),然后將PhongMaterials貼圖屬性設置為紋理,最后使用光澤值。
If it’s not a texture, it uses our older method. Note that you can set a shininess property to any of our original colors!
如果不是紋理,則使用我們的舊方法。 請注意,您可以為我們的任何原始顏色設置光澤屬性!
Important: if your textures just remain black when you try add them. Check your console. Are you getting cross domain CORS errors? This is a CodePen bug and I’ve done my best to try fix it. These assets are hosted directly in CodePen via a Pro feature so its unfortunate to have to battle with this. Apparently, the best bet here is to not visit those image URLs directly, otherwise I recommend signing up to Cloudinary and using their free tier, you may have better luck pointing your textures there.
重要提示:如果您嘗試添加紋理時仍保持黑色。 檢查您的控制臺。 您是否收到跨域CORS錯誤? 這是一個CodePen錯誤,我已盡力嘗試修復它。 這些資產通過Pro功能直接托管在CodePen中,因此不幸的是必須與之抗衡。 顯然,這里最好的選擇是不要直接訪問這些圖像URL,否則我建議注冊Cloudinary并使用其免費套餐,這樣可能更好地將紋理指向那里。
Here’s a pen with the textures working on my end at least:
這是一支至少具有紋理效果的筆:
See the Pen Texture support by Kyle Wetton (@kylewetton) on CodePen.
見筆紋理支持由凱爾Wetton( @kylewetton )上CodePen 。
第七部分:畫龍點睛 (Part 7: Finishing touches)
I’ve had projects get run passed clients with a big button that is begging to be pressed, positively glistening with temptation to even just hover over it, and them and their co-workers (Dave from accounts) come back with feedback about how they didn’t know there was anything to be pressed (screw you, Dave).
我曾經讓項目通過一個很大的按鈕來傳遞給客戶,這個按鈕希望被按下,即使只是將鼠標懸停在這個按鈕上,他們也會積極地閃閃發光,他們和他們的同事(來自帳戶的Dave)回來時會收到有關他們如何反饋的反饋不知道有什么要緊迫的(戴夫,打給你)。
So let’s add some calls to action. First, let’s chuck in a patch of HTML above the canvas element:
因此,讓我們添加一些號召性用語。 首先,讓我們在canvas元素上方插入HTML補丁:
<!-- Just a quick notice to the user that it can be interacted with --> <span class="drag-notice" id="js-drag-notice">Drag to rotate 360°</span>The CSS places this call-to-action above the chair, it’s a nice big button that instructs the user to drag to rotate the chair. It just stays there though? We will get to that.
CSS將號召性用語放置在椅子上方,這是一個不錯的大按鈕,它指示用戶拖動以旋轉椅子。 它只是留在那里嗎? 我們將做到這一點。
Let’s spin the chair once it’s loaded first, then, once the spin is done, let’s hide that call-to-action.
首先加載椅子,然后旋轉,然后旋轉完成,然后隱藏號召性用語。
First, lets add a loaded variable to the top of our JavaScript and set it to false:
首先,讓我們在JavaScript的頂部添加一個已加載的變量,并將其設置為false:
var loaded = false;Right at the bottom of your JavaScript, add this function
在您JavaScript底部,添加此函數
// Function - Opening rotate let initRotate = 0;function initialRotation() {initRotate++; if (initRotate <= 120) {theModel.rotation.y += Math.PI / 60;} else {loaded = true;} }This simply rotates the the model 360 degrees within the span of 120 frames (around 2 seconds at 60fps), and we’re going to run this in the animate function to call it for 120 frames, once its done, it’s going to set loaded to true in our animate function. Here’s how it will look in its entirely with the new code at the end there:
只需在120幀的范圍內(360 fps時約2秒)將模型旋轉360度,我們將在animate函數中運行此模型以調用120幀,一旦完成,將設置加載在我們的animate功能中為真。 下面是新代碼的整體外觀:
function animate() {controls.update();renderer.render(scene, camera);requestAnimationFrame(animate);if (resizeRendererToDisplaySize(renderer)) {const canvas = renderer.domElement;camera.aspect = canvas.clientWidth / canvas.clientHeight;camera.updateProjectionMatrix();}if (theModel != null && loaded == false) {initialRotation();} }animate();We check that theModel doesn’t equal null, and that the variable loaded is false, and we run that function for 120 frames, at which point the function switches to loaded = true, and our animate function ignores it.
我們檢查theModel不等于null ,并且加載的變量為false,然后將該函數運行120幀,這時該函數切換為load = true ,而我們的動畫函數會忽略它。
You should have a nice spinning chair. When that chair stops is a great time to remove our call-to-action.
您應該有一把漂亮的旋轉椅。 當椅子停下來時,是刪除我們的號召性用語的好時機。
In the CSS, there’s a class that can be added to that call-to-action that will hide it with an animation, this animation has a delay of 3 seconds, so let’s add that class at the same time the rotation starts.
在CSS中,可以在該號召性用語中添加一個類,該類將通過動畫進行隱藏,該動畫的延遲時間為3秒,因此讓我們在旋轉開始的同時添加該類。
At the top of your JavaScript we will reference it:
在您JavaScript頂部,我們將引用它:
const DRAG_NOTICE = document.getElementById('js-drag-notice');and update your animate function like so
并像這樣更新您的動畫功能
if (theModel != null && loaded == false) {initialRotation();DRAG_NOTICE.classList.add('start');}Great! Okay, here’s some more colors, update your color array, I’ve give a lightweight sliding function below it:
大! 好的,這里有更多顏色,更新您的顏色陣列,我在其下方提供了輕量級的滑動功能:
const colors = [ {texture: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/wood_.jpg',size: [2,2,2],shininess: 60 }, {texture: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/fabric_.jpg',size: [4, 4, 4],shininess: 0 }, {texture: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/pattern_.jpg',size: [8, 8, 8],shininess: 10 }, {texture: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/denim_.jpg',size: [3, 3, 3],shininess: 0 }, {texture: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/quilt_.jpg',size: [6, 6, 6],shininess: 0 }, {color: '131417' }, {color: '374047' }, {color: '5f6e78' }, {color: '7f8a93' }, {color: '97a1a7' }, {color: 'acb4b9' }, {color: 'DF9998', }, {color: '7C6862' }, {color: 'A3AB84' }, {color: 'D6CCB1' }, {color: 'F8D5C4' }, {color: 'A3AE99' }, {color: 'EFF2F2' }, {color: 'B0C5C1' }, {color: '8B8C8C' }, {color: '565F59' }, {color: 'CB304A' }, {color: 'FED7C8' }, {color: 'C7BDBD' }, {color: '3DCBBE' }, {color: '264B4F' }, {color: '389389' }, {color: '85BEAE' }, {color: 'F2DABA' }, {color: 'F2A97F' }, {color: 'D85F52' }, {color: 'D92E37' }, {color: 'FC9736' }, {color: 'F7BD69' }, {color: 'A4D09C' }, {color: '4C8A67' }, {color: '25608A' }, {color: '75C8C6' }, {color: 'F5E4B7' }, {color: 'E69041' }, {color: 'E56013' }, {color: '11101D' }, {color: '630609' }, {color: 'C9240E' }, {color: 'EC4B17' }, {color: '281A1C' }, {color: '4F556F' }, {color: '64739B' }, {color: 'CDBAC7' }, {color: '946F43' }, {color: '66533C' }, {color: '173A2F' }, {color: '153944' }, {color: '27548D' }, {color: '438AAC' } ]Awesome! These hang off the page though, right at the bottom of your JavaScript, add this function, it will allow you to drag the swatches panel with mouse and touch. For the interest of keeping on topic, I won’t delve too much into how it works.
太棒了! 盡管這些掛起在頁面的右側,但是就在JavaScript底部,添加了此功能,它將使您能夠使用鼠標和觸摸來拖動色板。 為了保持話題的興趣,我不會過多地研究它的工作原理。
var slider = document.getElementById('js-tray'), sliderItems = document.getElementById('js-tray-slide'), difference;function slide(wrapper, items) {var posX1 = 0,posX2 = 0,posInitial,threshold = 20,posFinal,slides = items.getElementsByClassName('tray__swatch');// Mouse eventsitems.onmousedown = dragStart;// Touch eventsitems.addEventListener('touchstart', dragStart);items.addEventListener('touchend', dragEnd);items.addEventListener('touchmove', dragAction);function dragStart (e) {e = e || window.event;posInitial = items.offsetLeft;difference = sliderItems.offsetWidth - slider.offsetWidth;difference = difference * -1;if (e.type == 'touchstart') {posX1 = e.touches[0].clientX;} else {posX1 = e.clientX;document.onmouseup = dragEnd;document.onmousemove = dragAction;}}function dragAction (e) {e = e || window.event;if (e.type == 'touchmove') {posX2 = posX1 - e.touches[0].clientX;posX1 = e.touches[0].clientX;} else {posX2 = posX1 - e.clientX;posX1 = e.clientX;}if (items.offsetLeft - posX2 <= 0 && items.offsetLeft - posX2 >= difference) {items.style.left = (items.offsetLeft - posX2) + "px";}}function dragEnd (e) {posFinal = items.offsetLeft;if (posFinal - posInitial < -threshold) { } else if (posFinal - posInitial > threshold) {} else {items.style.left = (posInitial) + "px";}document.onmouseup = null;document.onmousemove = null;}}slide(slider, sliderItems);Now, head to your CSS and under .tray__slider, uncomment this small animation
現在,轉到CSS并在.tray__slider下,取消注釋此小動畫
/* transform: translateX(-50%);animation: wheelin 1s 2s ease-in-out forwards; */Okay, let’s finish it off with a the final two touches, and we’re done!
好的,讓我們完成最后的兩次接觸,就可以完成了!
Let’s update our .controls div to include this extra call-to-action:
讓我們更新.controls div以包括以下額外的號召性用語:
<div class="controls"> <div class="info"><div class="info__message"><p><strong> Grab </strong> to rotate chair. <strong> Scroll </strong> to zoom. <strong> Drag </strong> swatches to view more.</p></div> </div><!-- This tray will be filled with colors via JS, and the ability to slide this panel will be added in with a lightweight slider script (no dependency used for this) --><div id="js-tray" class="tray"><div id="js-tray-slide" class="tray__slide"></div></div> </div>Note that we have a new info section that includes some instructions on how to control the app.
請注意,我們有一個新的信息部分,其中包含有關如何控制應用程序的一些說明。
Finally, let’s add a loading overlay so that our app is clean while everything loads, and we will remove it once the model is loaded.
最后,讓我們添加一個加載疊加層,以便在加載所有內容時我們的應用程序保持干凈,并且在加載模型后將其刪除。
Add this to the top of our HTML, below the body tag.
將此添加到我們HTML頂部,在body標記下方。
<!-- The loading element overlays all else until the model is loaded, at which point we remove this element from the DOM --> <div class="loading" id="js-loader"><div class="loader"></div></div>Here’s the thing about our loader, in order for it to load first, we’re going to add the CSS to the head tag instead of being included in the CSS. So simply add this CSS just above the closing head tag.
這是關于我們的加載器的事情,為了使其首先加載,我們將CSS添加到head標簽中,而不是包含在CSS中。 因此,只需在封閉的head標簽上方添加此CSS。
Almost there! Let’s remove it once the model is loaded.
差不多好了! 加載模型后,將其刪除。
At the top of our JavaScript, lets reference it:
讓我們在JavaScript的頂部引用它:
const LOADER = document.getElementById('js-loader');Then in our loader function, after scene.add(theModel), include this line
然后在我們的加載器函數中,位于scene.add(theModel)之后,包括以下行
// Remove the loaderLOADER.remove();Now our app loads behind this DIV, polishing it off:
現在,我們的應用程序將在此DIV后面加載,并對其進行完善:
And that’s it! Here’s the completed pen for reference.
就是這樣! 這是完成的筆供參考。
See the Pen 3D Chair Customizer Tutorial – Part 4 by Kyle Wetton (@kylewetton) on CodePen.
請參閱CodePen上的Kyle Wetton( @kylewetton )編寫的Pen 3D Chair Customizer教程–第4部分。
You can also check out the demo hosted here on Codrops.
您也可以查看Codrops上托管的演示。
謝謝您的支持! (Thank you for sticking with me!)
This is a big tutorial. If you feel I made a mistake somewhere, please let me know in the comments, and thanks again for following with me as we create this absolute unit.
這是一個很大的教程。 如果您覺得我在某個地方犯了一個錯誤,請在評論中讓我知道,并再次感謝您在我們創建此絕對單位時關注我。
翻譯自: https://tympanus.net/codrops/2019/09/17/how-to-build-a-color-customizer-app-for-a-3d-model-with-three-js/
總結
以上是生活随笔為你收集整理的如何使用Three.js为3D模型构建Color Customizer应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 双屏显示
- 下一篇: 数据库连接中的等值连接、自然连接、外连接