材质简单理解就是设置几何体各个面的颜色。但它不是单纯的颜色,它能模拟在不同光照下颜色的表现。比如太阳光照射光滑的物体,表面会出现白色的反光,都能模拟。材质和渲染器无关,在开发中定义一份材质就可以重复使用。
定义材质的常用的方式有两种:
- 在实例化时传入要配置的参数
const material = new THREE.MeshBasicMaterial({
color: 0xFF0000, // 也可以使用CSS的颜色字符串
});
- 通过材质的方法设置属性
const material = new THREE.MeshBasicMaterial();
material.color.setHSL(0, 1, 0.5);
material.color.set(0x00FFFF); // 同 CSS的 #RRGGBB 风格
MeshBasicMaterial基础材质
- 以简单着色的方式实现
- 不受灯光的影响
// 基础材质
const sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
const sphereMaterial = new THREE.MeshBasicMaterial({color: 0xf37e7d});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
// 位置
sphere.position.x = 0;
sphere.position.y = 4;
sphere.position.z = 2;
// 阴影
sphere.castShadow = true;
scene.add(sphere);
// 基础材质
const sphereGeometry1 = new THREE.SphereGeometry(4, 20, 20);
// wireframe 基础材质的属性,设置true,只渲染线框
const sphereMaterial1 = new THREE.MeshBasicMaterial({color: 0xf37e7d, wireframe: true});
const sphere1 = new THREE.Mesh(sphereGeometry1, sphereMaterial1)
sphere1.position.x = 10;
sphere1.position.y = 4;
sphere1.position.z = 0;
sphere1.castShadow = true;
scene.add(sphere1);
运行效果:
MeshLambertMaterial Lambert网格材质
- 表面光滑的材质
- 受灯光的影响,不过只在顶点计算光照
- 能很好的模拟一些表面(例如未经处理的木材或石材)。因为只在顶点计算光照,不能模拟具有镜面高光的表面(如地板砖这些)
// 基础材质
const sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
const sphereMaterial = new THREE.MeshLambertMaterial({color: 0xf37e7d});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
// 位置
sphere.position.x = 0;
sphere.position.y = 4;
sphere.position.z = 2;
// 阴影
sphere.castShadow = true;
scene.add(sphere);
这里灯光用的方向光黑色,不设置灯光几何体就会展示为全黑。几何体最后展示的颜色是,灯光颜色乘以材质的颜色来展示的。
运行效果:
MeshPhongMaterial Phong网格材质
- 具有镜面高光的材质
- 每个像素都会计算光照
- 模拟具有镜面高光的表面(如地板砖)
// 基础材质
const sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
// shininess属性,决定高光的光泽,值越大光泽越亮。默认是30
const sphereMaterial = new THREE.MeshPhongMaterial({color: 0xf37e7d, shininess: 50});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
// 位置
sphere.position.x = 0;
sphere.position.y = 4;
sphere.position.z = 2;
// 阴影
sphere.castShadow = true;
scene.add(sphere);
运行效果:
基于深度着色的MeshDepthMaterial
使用这种材质的物体,其外观不是由光照或某个材质属性决定的;而是由物体到相机的距离决定的。可以将这种材质与其他材质相结合,从而很容易地创建出逐渐消失的效果。
const cubeGeometry = new THREE.CubeGeometry(20, 20, 20);
const cubeMaterial = new THREE.MeshDepthMaterial();
for (let i = 0; i < 300; i++) {
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = 800 * ( 2.0 * Math.random() - 1.0 );
cube.position.y = 800 * ( 2.0 * Math.random() - 1.0 );
cube.position.z = 800 * ( 2.0 * Math.random() - 1.0 );
cube.rotation.x = Math.random() * Math.PI;
cube.rotation.y = Math.random() * Math.PI;
cube.rotation.z = Math.random() * Math.PI;
cube.updateMatrix();
cube.add(cube);
}
注意:发现80+的版本不支持这个材质,全部都是黑色,换了一个60的版本的 three.js 文件,就没问题了。
运行效果(初始颜色设置成黑色,黑色好看):
融合材质
MeshDepthMaterial 不能设置颜色,一切都是由材质的默认属性决定的。但是,three.js 可通过联合材质创建出新效果。
const cubeGeometry = new THREE.CubeGeometry(20, 20, 20);
const cubeMaterial = new THREE.MeshDepthMaterial();
// 为了使颜色能够有渐变效果,必须设置MeshBasicMaterial的transparent为true,设置融合blending的方式
const cubeColorMaterial = new THREE.MeshBasicMaterial({color: 0x00ff00, transparent: true, blending: THREE.MultiplyBlending});
for (let i = 0; i < 300; i++) {
// 这些方块可以从MeshDepthMaterial对象获得渐变效果,从MeshBasicMaterial获取颜色
const sphere = new THREE.SceneUtils.createMultiMaterialObject(cubeGeometry, [cubeColorMaterial, sphereMaterial]);
cube.position.x = 800 * ( 2.0 * Math.random() - 1.0 );
cube.position.y = 800 * ( 2.0 * Math.random() - 1.0 );
cube.position.z = 800 * ( 2.0 * Math.random() - 1.0 );
cube.rotation.x = Math.random() * Math.PI;
cube.rotation.y = Math.random() * Math.PI;
cube.rotation.z = Math.random() * Math.PI;
cube.updateMatrix();
scene.add(cube);
}
运行效果:
为每个面指定材质的MeshFaceMaterial
通过 MeshFaceMaterial 可以为几何体的每一个面指定不同的材质。假如你有一个方块,上面有六个面,你可以用这种材质来为每个面指定一个材质。
const CubeGeometry = new THREE.CubeGeometry(5, 5, 5);
let mats = [];
mats.push(new THREE.MeshBasicMaterial({ color: 0x009e60 }));
mats.push(new THREE.MeshBasicMaterial({ color: 0x0051ba }));
mats.push(new THREE.MeshBasicMaterial({ color: 0xffd500 }));
mats.push(new THREE.MeshBasicMaterial({ color: 0xff5800 }));
mats.push(new THREE.MeshBasicMaterial({ color: 0xc41e3a }));
mats.push(new THREE.MeshBasicMaterial({ color: 0xffffff }));
const CubeMaterial = new THREE.MeshFaceMaterial(mats);
const Cube = new THREE.Mesh(CubeGeometry, CubeMaterial);
Cube.position.x = 0;
Cube.position.y = 4;
Cube.position.z = 2;
Cube.castShadow = true;
scene.add(Cube);
运行效果:
使用ShaderMaterial创建自己的着色器
ShaderMaterial 是 three.js 库中功能最丰富、最复杂的一种材质。通过它,可以使用自己定制的着色器,直接在 WebGL 环境中运行。ShaderMaterial 包含的几个常用属性 wireframe、wireframeLinewidth、shading、vertexColors、fog。ShaderMaterial 还包含几个特别属性:
- fragementShader (片元着色器):这个着色器定义的是每个传入的像素颜色
- vertexShader (顶点着色器):这个着色器允许你修改每一个传入的顶点的位置
- uniforms:通过这个属性可以向你的着色器发信息。同样的信息会发送到每一个顶点和片元
- defines:这个属性可以转换为 vertexShader 和 fragmentShader 里的 #define 代码。该属性可以用来设置着色器程序里的一些全局变量
- attributes:改属性可以修改每个顶点和片元。通常用来传递位置数据和法向量相关的数据。如果要用这个属性,anemia 你要为几何体中的所有顶点提供信息
- lights:定义光照数据是否传递给着色器。默认为false
在创建ShaderMaterial,必须要传递两个重要的属性vertexShader和fragmentShader。两个都是对应的一段WebGL顶点和片元着色器源码字符串。
着色器代码是用的《three.js开发指南》里面的,想学习这个的同学可以看一下。
<script id="vertex-shader" type="x-shader/x-vertex">
uniform float time;
varying vec2 vUv;
void main(){
vec3 posChanged = position;
posChanged.x = posChanged.x*(abs(sin(time*1.0)));
posChanged.y = posChanged.y*(abs(cos(time*1.0)));
posChanged.z = posChanged.z*(abs(sin(time*1.0)));
//gl_Position = projectionMatrix * modelViewMatrix * vec4(position*(abs(sin(time)/2.0)+0.5),1.0);
gl_Position = projectionMatrix * modelViewMatrix * vec4(posChanged,1.0);
}
</script>
<script id="fragment-shader-1" type="x-shader/x-fragment">
precision highp float;
uniform float time;
uniform float alpha;
uniform vec2 resolution;
varying vec2 vUv;
void main2(void)
{
vec2 position = vUv;
float red = 1.0;
float green = 0.25 + sin(time) * 0.25;
float blue = 0.0;
vec3 rgb = vec3(red, green, blue);
vec4 color = vec4(rgb, alpha);
gl_FragColor = color;
}
#define PI 3.14159
#define TWO_PI (PI*2.0)
#define N 68.5
void main(void)
{
vec2 center = (gl_FragCoord.xy);
center.x=-10.12*sin(time/200.0);
center.y=-10.12*cos(time/200.0);
vec2 v = (gl_FragCoord.xy - resolution/20.0) / min(resolution.y,resolution.x) * 15.0;
v.x=v.x-10.0;
v.y=v.y-200.0;
float col = 0.0;
for(float i = 0.0; i < N; i++)
{
float a = i * (TWO_PI/N) * 61.95;
col += cos(TWO_PI*(v.y * cos(a) + v.x * sin(a) + sin(time*0.004)*100.0 ));
}
col /= 5.0;
gl_FragColor = vec4(col*1.0, -col*1.0,-col*4.0, 1.0);
}
</script>
<script id="fragment-shader-2" type="x-shader/x-fragment">
// from http://glsl.heroku.com/e#7906.0
uniform float time;
uniform vec2 resolution;
// 2013-03-30 by @hintz
#define CGFloat float
#define M_PI 3.14159265359
vec3 hsvtorgb(float h, float s, float v)
{
float c = v * s;
h = mod((h * 6.0), 6.0);
float x = c * (1.0 - abs(mod(h, 2.0) - 1.0));
vec3 color;
if (0.0 <= h && h < 1.0)
{
color = vec3(c, x, 0.0);
}
else if (1.0 <= h && h < 2.0)
{
color = vec3(x, c, 0.0);
}
else if (2.0 <= h && h < 3.0)
{
color = vec3(0.0, c, x);
}
else if (3.0 <= h && h < 4.0)
{
color = vec3(0.0, x, c);
}
else if (4.0 <= h && h < 5.0)
{
color = vec3(x, 0.0, c);
}
else if (5.0 <= h && h < 6.0)
{
color = vec3(c, 0.0, x);
}
else
{
color = vec3(0.0);
}
color += v - c;
return color;
}
void main(void)
{
vec2 position = (gl_FragCoord.xy - 0.5 * resolution) / resolution.y;
float x = position.x;
float y = position.y;
CGFloat a = atan(x, y);
CGFloat d = sqrt(x*x+y*y);
CGFloat d0 = 0.5*(sin(d-time)+1.5)*d;
CGFloat d1 = 5.0;
CGFloat u = mod(a*d1+sin(d*10.0+time), M_PI*2.0)/M_PI*0.5 - 0.5;
CGFloat v = mod(pow(d0*4.0, 0.75),1.0) - 0.5;
CGFloat dd = sqrt(u*u+v*v);
CGFloat aa = atan(u, v);
CGFloat uu = mod(aa*3.0+3.0*cos(dd*30.0-time), M_PI*2.0)/M_PI*0.5 - 0.5;
// CGFloat vv = mod(dd*4.0,1.0) - 0.5;
CGFloat d2 = sqrt(uu*uu+v*v)*1.5;
gl_FragColor = vec4( hsvtorgb(dd+time*0.5/d1, sin(dd*time), d2), 1.0 );
}
</script>
<script id="fragment-shader-3" type="x-shader/x-fragment">
uniform float time;
uniform vec2 resolution;
// tie nd die by Snoep Games.
void main( void ) {
vec3 color = vec3(1.0, 0., 0.);
vec2 pos = (( 1.4 * gl_FragCoord.xy - resolution.xy) / resolution.xx)*1.5;
float r=sqrt(pos.x*pos.x+pos.y*pos.y)/15.0;
float size1=2.0*cos(time/60.0);
float size2=2.5*sin(time/12.1);
float rot1=13.00; //82.0+16.0*sin(time/4.0);
float rot2=-50.00; //82.0+16.0*sin(time/8.0);
float t=sin(time);
float a = (60.0)*sin(rot1*atan(pos.x-size1*pos.y/r,pos.y+size1*pos.x/r)+time);
a += 200.0*acos(pos.x*2.0+cos(time/2.0))+asin(pos.y*5.0+sin(time/2.0));
a=a*(r/50.0);
a=200.0*sin(a*5.0)*(r/30.0);
if(a>5.0) a=a/200.0;
if(a<0.5) a=a*22.5;
gl_FragColor = vec4( cos(a/20.0),a*cos(a/200.0),sin(a/8.0), 1.0 );
}
</script>
<script id="fragment-shader-4" type="x-shader/x-fragment">
uniform float time;
uniform vec2 resolution;
vec2 rand(vec2 pos)
{
return
fract(
(
pow(
pos+2.0,
pos.yx+2.0
)*555555.0
)
);
}
vec2 rand2(vec2 pos)
{
return rand(rand(pos));
}
float softnoise(vec2 pos, float scale) {
vec2 smplpos = pos * scale;
float c0 = rand2((floor(smplpos) + vec2(0.0, 0.0)) / scale).x;
float c1 = rand2((floor(smplpos) + vec2(1.0, 0.0)) / scale).x;
float c2 = rand2((floor(smplpos) + vec2(0.0, 1.0)) / scale).x;
float c3 = rand2((floor(smplpos) + vec2(1.0, 1.0)) / scale).x;
vec2 a = fract(smplpos);
return mix(mix(c0, c1, smoothstep(0.0, 1.0, a.x)),
mix(c2, c3, smoothstep(0.0, 1.0, a.x)),
smoothstep(0.0, 1.0, a.x));
}
void main( void ) {
vec2 pos = gl_FragCoord.xy / resolution.y - time * 0.4;
float color = 0.0;
float s = 1.0;
for (int i = 0; i < 6; ++i) {
color += softnoise(pos + vec2(0.01 * float(i)), s * 4.0) / s / 2.0;
s *= 2.0;
}
gl_FragColor = vec4(color,mix(color,cos(color),sin(color)),color,1);
}
</script>
<script id="fragment-shader-5" type="x-shader/x-fragment">
uniform vec2 resolution;
uniform float time;
vec2 rand(vec2 pos)
{
return fract( 0.00005 * (pow(pos+2.0, pos.yx + 1.0) * 22222.0));
}
vec2 rand2(vec2 pos)
{
return rand(rand(pos));
}
float softnoise(vec2 pos, float scale)
{
vec2 smplpos = pos * scale;
float c0 = rand2((floor(smplpos) + vec2(0.0, 0.0)) / scale).x;
float c1 = rand2((floor(smplpos) + vec2(1.0, 0.0)) / scale).x;
float c2 = rand2((floor(smplpos) + vec2(0.0, 1.0)) / scale).x;
float c3 = rand2((floor(smplpos) + vec2(1.0, 1.0)) / scale).x;
vec2 a = fract(smplpos);
return mix(
mix(c0, c1, smoothstep(0.0, 1.0, a.x)),
mix(c2, c3, smoothstep(0.0, 1.0, a.x)),
smoothstep(0.0, 1.0, a.y));
}
void main(void)
{
vec2 pos = gl_FragCoord.xy / resolution.y;
pos.x += time * 0.1;
float color = 0.0;
float s = 1.0;
for(int i = 0; i < 8; i++)
{
color += softnoise(pos+vec2(i)*0.02, s * 4.0) / s / 2.0;
s *= 2.0;
}
gl_FragColor = vec4(color);
}
</script>
<script id="fragment-shader-6" type="x-shader/x-fragment">
uniform float time;
uniform vec2 resolution;
void main( void )
{
vec2 uPos = ( gl_FragCoord.xy / resolution.xy );//normalize wrt y axis
//suPos -= vec2((resolution.x/resolution.y)/2.0, 0.0);//shift origin to center
uPos.x -= 1.0;
uPos.y -= 0.5;
vec3 color = vec3(0.0);
float vertColor = 2.0;
for( float i = 0.0; i < 15.0; ++i )
{
float t = time * (0.9);
uPos.y += sin( uPos.x*i + t+i/2.0 ) * 0.1;
float fTemp = abs(1.0 / uPos.y / 100.0);
vertColor += fTemp;
color += vec3( fTemp*(10.0-i)/10.0, fTemp*i/10.0, pow(fTemp,1.5)*1.5 );
}
vec4 color_final = vec4(color, 1.0);
gl_FragColor = color_final;
}
</script>
//创建ShaderMaterial纹理的函数
function createMaterial(vertexShader, fragmentShader) {
const vertShader = document.getElementById(vertexShader).innerHTML; //获取顶点着色器的代码
const fragShader = document.getElementById(fragmentShader).innerHTML; //获取片元着色器的代码
//配置着色器里面的attribute变量的值
const attributes = {};
//配置着色器里面的uniform变量的值
const uniforms = {
time: { type: "f", value: 0.2 },
scale: { type: "f", value: 0.2 },
alpha: { type: "f", value: 0.6 },
resolution: {
type: "v2",
value: new THREE.Vector2(window.innerWidth, window.innerHeight),
},
};
const meshMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
defaultAttributeValues: attributes,
vertexShader: vertShader,
fragmentShader: fragShader,
transparent: true,
});
return meshMaterial;
}
//声明一个立方体几何图形
const cubeGeometry = new THREE.BoxGeometry(20, 20, 20)
//创建立方体六个面的纹理
const meshMaterial1 = createMaterial(
"vertex-shader",
"fragment-shader-1"
);
const meshMaterial2 = createMaterial(
"vertex-shader",
"fragment-shader-2"
);
const meshMaterial3 = createMaterial(
"vertex-shader",
"fragment-shader-3"
);
const meshMaterial4 = createMaterial(
"vertex-shader",
"fragment-shader-4"
);
const meshMaterial5 = createMaterial(
"vertex-shader",
"fragment-shader-5"
);
const meshMaterial6 = createMaterial(
"vertex-shader",
"fragment-shader-6"
);
const material = [
meshMaterial6,
meshMaterial5,
meshMaterial4,
meshMaterial3,
meshMaterial2,
meshMaterial1,
]
const cube = new THREE.Mesh(cubeGeometry, material);
scene.add(cube);
运行效果(虽然有点像理发店门口的,将就看一下):
总结一下
在 three.js 中越复杂的材质会消耗更多的 GPU 功耗。在绘制物体时我们就需要判断需要哪些材质功能,找到对应的材质以此来减少程序的消耗。