Beginning with 3D WebGL (pt. 3) - Materials
This is part 3 in a series for beginners wanting to get in to using 3D WebGL. If you haven't checked out the other post be sure to have a look:
- pt 1. The Scene
- pt 2. Geometry
- pt 3. Materials
- pt 4. Animation
Cool fact: I honestly can't do anything with WebGL materials without thinking of this song. So I have had Madonna in my head for hours while trying to draft this post for you. I'm not complaining. I love 1980s Madonna.
Because we are living in a material world, there are a helluva lot of materials available in Three.js for us to use, with a helluva lot of options/paramaters. So for me to go through every one with you would get kind of overwhelming and/or boring, which is exactly what I am trying to avoid in this series!
Instead, I'm going to attempt to introduce some useful material examples, in the hope that you can feel comfortable with experimenting with materials in your 3D scenes.
What is a material anyway?
The Three.js documentation explains this in the most concise way: Materials describe the appearance of objects. Just like objects in real life, the material they are made out of determines how they appear in the 3D world. In the last post, we looked at how to create models/meshes with geometry. By applying a material to the mesh we can specify its color, texture, and how it reflects light.
Some pretty cool materials that you can use
MeshNormalMaterial
The mesh normal material is not the most useful material because you have very little control over how the material appears. This is because the color of the planes of the mesh is dictated by their position. It does make some really pretty rainbow colored objects though so I really like it :) It's worth noting this material isn't effected by lighting.
var material = new THREE.MeshNormalMaterial();
MeshLambertMaterial
The mesh lambert material is described as a non-shiny material, so I like to think of it is a matte-finish material. Like a matte plastic.
You can specify a color for your material or a texture (image).
An aside: working with Colors within Three.js
Materials accept a color parameter as a hexadecimal value, eg. 0xffffff
. Lucky for us, Three.js has a very handy color object for working with colors. You can create a color by taking a hex string, rgba string or hsl string, and passing it in to the color.
// use hex string
var color = new THREE.Color("#6f4171");
// use rgba string
var color = new THREE.Color("rgba(188, 141, 190, 1)");
Then, to get the hexadecimal value of that color you can use one of the Color methods.
var hex = color.getHex()
The Color object has a whollle bunch of methods to help us do cool things with color. We'll probably look at some of these over the course of these posts but if you can't wait, check out the documentation.
Creating a MeshLambertMaterial with a flat color
var color = new THREE.Color( "#7833aa" );
var material = new THREE.MeshLambertMaterial( {color: color.getHex()} );
So in the demo above, I have 2 point lights shining on the meshLambertMaterial which creates a nice matte glow when the mesh catches the light - but there are still quite a few shadows. Let's see how the material looks with more lighting.
Okay this scene has a tonne of lights. This scene is a lit situation - a lituation if you will. I've also created a transparent renderer to let a lighter body background come through. var renderer = new THREE.WebGLRenderer({alpha: true});
Using wireframes
Within these materials there is an option to use a wireframe instead of filling all the planes - which is a pretty cool effect in itself. To do this, you just set the wireframe
option to true.
var material = new THREE.MeshLambertMaterial( {color: color.getHex(), wireframe: true} );
Creating a MeshLambertMaterial with a texture (image)
Purple colored materials are super pretty but maybe you want to get fancy and create an image to wrap your mesh in. Maybe you want to create a brick wall with a brick texture. Or maybe you want to create a 3d balloon of your boss's face, which is totally not weird and will probably just impress them with your new 3D talents.
// create a loader to get an image from a URL
var textureLoader = new THREE.TextureLoader();
// we've gotta set this to use cross-origin images
textureLoader.crossOrigin = true;
// load in the image
textureLoader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53148/chrispizza.png', function(texture) {
// this code makes the texture repeat
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 2, 2 );
// set the texture as the map for the material
var material = new THREE.MeshLambertMaterial( {map: texture} );
}
MeshPhongMaterial
Now we can move on to the MeshPhongMaterial, or as I like to think of it, the fancy person's material. This material is pretty similar to the MeshLamberMaterial except its shiny, (ahhhhhh). Because they are similar you already know how to create a MeshPhongMaterial with a flat color or image texture because you do it the same way you did it with the lambert! Amazing! You're basically a level 12 3D mage now.
var color = new THREE.Color( "#7833aa" );
var material = new THREE.MeshPhongMaterial( {color: color.getHex()} );
See those flecks of light reflecting off of the mesh? That's because of the shinyness of this material. A couple of properties worth noting is the shinyness property (default of 30) which controls the level of shine, and the specular property which controls the color of that shine.
var color = new THREE.Color( "#7833aa" );
var material = new THREE.MeshPhongMaterial( {color: color.getHex(), specular: 0x009900, shinyness: 20 } );
In this pen I've added a green specular color which looks super weird with the purple but demonstrates how this property works.
Okay so you can do some pretty cool things with the MeshPhongMaterial but in my opinion the most fun part is being able to use a bump map. A bump map is a black and white map that creates perceived depth in your object when it is in lighting. This way you can create ridges and bumps in your mesh without having to complicate the geometry.
var textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = true;
textureLoader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53148/4268-bump.jpg', function(texture) {
// apply the texture as a bump map
var material = new THREE.MeshPhongMaterial( {color: color.getHex(), bumpMap: texture} );
});
Whaatt! After adding the bump map (a black and white image) to my material, it totally transforms the object to make it look like it has actual ridges. How cool is that!?! I will only accept "pretty darn cool" as an answer.
And on this high note, I will sign off for this post today. Be sure to check back for future posts, where we'll look at animation & 3d vector math, user input, camera work, particles, shaders, and more.
And if you make a CodePen after reading this series, I'd love to see it! Make sure you post it in the comments below.
Check out part 4: Animation
Your questions and suggestions give me ideas for what to write about next! So if you have any questions please comment below, send me a tweet @rachsmithtweets, submit to my AMA, or flick me an email at contact at rachsmith dot com.