|
@@ -3,6 +3,23 @@
|
|
|
/* global threejsLessonUtils */
|
|
|
|
|
|
{
|
|
|
+ function makeSphere(widthDivisions, heightDivisions) {
|
|
|
+ const radius = 7;
|
|
|
+ return new THREE.SphereBufferGeometry(radius, widthDivisions, heightDivisions);
|
|
|
+ }
|
|
|
+
|
|
|
+ const highPolySphereGeometry = function() {
|
|
|
+ const widthDivisions = 100;
|
|
|
+ const heightDivisions = 50;
|
|
|
+ return makeSphere(widthDivisions, heightDivisions);
|
|
|
+ }();
|
|
|
+
|
|
|
+ const lowPolySphereGeometry = function() {
|
|
|
+ const widthDivisions = 12;
|
|
|
+ const heightDivisions = 9;
|
|
|
+ return makeSphere(widthDivisions, heightDivisions);
|
|
|
+ }();
|
|
|
+
|
|
|
function smoothOrFlat(flatShading, radius = 7) {
|
|
|
const widthDivisions = 12;
|
|
|
const heightDivisions = 9;
|
|
@@ -15,14 +32,14 @@
|
|
|
}
|
|
|
|
|
|
function basicLambertPhongExample(MaterialCtor, lowPoly, params = {}) {
|
|
|
- const radius = 7;
|
|
|
- const widthDivisions = lowPoly ? 8 : 100;
|
|
|
- const heightDivisions = lowPoly ? 5 : 50;
|
|
|
- const geometry = new THREE.SphereBufferGeometry(radius, widthDivisions, heightDivisions);
|
|
|
+ const geometry = lowPoly ? lowPolySphereGeometry : highPolySphereGeometry;
|
|
|
const material = new MaterialCtor(Object.assign({
|
|
|
color: 'hsl(210,50%,50%)',
|
|
|
}, params));
|
|
|
- return new THREE.Mesh(geometry, material);
|
|
|
+ return {
|
|
|
+ obj3D: new THREE.Mesh(geometry, material),
|
|
|
+ trackball: lowPoly,
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
function sideExample(side) {
|
|
@@ -48,6 +65,64 @@
|
|
|
return base;
|
|
|
}
|
|
|
|
|
|
+ function makeStandardPhysicalMaterialGrid(elem, physical, update) {
|
|
|
+ const numMetal = 5;
|
|
|
+ const numRough = 7;
|
|
|
+ const meshes = [];
|
|
|
+ const MatCtor = physical ? THREE.MeshPhysicalMaterial : THREE.MeshStandardMaterial;
|
|
|
+ const color = physical ? 'hsl(160,50%,50%)' : 'hsl(140,50%,50%)';
|
|
|
+ for (let m = 0; m < numMetal; ++m) {
|
|
|
+ const row = [];
|
|
|
+ for (let r = 0; r < numRough; ++r) {
|
|
|
+ const material = new MatCtor({
|
|
|
+ color,
|
|
|
+ roughness: r / (numRough - 1),
|
|
|
+ metalness: m / (numMetal - 1),
|
|
|
+ });
|
|
|
+ const mesh = new THREE.Mesh(highPolySphereGeometry, material);
|
|
|
+ row.push(mesh);
|
|
|
+ }
|
|
|
+ meshes.push(row);
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ obj3D: null,
|
|
|
+ trackball: false,
|
|
|
+ render(renderInfo) {
|
|
|
+ const {camera, scene, renderer} = renderInfo;
|
|
|
+ const rect = elem.getBoundingClientRect();
|
|
|
+
|
|
|
+ const width = (rect.right - rect.left) * renderInfo.pixelRatio;
|
|
|
+ const height = (rect.bottom - rect.top) * renderInfo.pixelRatio;
|
|
|
+ const left = rect.left * renderInfo.pixelRatio;
|
|
|
+ const top = rect.top * renderInfo.pixelRatio;
|
|
|
+
|
|
|
+ const cellSize = Math.min(width / numRough, height / numMetal) | 0;
|
|
|
+ const xOff = (width - cellSize * numRough) / 2;
|
|
|
+ const yOff = (height - cellSize * numMetal) / 2;
|
|
|
+
|
|
|
+ camera.aspect = 1;
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
+
|
|
|
+ if (update) {
|
|
|
+ update(meshes);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (let m = 0; m < numMetal; ++m) {
|
|
|
+ for (let r = 0; r < numRough; ++r) {
|
|
|
+ const x = left + xOff + r * cellSize;
|
|
|
+ const y = top + yOff + m * cellSize;
|
|
|
+ renderer.setViewport(x, y, cellSize, cellSize);
|
|
|
+ renderer.setScissor(x, y, cellSize, cellSize);
|
|
|
+ const mesh = meshes[m][r];
|
|
|
+ scene.add(mesh);
|
|
|
+ renderer.render(scene, camera);
|
|
|
+ scene.remove(mesh);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
threejsLessonUtils.addDiagrams({
|
|
|
smoothShading: {
|
|
|
create() {
|
|
@@ -142,6 +217,83 @@
|
|
|
return basicLambertPhongExample(THREE.MeshToonMaterial);
|
|
|
},
|
|
|
},
|
|
|
+ MeshStandardMaterial: {
|
|
|
+ create(props) {
|
|
|
+ return makeStandardPhysicalMaterialGrid(props.renderInfo.elem, false);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ MeshPhysicalMaterial: {
|
|
|
+ create(props) {
|
|
|
+ const settings = {
|
|
|
+ clearCoat: .5,
|
|
|
+ clearCoatRoughness: 0,
|
|
|
+ };
|
|
|
+
|
|
|
+ function addElem(parent, type, style = {}) {
|
|
|
+ const elem = document.createElement(type);
|
|
|
+ Object.assign(elem.style, style);
|
|
|
+ parent.appendChild(elem);
|
|
|
+ return elem;
|
|
|
+ }
|
|
|
+
|
|
|
+ function addRange(elem, obj, prop, min, max) {
|
|
|
+ const outer = addElem(elem, 'div', {
|
|
|
+ width: '100%',
|
|
|
+ textAlign: 'center',
|
|
|
+ 'font-family': 'monospace',
|
|
|
+ });
|
|
|
+
|
|
|
+ const div = addElem(outer, 'div', {
|
|
|
+ textAlign: 'left',
|
|
|
+ display: 'inline-block',
|
|
|
+ });
|
|
|
+
|
|
|
+ const label = addElem(div, 'label', {
|
|
|
+ display: 'inline-block',
|
|
|
+ width: '12em',
|
|
|
+ });
|
|
|
+ label.textContent = prop;
|
|
|
+
|
|
|
+ const num = addElem(div, 'div', {
|
|
|
+ display: 'inline-block',
|
|
|
+ width: '3em',
|
|
|
+ });
|
|
|
+
|
|
|
+ function updateNum() {
|
|
|
+ num.textContent = obj[prop].toFixed(2);
|
|
|
+ }
|
|
|
+ updateNum();
|
|
|
+
|
|
|
+ const input = addElem(div, 'input', {
|
|
|
+ });
|
|
|
+ Object.assign(input, {
|
|
|
+ type: 'range',
|
|
|
+ min: 0,
|
|
|
+ max: 100,
|
|
|
+ value: (obj[prop] - min) / (max - min) * 100,
|
|
|
+ });
|
|
|
+ input.addEventListener('input', () => {
|
|
|
+ obj[prop] = min + (max - min) * input.value / 100;
|
|
|
+ updateNum();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ const {elem} = props.renderInfo;
|
|
|
+ addRange(elem, settings, 'clearCoat', 0, 1);
|
|
|
+ addRange(elem, settings, 'clearCoatRoughness', 0, 1);
|
|
|
+ const area = addElem(elem, 'div', {
|
|
|
+ width: '100%',
|
|
|
+ height: '400px',
|
|
|
+ });
|
|
|
+
|
|
|
+ return makeStandardPhysicalMaterialGrid(area, true, (meshes) => {
|
|
|
+ meshes.forEach(row => row.forEach(mesh => {
|
|
|
+ mesh.material.clearCoat = settings.clearCoat;
|
|
|
+ mesh.material.clearCoatRoughness = settings.clearCoatRoughness;
|
|
|
+ }));
|
|
|
+ });
|
|
|
+ },
|
|
|
+ },
|
|
|
MeshDepthMaterial: {
|
|
|
create(props) {
|
|
|
const {camera} = props;
|