As we transition into the world of augmented and virtual reality, we will see normal applications interact with 3D models and animations everywhere. Let us learn how we can generate a 360 degree animated image from a 3D model with Nuxt.Js
The final project can be viewed on Codesandbox.
You can find the full source code on my Github repository.
We will be using Nuxt.Js in this project. It is a performance and easy-to-use Vue.Js framework. To get started, ensure you have either Yarn or NPM v5.1+ or v6.2+ installed. Open your directory of choice and run the following command in the terminal.
yarn create nuxt-app nuxtjs-3d-model-to-360-gif
# OR
npm init nuxt-app nuxtjs-3d-model-to-360-gif
# OR
npx create-nuxt-app nuxtjs-3d-model-to-360-gif
Code language: PHP (php)
The above command will trigger a set of setup questions. Here are our recommended defaults:
Project name: uxtjs-3d-model-to-360-gif Programming language: JavaScript Package manager: Yarn UI framework: Tailwind CSS Nuxt.js modules: N/A Linting tools: N/A Testing framework: None Rendering mode: Universal (SSR/SSG) Deployment target: Server (Node.js hosting) Development tools: N/A
Once the setup is complete, you may enter and run your project. The app will be accessible through the URL https://localhost:3000.
cd nuxtjs-3d-model-to-360-gif
yarn dev
# OR
npm run dev
Code language: PHP (php)
Cloudinary is a media management platform that enables us to make the most of our media assets. This is through its comprehensive set of APIs and SDKs. Let us install the recommended Nuxt.Js plugin, @nuxtjs/cloudinary.
Open the terminal in your project folder to add the dependency:
yarn add @nuxtjs/cloudinary
# or
npm install @nuxtjs/cloudinary
Code language: CSS (css)
Once installation is complete, add the module in the modules
section of the nuxt.config.js
file:
// nuxt.config.js
export default {
...
modules: [
'@nuxtjs/cloudinary'
]
...
}
Code language: JavaScript (javascript)
We will then configure our Cloudinary instance by adding a cloudinary
section to the nuxt.config.js
file:
// nuxt.config.js
export default {
...
cloudinary: {
cloudName: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
useComponent: true,
secure: true
}
}
Code language: JavaScript (javascript)
Environmental variables as values that are not dependent on our code itself but on the environment in which the code is executed. In the above piece of code, we refer to the NUXT_ENV_CLOUDINARY_CLOUD_NAME
environmental variable.
To set it up, let’s first create the .env
file which will contain our environmental variables:
touch .env
Code language: CSS (css)
We will then set our environmental variables in the file
NUXT_ENV_CLOUDINARY_CLOUD_NAME=<secret-cloud-name>
Code language: HTML, XML (xml)
We will be uploading our 3D models just as we upload images. Cloudinary currently only supports the following file formats: .glb, .gltz, and .fbxz.
First, we create a simple HTML form.
<!-- pages/index.vue -->
<template>
...
<form @submit.prevent="upload">
<input
@change="handleFile"
id="model"
name="model"
type="file"
accept=".glb, .gltz, .fbxz"
required
/>
<p v-if="uploading">Uploading...</p>
<button
v-else
type="submit"
>
Upload
</button>
</form>
...
</template>
Code language: HTML, XML (xml)
This will trigger the handleFile
method when a file is selected and the upload
method when the form is submitted.
// pages/index.vue
export default {
data() {
return {
model: null,
uploading: false,
display: null ,
};
},
...
methods: {
async handleFile(e) {
this.model = e.target.files[0];
},
async readData(f) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(f);
});
},
async upload() {
this.uploading = true;
const modelData = await this.readData(this.model);
const cloudinaryModel = await this.$cloudinary.upload(modelData, {
upload_preset: "default-preset",
folder: "nuxtjs-3d-model-to-360",
});
this.display = {
publicId: cloudinaryModel.public_id
}
this.uploading = false;
},
},
};
Code language: JavaScript (javascript)
handleFile
stores the selected file in the page state. When upload
is triggered, readData
reads the file data and returns it to the upload
method which uploads the model to Cloudinary.
Once the upload is complete, the models public_id
in Cloudinary is stored in the display
page variable. This variable will hold the public_id
of the model we will be displaying.
During upload, we refer to the default-preset
upload preset. This is a set of instructions we configure to be applied to our file uploads. You can add one on the create upload preset page. Here are our recommended defaults:
Name: default-preset Mode: unsigned Unique filename: true Delivery type: upload Access mode: public
We want the users of our app to be able to preview 360 degree GIFs of pre-loaded 3D models. This is so that they don’t have to look for 3D models if they already don’t have some. To get started, upload the following 3D models into your Cloudinary dashboard in a folder named nuxtjs-3d-model-to-360
.
The resultant folder should look similar to this:
Let us store these samples in our page state. We will include the credits as we downloaded the models from additional sources.
// pages/index.vue
<script>
export default {
name: 'IndexPage',
data(){
return {
...
samples:[
{
name: "Cloudinary logo",
credit: "Cloudinary logo by Cloudinary",
publicId: 'nuxtjs-3d-model-to-360/CldLogo3D',
},
{
name:"18th century oilсan",
credit: "18th century oilсan (https://skfb.ly/oqKtP) by motionpix is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).",
publicId:"nuxtjs-3d-model-to-360/OilCan",
},
{
name:"Pencil",
credit: "Pencil (https://skfb.ly/o7GUU) by jackie glade is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).",
publicId:"nuxtjs-3d-model-to-360/pencil",
}
]
};
},
}
Code language: HTML, XML (xml)
We now need to allow the user to select a model. We can do this by adding buttons underneath the upload form.
<!-- pages/index.vue -->
<template>
...
<div
v-for="sample in samples"
:key="sample.publicId"
>
<button @click="display=sample">
<span>{{sample.name</span>
</button>
</div>
...
</template>
Code language: HTML, XML (xml)
To render the 360-degree GIFS, we use the cld-image
componenent. We load up the public-id
of the model we want to use. We use transformations to:
- animate it as a GIF with a 10-second delay
- limit the height and weight ensuring if fills the container
- add a background color so that the model is easily visible.
We only do this when display
is actually set.
<!-- pages/index.vue -->
<template>
...
<div v-if="display" >
<cld-image
:alt="`${display.name} loading`" :public-id="display.publicId"
>
<cld-transformation
delay="10"
flags="animated"
fetch-format="gif"
>
</cld-transformation>
<cld-transformation
height="200"
width="400"
crop="fill"
>
</cld-transformation>
<cld-transformation
background="#3448C5"
>
</cld-transformation>
</cld-image>
<p>
{{ display.credit }}
</p>
</div>
...
</template>
Code language: HTML, XML (xml)
With the above we have uploaded 3D models, preloaded others and rendered the selected model into a 360 degree GIF. There is more that we can achieve with the Cloudinary platform and toolset. Feel free to review the extensive documentation here.