"use strict"; // 參考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode //////////////////////////////////////////////////////////////////////////////// // 銳利的鏡面反射 //////////////////////////////////////////////////////////////////////////////// var camera, scene, renderer; var cameraControls; var effectController; var clock = new THREE.Clock(); var teapotSize = 600; var tess = -1; // 強制初始化 var ambientLight, light; var teapot; var phongBalancedMaterial; var vertexShader = ` varying vec3 vNormal; varying vec3 vViewPosition; void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); vNormal = normalize( normalMatrix * normal ); vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); vViewPosition = -mvPosition.xyz; } ` var fragmentShader = ` uniform vec3 uMaterialColor; uniform vec3 uSpecularColor; uniform vec3 uDirLightPos; uniform vec3 uDirLightColor; uniform vec3 uAmbientLightColor; uniform float uKd; uniform float uKs; uniform float shininess; uniform float uDropoff; varying vec3 vNormal; varying vec3 vViewPosition; void main() { // 環境光 gl_FragColor = vec4( uAmbientLightColor, 1.0 ); // 計算往光源的方向 vec4 lDirection = viewMatrix * vec4( uDirLightPos, 0.0 ); vec3 lVector = normalize( lDirection.xyz ); vec3 normal = normalize( vNormal ); // 漫射:N * L。法向量必須正規化,因為經過內插計算 float diffuse = max( dot( normal, lVector ), 0.0); // 這樣可能會切斷高光,但是比奇怪的閃爍好 if (diffuse <= 0.0) { return; } gl_FragColor.rgb += uKd * uMaterialColor * uDirLightColor * diffuse; // 鏡面反射:N * H 的指數。H 是往光的向量加上往攝影機的向量正規化 vec3 viewPosition = normalize( vViewPosition ); vec3 pointHalfVector = normalize( lVector + viewPosition ); float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 ); float specular = pow( pointDotNormalHalf, shininess ); specular *= diffuse*(2.0 + shininess)/8.0; // 學生作業:在這裡加入條件,當 specular < uDropoff 設為 0.0 否則設為 1.0 gl_FragColor.rgb += uDirLightColor * uSpecularColor * uKs * specular; } ` function init() { canvasWidth = window.innerWidth; canvasHeight = window.innerHeight; var canvasRatio = canvasWidth/canvasHeight; // 光源 ambientLight = new THREE.AmbientLight(0x333333); // 0.2 light = new THREE.DirectionalLight(0xffffff, 1.0); light.position.set(700, 3000, 1200); // RENDERER renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(canvasWidth, canvasHeight); renderer.setClearColorHex(0xAAAAAA, 1.0); renderer.gammaInput = true; renderer.gammaOutput = true; // 攝影機 camera = new THREE.PerspectiveCamera(45, canvasRatio, 1, 80000); camera.position.set(-1246, 35, -439); // 控制 cameraControls = new THREE.OrbitAndPanControls(camera, renderer.domElement); cameraControls.target.set(0, -436, 0); // 材質 // 筆記:將「逐像素」關閉不會影響鏡面高光 // 只會影響「往光的方向」會不會逐像素計算 var materialColor = new THREE.Color(); materialColor.setRGB(1.0, 0.8, 0.6); phongBalancedMaterial = createShaderMaterial("phongBalanced", light, ambientLight); phongBalancedMaterial.uniforms.uMaterialColor.value.copy(materialColor); phongBalancedMaterial.side = THREE.DoubleSide; } function createShaderMaterial(id, light, ambientLight) { var shaderTypes = { 'phongBalanced' : { uniforms: { "uDirLightPos": { type: "v3", value: new THREE.Vector3() }, "uDirLightColor": { type: "c", value: new THREE.Color( 0xffffff ) }, "uAmbientLightColor": { type: "c", value: new THREE.Color( 0x050505 ) }, "uMaterialColor": { type: "c", value: new THREE.Color( 0xffffff ) }, "uSpecularColor": { type: "c", value: new THREE.Color( 0xffffff ) }, uKd: { type: "f", value: 0.7 }, uKs: { type: "f", value: 0.3 }, shininess: { type: "f", value: 100.0 }, uDropoff: { type: "f", value: 0.5 } } } }; var shader = shaderTypes[id]; var u = THREE.UniformsUtils.clone(shader.uniforms); var vs = vertexShader; var fs = fragmentShader; var material = new THREE.ShaderMaterial({ uniforms: u, vertexShader: vs, fragmentShader: fs }); material.uniforms.uDirLightPos.value = light.position; material.uniforms.uDirLightColor.value = light.color; material.uniforms.uAmbientLightColor.value = ambientLight.color; return material; } function setupGui() { effectController = { shininess: 100.0, dropoff: 0.5, ka: 0.2, kd: 0.4, ks: 0.35, metallic: false, hue: 0.09, saturation: 0.46, lightness: 0.7, lhue: 0.04, lsaturation: 0.0, llightness: 0.7, // 奇怪的是如果你以負數初始化,這些滑桿不會顯示小數位 lx: 0.65, ly: 0.43, lz: 0.35, newTess: 10 }; var h; var gui = new dat.GUI(); // 材質(屬性) h = gui.addFolder("材質控制"); h.add(effectController, "shininess", 1.0, 1000.0, 1.0).name("閃亮程度"); h.add(effectController, "dropoff", 0.0, 2.0, 0.025).name("衰減"); h.add(effectController, "ka", 0.0, 1.0, 0.025).name("環境光係數"); h.add(effectController, "kd", 0.0, 1.0, 0.025).name("漫射係數"); h.add(effectController, "ks", 0.0, 1.0, 0.025).name("閃亮程度係數"); h.add(effectController, "metallic").name("金屬色澤"); h.add(effectController, "newTess", [2,3,4,5,6,8,10,12,16,24,32] ).name("鑲嵌等級"); // 材質(顏色) h = gui.addFolder("材質顏色"); h.add(effectController, "hue", 0.0, 1.0, 0.025).name("色相"); h.add(effectController, "saturation", 0.0, 1.0, 0.025).name("飽和度"); h.add(effectController, "lightness", 0.0, 1.0, 0.025).name("明度"); // 光源(顏色) h = gui.addFolder("光源顏色"); h.add(effectController, "lhue", 0.0, 1.0, 0.025).name("色相"); h.add(effectController, "lsaturation", 0.0, 1.0, 0.025).name("飽和度"); h.add(effectController, "llightness", 0.0, 1.0, 0.025).name("明度"); // 光源(方向) h = gui.addFolder("光源方向"); h.add(effectController, "lx", -1.0, 1.0, 0.025).name("X"); h.add(effectController, "ly", -1.0, 1.0, 0.025).name("Y"); h.add(effectController, "lz", -1.0, 1.0, 0.025).name("Z"); } function animate() { requestAnimationFrame(animate); render(); } function render() { var delta = clock.getDelta(); cameraControls.update(delta); if (effectController.newTess !== tess ) { tess = effectController.newTess; fillScene(); } phongBalancedMaterial.uniforms.shininess.value = effectController.shininess; phongBalancedMaterial.uniforms.uDropoff.value = effectController.dropoff; phongBalancedMaterial.uniforms.uKd.value = effectController.kd; phongBalancedMaterial.uniforms.uKs.value = effectController.ks; var materialColor = new THREE.Color(); materialColor.setHSL(effectController.hue, effectController.saturation, effectController.lightness); phongBalancedMaterial.uniforms.uMaterialColor.value.copy(materialColor); if (!effectController.metallic) { materialColor.setRGB(1, 1, 1); } phongBalancedMaterial.uniforms.uSpecularColor.value.copy(materialColor); // 環境光只是材質的顏色乘上 ka,無關光源的顏色 ambientLight.color.setHSL(effectController.hue, effectController.saturation, effectController.lightness * effectController.ka); light.position.set(effectController.lx, effectController.ly, effectController.lz); light.color.setHSL(effectController.lhue, effectController.lsaturation, effectController.llightness); renderer.render(scene, camera); } function fillScene() { scene = new THREE.Scene(); scene.fog = new THREE.Fog(0x808080, 2000, 4000); scene.add(camera); scene.add(ambientLight); scene.add(light); teapot = new THREE.Mesh( new THREE.TeapotGeometry(teapotSize, tess, true, true, true, true), phongBalancedMaterial); teapot.position.y = -teapotSize; scene.add(teapot); } function addToDOM() { var container = document.getElementById('container'); var canvas = container.getElementsByTagName('canvas'); if (canvas.length>0) { container.removeChild(canvas[0]); } container.appendChild( renderer.domElement ); } document.addEventListener("DOMContentLoaded", function () { try { init(); fillScene(); setupGui(); addToDOM(); animate(); } catch(e) { var errorReport = "你的程式遇到不可復原的錯誤,無法繪製 Canvas。錯誤是:<br/><br/>"; $('#container').append(errorReport+e); } })