
DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - buffer geometry custom attributes - particlestitle>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #ffffff;
background-color: #000000;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px;
width: 100%;
padding: 5px;
font-family: Monospace;
font-size: 13px;
text-align: center;
font-weight: bold;
}
a {
color: #fff;
}
style>
head>
<body>
<div id="container">div>
<script src="../build/three.js">script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float size;
attribute vec3 customColor;
varying vec3 vColor;
void main() {
vColor = customColor;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 300.0 / mvPosition.x );
gl_Position = projectionMatrix * mvPosition;
}
script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D texture;
varying vec3 vColor;
void main() {
gl_FragColor = vec4( vColor, 1.0 );
gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord );
}
script>
<script>
var renderer, scene, camera, stats;
var particleSystem, uniforms, geometry;
var particles = 200;
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 40, WIDTH / HEIGHT, 1, 10000 );
camera.position.z = 500;
scene = new THREE.Scene();
uniforms = {
texture: { value: new THREE.TextureLoader().load( "textures/sprites/spark1.png" ) }
};
var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
transparent: true
});
var radius = 400;
var geometry = new THREE.BufferGeometry();
var positions = new Float32Array( particles * 3 );
var colors = new Float32Array( particles * 3 );
var sizes = new Float32Array( particles );
for ( var i = 0, i3 = 0; i < particles; i ++, i3 += 3 ) {
positions[ i3 + 0 ] = ( Math.random() * 2 - 1 ) * radius;
positions[ i3 + 1 ] = ( Math.random() * 2 - 1 ) * radius;
positions[ i3 + 2 ] = 0;
colors[ i3 + 0 ] = 1;
colors[ i3 + 1 ] = 1;
colors[ i3 + 2 ] = 1;
sizes[ i ] = 10;
}
geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.addAttribute( 'customColor', new THREE.BufferAttribute( colors, 3 ) );
geometry.addAttribute( 'size', new THREE.BufferAttribute( sizes, 1 ) );
particleSystem = new THREE.Points( geometry, shaderMaterial );
scene.add( particleSystem );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( WIDTH, HEIGHT );
var container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
script>
body>
html>
高光网格材质 MeshPhongMaterial、标准网格材质MeshStandardMaterial、物理网格材质MeshPhysicalMaterial,次时代、PBR

