ianmackenzie / elm-3d-scene / Scene3d.Material


type alias Material coordinates attributes =
Scene3d.Types.Material coordinates attributes

A Material controls the color, reflectivity etc. of a given object. It may be constant across the object or be textured.

The attributes type parameter of a material is used to restrict what objects it can be used with. For example, Material.matte returns a value with an attributes type of { a | normals : () }; you can read this as "this material can be applied to any mesh that has normals".

The coordinates type parameter is currently unused but will be used in the future for things like procedural textures defined in a particular coordinate system; those textures will then only be able to be applied to objects defined in the same coordinate system.

Simple materials

color : Color -> Material coordinates attributes

A simple constant color material. This material can be applied to any object and ignores lighting entirely - the entire object will have exactly the given color regardless of lights or scene exposure/white balance settings. Here's a rubber duck model with a constant blue color:

Duckling with constant color

Note that transparency is not currently supported, so any alpha value in the given color will be ignored.

emissive : Scene3d.Types.Chromaticity -> Luminance -> Material coordinates attributes

An emissive or 'glowing' material, where you specify the chromaticity and intensity of the emitted light. The result will be a constant color, but one that (unlike Material.color) will depend on the exposure and white balance settings used when rendering the scene.

matte : Color -> Material coordinates { a | normals : () }

A perfectly matte (Lambertian) material which reflects light equally in all directions. Lambertian materials are faster to render than physically-based materials like Material.metal or Material.nonmetal, so consider using them for large surfaces like floors that don't need to be shiny:

Matte duckling