如果你想展示一个三维场景,比如一辆轿车,首先需要 3D 美术建模和烘培,然后程序员通过 Three.js 引擎加载解析显示出来。
对于3D美术来说烘培的时候有次时代、PBR 两种流程,这两种所谓的流程,对应的就是 Three.js 的高光网格材质 MeshPhongMaterial、基于物理的材质MeshStandardMaterial或MeshPhysicalMaterial。
对于程序员而言,如果你不想深入理解什么是高光网格材质,什么是基于物理的材质,每种材质对应的着色器代码应该如何编写,这种情况下,你只需要会选择使用哪种网格材质就可以。
如果3D美术烘培的时候是次时代流程,也就是贴图中你可以看到高光贴图 .specularMap,你需要选择高光网格材质 MeshPhongMaterial 渲染该模型,如果3D美术烘培的时候是PBR流程,也就是贴图中你可以看到金属度贴图 .metalnessMap 和粗糙度贴图 .roughnessMap,你需要选择基于物理的材质 MeshStandardMaterial或 MeshPhysicalMaterial 解析渲染。
质感
如果展示一个物体,需要很好的质感,比如轿车、珠宝等产品展示,可以让 3D 美术选择 PBR 流程烘培导出贴图,程序员使用基于物理的材质 MeshStandardMaterial 或 MeshPhysicalMaterial 进行解析渲染。
var scene, camera, renderer, envMap, phongMaterial, standardMaterial, params1, params2, faceNormalsHelper, vertexNormalsHelper;
init();
function init(){
const assetPath = 'https://your path/';
envMap = new THREE.CubeTextureLoader().setPath(`${assetPath}skybox3_`).load([
'px.jpg', 'nx.jpg',
'py.jpg', 'ny.jpg',
'pz.jpg', 'nz.jpg'
])
scene = new THREE.Scene();
scene.background = envMap;
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set(0, 0, 10);
const ambient = new THREE.HemisphereLight(0xffffbb, 0x080820);
scene.add(ambient);
const light = new THREE.DirectionalLight(0xFFFFFF, 3);
light.position.set(0,4,4);
scene.add(light);
const albedoMap = new THREE.TextureLoader().load(`${assetPath}TexturesCom_Orange_512_albedo.jpg`);
const normalMap = new THREE.TextureLoader().load(`${assetPath}TexturesCom_Orange_512_normal.jpg`);
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
const controls = new THREE.OrbitControls( camera, renderer.domElement );
//Add meshes here
const geometry = new THREE.SphereGeometry(1, 30, 20);
phongMaterial = new THREE.MeshPhongMaterial();
standardMaterial = new THREE.MeshStandardMaterial();
const phongSphere = new THREE.Mesh( geometry, phongMaterial);
const standardSphere = new THREE.Mesh( geometry, standardMaterial);
for(let xPos=-3; xPos<3; xPos+=3){
const sphereA = phongSphere.clone();
sphereA.position.set(xPos, 1.5, 0);
scene.add(sphereA);
if (xPos==0){
faceNormalsHelper = new THREE.FaceNormalsHelper(sphereA, 0.25);
vertexNormalsHelper = new THREE.VertexNormalsHelper(sphereA, 0.25);
faceNormalsHelper.visible = false;
vertexNormalsHelper.visible = false;
scene.add(faceNormalsHelper);
scene.add(vertexNormalsHelper);
}
const sphereB = standardSphere.clone();
sphereB.position.set(xPos, -1.5, 0);
scene.add(sphereB);
}
params1 = {
color: 0xffffff,
envMap: 'none',
reflectivity: 1,
albedoMap: 'none',
normalMap: 'none',
normalScale: 1,
shininess: 30,
facetted: false,
normals: 'none'
}
params2 = {
color: 0xffffff,
emissive: 0,
envMap: 'none',
reflectivity: 1,
albedoMap: 'none',
normalMap: 'none',
normalScale: 1,
roughness: 0.5,
metalness: 0.5,
facetted: false
}
const gui = new dat.gui.GUI();
gui.add(params1, 'normals', ['none', 'face', 'vertex']).onChange(function(value){
faceNormalsHelper.visible = false;
vertexNormalsHelper.visible = false;
phongMaterial.wireframe = false;
switch(value){
case 'face':
faceNormalsHelper.visible = true;
phongMaterial.wireframe = true;
break;
case 'vertex':
vertexNormalsHelper.visible = true;
phongMaterial.wireframe = true;
break;
}
});
const f1 = gui.addFolder('Phong Material');
f1.addColor(params1, 'color').onChange( function() { phongMaterial.color.set( params1.color ); } );
f1.add(params1, 'envMap', ['none', 'cathedral']).onChange( function(){
switch (params1.envMap){
case 'cathedral':
phongMaterial.envMap = envMap;
break;
default:
phongMaterial.envMap = null;
break;
}
phongMaterial.needsUpdate = true;
});
f1.add(params1, 'reflectivity').min(0).max(1).step(0.01).onChange( function(){ phongMaterial.reflectivity = params1.reflectivity });
f1.open();
f1.add(params1, 'albedoMap', ['none', 'orange']).onChange( function(value){
switch (value){
case 'orange':
phongMaterial.map = albedoMap;
break;
default:
phongMaterial.map = null;
break;
}
phongMaterial.needsUpdate = true;
});
f1.add(params1, 'normalMap', ['none', 'dimples']).onChange( function(value){
switch (value){
case 'dimples':
phongMaterial.normalMap = normalMap;
break;
default:
phongMaterial.normalMap = null;
break;
}
phongMaterial.needsUpdate = true;
});
f1.add(params1, 'normalScale').min(0).max(1).step(0.01).onChange( function(value){ phongMaterial.normalScale.x = value;
phongMaterial.normalScale.y = value; });
f1.add(params1, 'shininess').min(0).max(255).step(0.5).onChange( function(value){ phongMaterial.shininess = value });
f1.add(params1, 'facetted').onChange( function(value){
phongMaterial.flatShading = value;
phongMaterial.needsUpdate = true;
});
const f2 = gui.addFolder('Standard Material');
f2.addColor(params2, 'color').onChange( function(value) { standardMaterial.color.set( value ); } );
f2.addColor(params2, 'emissive').onChange( function(value) { standardMaterial.emissive.set( value ); } );
f2.add(params2, 'envMap', ['none', 'cathedral']).onChange( function(value){
switch (value){
case 'cathedral':
standardMaterial.envMap = envMap;
break;
default:
standardMaterial.envMap = null;
break;
}
standardMaterial.needsUpdate = true;
});
f2.add(params2, 'albedoMap', ['none', 'orange']).onChange( function(value){
switch (value){
case 'orange':
standardMaterial.map = albedoMap;
break;
default:
standardMaterial.map = null;
break;
}
standardMaterial.needsUpdate = true;
});
f2.add(params2, 'normalMap', ['none', 'dimples']).onChange( function(value){
switch (value){
case 'dimples':
standardMaterial.normalMap = normalMap;
break;
default:
standardMaterial.normalMap = null;
break;
}
standardMaterial.needsUpdate = true;
});
f2.add(params2, 'normalScale').min(0).max(1).step(0.01).onChange( function(value){ standardMaterial.normalScale.x = value;
standardMaterial.normalScale.y = value; });
f2.add(params2, 'roughness').min(0).max(1).step(0.01).onChange( function(value){ standardMaterial.roughness = value });
f2.add(params2, 'metalness').min(0).max(1).step(0.01).onChange( function(value){ standardMaterial.metalness = value });
f2.add(params1, 'facetted').onChange( function(value){
standardMaterial.flatShading = value;
standardMaterial.needsUpdate = true;
});
window.addEventListener( 'resize', resize, false);
update();
}
function update(){
requestAnimationFrame( update );
renderer.render( scene, camera );
}
function resize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}