(Note that this doesn't appear to be quite the same blue as before, since the lights themselves are colored.)

Physically-based materials

Physically based rendering (PBR) is a modern rendering technique that attempts to realistically render real-world materials such as metals and plastics. elm-3d-scene uses a fairly simple, common variant of PBR where materials have three main parameters:

nonmetal : { baseColor : Color, roughness : Basics.Float } -> Material coordinates { a | normals : () }

A non-metal material such as plastic, wood, paper etc. This allows for some nice lighting highlights compared to matte:

Rough plastic duckling

An even shinier version (lower roughness):

Shiny plastic duckling

metal : { baseColor : Color, roughness : Basics.Float } -> Material coordinates { a | normals : () }

A metal material such as steel, aluminum, gold etc. See here and here for base colors of different metals. Here's what 'blue metal' might look like:

Rough metal duckling

As with nonmetals, roughness can be decreased to get a shinier surface:

Shiny metal duckling

Note that metals are generally more sensitive to light direction than nonmetals, so if you only have directional and/or point lights in your scene then metallic objects will often have a couple bright highlights but otherwise be very dark. This can usually be addressed by using at least a small amount of soft lighting so that there is at least a small amount of light coming from every direction.

pbr : { baseColor : Color, roughness : Basics.Float, metallic : Basics.Float } -> Material coordinates { a | normals : () }

A custom PBR material with a metallic parameter that can be anywhere between 0 and 1. Values in between 0 and 1 can be used to approximate things like dusty metal, where a surface can be thought of as partially metal and partially non-metal:

Partially metallic duckling

Textured materials

Textured materials behave just like their non-textured versions above, but require a mesh that has UV (texture) coordinates. Color, roughness and metallicness can then be controlled by a texture image instead of being restricted to constant values.

Note that images used as textures should generally have dimensions that are powers of 2: 2048x2048, 1024x512, etc. Images you get from sites like CC0 Textures will almost always have appropriate dimensions, but if you want to use your own images some cropping/resizing may be needed.


type alias Texture value =
Scene3d.Types.Texture value

A Texture value represents an image that is mapped over the surface of an object. Textures can be used to control the color at different points on an object (Texture Color) but can also be used to control roughness or metallicness when using a physically-based material (Texture Float).

constant : value -> Texture value

A special texture that has the same value everywhere. This can be useful with materials like texturedPbr which take multiple Texture arguments; sometimes you might want to use an actual texture for color but a constant value for roughness (or vice versa). For example, you might use

Material.constant blue

instead of loading a Texture Color from an image, or

Material.constant 0.5

to use a constant roughness value instead of a Texture Float loaded from an image.

load : String -> Task WebGL.Texture.Error (Texture value)

Load a texture from a given URL. Note that the resulting value can be used as either a Texture Color or a Texture Float - if used as a Texture Float then it will be the greyscale value of each pixel that is used (more precisely, its luminance).

The loaded texture will use bilinear texture filtering. To use nearest-neighbor filtering, trilinear filtering or to customize other texture options, use loadWith instead.

texturedColor : Texture Color -> Material coordinates { a | uvs : () }

A textured plain-color material, unaffected by lighting.

Textured color duckling

texturedEmissive : Texture Color -> Luminance -> Material coordinates { a | uvs : () }

A textured emissive material. The color from the texture will be multiplied by the given luminance to obtain the final emissive color.

texturedMatte : Texture Color -> Material coordinates { a | normals : (), uvs : () }

A textured matte material.

Textured matte duckling

texturedNonmetal : { baseColor : Texture Color, roughness : Texture Basics.Float } -> Material coordinates { a | normals : (), uvs : () }

A textured non-metal material. If you only have a texture for one of the two parameters (base color and roughness), you can use Material.constant for the other. Here's a model with a textured base color but constant roughness:

Rough textured nonmetal duckling

The same model but with roughness decreased for a shinier appearance:

Shiny textured nonmetal duckling

texturedMetal : { baseColor : Texture Color, roughness : Texture Basics.Float } -> Material coordinates { a | normals : (), uvs : () }

A textured metal material.

Textured metal duckling

texturedPbr : { baseColor : Texture Color, roughness : Texture Basics.Float, metallic : Texture Basics.Float } -> Material coordinates { a | normals : (), uvs : () }

A fully custom textured PBR material, where textures can be used to control all three parameters. As before, you can freely mix and match 'actual' textures with constant values for any of the three parameters. For example, here's a sphere with textured base color and textured metallicness but constant roughness:

Sphere with constant roughness

(You can see the effect of the textured metallicness in the small reddish rusty areas on the lower right-hand side.) Here's the same sphere but with a texture also used to control roughness - note how the rough scratches catch light that would otherwise be reflected away from the camera:

Sphere with textured roughness

Customized textures

loadWith : WebGL.Texture.Options -> String -> Task WebGL.Texture.Error (Texture value)

Load a texture with particular options, which control things like what form of texture filtering is used and how out-of-range texture coordinates are interpreted (clamped, wrapped around, etc.).

This module contains a few reasonable defaults (nearestNeighborFiltering, bilinearFiltering, and trilinearFiltering) but you can directly construct your own custom options record if desired. Unfortunately there's no one set of texture options that works well in all cases, so you may need to experiment to see what works best in your particular scene.

nearestNeighborFiltering : WebGL.Texture.Options

Don't interpolate between texture pixels at all when rendering; each on-screen pixel will simply get the color of the nearest texture pixel. This can be useful if you're deliberately going for a 'pixelated' look and want texture pixels to show up exactly on screen without any blurring. In most cases, though, using nearest-neighbor filtering will lead to unpleasant jagged edges (this is a zoomed-in portion of the Elm logo applied as a texture to a simple quad):

Nearest neighbor texture filtering

Note that the upper-right edge is smooth because that's actually the edge of the textured quad, instead of an edge between two different colors within the texture.

Nearest-neighbor filtering is implemented as:

nearestNeighborFiltering =
    { minify = WebGL.Texture.nearest
    , magnify = WebGL.Texture.nearest
    , horizontalWrap = WebGL.Texture.repeat
    , verticalWrap = WebGL.Texture.repeat
    , flipY = True
    }

bilinearFiltering : WebGL.Texture.Options

Apply some simple texture smoothing; each on-screen pixel will be a weighted average of the four closest texture pixels. This will generally lead to slightly smoother edges than nearest-neighbor filtering, at least for cases where one texture pixel is approximately the same size as one screen pixel:

Bilinear texture filtering

No mipmapping is used, so pixelation/aliasing may still occur especially for far-away objects where one texture pixel is much smaller than one screen pixel.

Bilinear filtering is implemented as:

bilinearFiltering =
    { minify = WebGL.Texture.linear
    , magnify = WebGL.Texture.linear
    , horizontalWrap = WebGL.Texture.repeat
    , verticalWrap = WebGL.Texture.repeat
    , flipY = True
    }

trilinearFiltering : WebGL.Texture.Options

Interpolate between nearby texture pixels as with bilinear filtering, but also interpolate between the two nearest mipmap levels. This will generally give the smoothest possible appearance, but may also lead to excessive blurriness:

Trilinear texture filtering

Trilinear filtering is implemented as:

trilinearFiltering =
    { minify = WebGL.Texture.linearMipmapLinear
    , magnify = WebGL.Texture.linear
    , horizontalWrap = WebGL.Texture.repeat
    , verticalWrap = WebGL.Texture.repeat
    , flipY = True
    }

Type annotations

The functions in this module all return values with a 'free' type parameter - for example, the return type of Material.matte is

Material coordinates { a | normals : () }

This makes most code simpler (it means that such a material can work with any kind of mesh that has normal vectors, even if for example that mesh also has texture coordinates) but makes it tricky to store a Material value in your own data structures without those data structures also needing a type parameter. The coordinates type parameter can usually be set to just WorldCoordinates (a type you will need to define yourself), but the a is a bit trickier.

The type aliases and functions below help deal with this problem in a convenient way. To store a material in a data structure, you can use one of the type aliases. For example, the material above might be stored as a

Material.Uniform WorldCoordinates

Then, if you need to turn this value back into a

Material coordinates { a | normals : () }

(so that you could apply it to a textured mesh, for example) you can use Material.uniform to do so. You can think of Material.uniform material as saying "yes, I know this is a uniform material, but I still want to apply it to this textured mesh".


type alias Plain coordinates =
Material coordinates {}

A material that doesn't require any particular vertex attributes. The only possibilities here are color and emissive.


type alias Unlit coordinates =
Material coordinates { uvs : () }

A material that requires UV (texture) coordinates but not normal vectors: texturedColor or texturedEmissive.


type alias Uniform coordinates =
Material coordinates { normals : () }

A material that requires normal vectors but not UV coordinates: matte, metal, nonmetal or pbr.


type alias Textured coordinates =
Material coordinates { normals : ()
, uvs : () 
}

A material that requires both normal vectors and UV coordinates: texturedMatte, texturedMetal, texturedNonmetal or texturedPbr.

plain : Plain coordinates -> Material coordinates attributes

Convert a Plain material (that can only be applied to a Plain mesh) back into one that can be applied to any mesh.

unlit : Unlit coordinates -> Material coordinates { a | uvs : () }

Convert an Unlit material (one that can only be applied to an Unlit mesh) back into one that can be applied to any mesh that has texture coordinates.

uniform : Uniform coordinates -> Material coordinates { a | normals : () }

Convert a Uniform material (one that can only be applied to a Uniform mesh) back into one that can be applied to any mesh that has normal vectors.