Проблема вызвана тем, что в фрагментном шейдере vNormal
- это вектор в модели spac, а position
- это точка в пространстве модели, а customPointLightPos
- это позиция в мировом пространстве.
Вынеобходимо преобразовать vNormal
из пространства модели для просмотра пространства в вершинном шейдере.это можно сделать с помощью normalMatrix
, который предоставляется THREE.js.
Чтобы рассчитать вектор света, необходимо преобразовать position
из пространства модели в пространство просмотра, которое можетбыть сделано с помощью modelViewMatrix
.
И вам необходимо преобразовать customPointLightPos
из мирового пространства для просмотра пространства, что может быть сделано с помощью viewMatrix
:
vNormal = normalMatrix * normal;
vec4 viewPos = modelViewMatrix * vec4(position, 1.0);
vec4 viewLightPos = viewMatrix * vec4(customPointLightPos, 1.0);
lightVec = normalize(viewLightPos.xyz - viewPos.xyz);
Это приводит к тому, что оба вектора связаны для одной и той же системы отсчета и могут сравниваться и использоваться соответственно для расчета освещенности.
См. Eaxmaple, где я применил предложенные изменения к вашему исходному коду:
class PromisedLoad {
static GenericLoader(loader, url, callback) {
return new Promise((resolve, reject) => {
loader.load(url, (object) => {
if (callback) {
callback(object, resolve);
} else {
resolve(object);
}
}, (progress) => {
console.log(progress);
}, (error) => {
reject(error);
});
});
}
static GetGLTF(url, callback) {
let gltfLoader = new THREE.GLTFLoader();
return this.GenericLoader(gltfLoader, url, callback);
}
}
let vertexShader2 = `
uniform float time;
uniform vec3 materialColor;
uniform vec3 ambientLightColor;
uniform float ambientLightStrength;
uniform vec3 customPointLightPos;
varying vec3 vNormal;
varying vec3 lightVec;
void main() {
vNormal = normalMatrix * normal;
vec4 viewPos = modelViewMatrix * vec4(position, 1.0);
vec4 viewLightPos = viewMatrix * vec4(customPointLightPos, 1.0);
lightVec = normalize(viewLightPos.xyz - viewPos.xyz);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
// import customFragmentShader from './shaders/fragmentShader1.glsl';
let fragmentShader1 = `
uniform float time;
uniform vec3 materialColor;
uniform vec3 ambientLightColor;
uniform float ambientLightStrength;
uniform vec3 customPointLightPos;
varying vec3 vNormal;
varying vec3 lightVec;
void main() {
float dProd = max(0.0, dot(vNormal, lightVec));
vec3 c = mix(materialColor * dProd, ambientLightColor, ambientLightStrength);
gl_FragColor = vec4(c, 1.0);
}
`;
let mouse = new THREE.Vector2();
window.addEventListener('mousemove', onDocumentMouseMove, false);
document.addEventListener('DOMContentLoaded', () => {
let renderer,
camera,
scene = null;
const container = document.getElementById('container');
let controls;
let startTime, time;
let cube;
let rotSpeed = new THREE.Vector3(0.05, 0.03, 0.0);
let axesHelper;
let uniforms;
let customPointLight;
initialize();
// console.log('rotSpeed: ', rotSpeed);
// setupGUI();
async function initialize() {
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({
antialias: true // to get smoother output
});
renderer.setClearColor(0x3b3b3b);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
// create a camera in the scene
camera = new THREE.PerspectiveCamera(
35,
window.innerWidth / window.innerHeight,
1,
10000
);
axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
addCube();
addCustomPointLight();
controls = new THREE.OrbitControls(camera);
scene.add(camera);
camera.position.z = 10;
controls.update();
// and then just look at it!
camera.lookAt(scene.position);
controls.update();
window.onresize = resize;
animate();
}
function resize() {
var aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
}
function addCube() {
// let geometry = new THREE.SphereGeometry(1, 32, 32);
let geometry = new THREE.BoxGeometry(1,1,1);
uniforms = {
time: {
type: 'f',
value: 0
},
materialColor: {
type: 'v3f',
value: new THREE.Vector3(1.0, 0.0, 0.0)
},
ambientLightColor: {
type: 'v3f',
value: new THREE.Vector3(0.0, 0.0, 1.0)
},
ambientLightStrength: {
type: 'f',
value: 0.3
},
customPointLightPos: {
type: 'v3f',
value: new THREE.Vector3(2.0, 2.0, 2.0)
}
};
const shaderMaterialParams = {
uniforms: uniforms,
vertexShader: vertexShader2,
fragmentShader: fragmentShader1
};
const customMaterial = new THREE.ShaderMaterial(shaderMaterialParams);
cube = new THREE.Mesh(geometry, customMaterial);
scene.add(cube);
}
function addCustomPointLight() {
let geo = new THREE.BoxGeometry(0.3, 0.3, 0.3);
let mat = new THREE.MeshBasicMaterial();
customPointLight = new THREE.Mesh(geo, mat);
customPointLight.position.set(2, 2, 2);
scene.add(customPointLight);
}
function normalize(x, fromMin, fromMax) {
let totalRange;
x = Math.abs(x);
totalRange = Math.abs(fromMin) + Math.abs(fromMax);
// now we can map out the range from 0 to the totalRange and get a normalized (0 - 1) value
return x / totalRange;
}
function animate() {
requestAnimationFrame(animate);
time = performance.now() / 1000;
cube.material.uniforms.time.value = time;
cube.rotation.x += rotSpeed.x;
cube.rotation.y += rotSpeed.y;
render();
}
function render() {
renderer.render(scene, camera);
controls.update();
}
setupGUI(rotSpeed, uniforms, cube, customPointLight);
});
function setupGUI(rotSpeed, uniforms, cube, customPointLight) {
let options = {
velx: 0,
vely: 0,
rotSpeed: rotSpeed,
materialColor: uniforms.materialColor.value.toArray(),
ambientLightColor: uniforms.ambientLightColor.value.toArray(),
ambientLightStrength: uniforms.ambientLightStrength.value,
customPointLightPos: {
x: 2,
y: 2,
z: 2
}
};
let gui = new dat.GUI();
let rotation = gui.addFolder('Rotation');
rotation
.add(options.rotSpeed, 'x', -0.02, 0.02)
.name('X')
.listen();
rotation
.add(options.rotSpeed, 'y', -0.02, 0.02)
.name('Y')
.listen();
rotation.open();
let uniformsGUI = gui.addFolder('Uniforms');
uniformsGUI
.addColor(options, 'materialColor')
.onChange(function(value) {
cube.material.uniforms.materialColor.value.x = value[0] / 255;
cube.material.uniforms.materialColor.value.y = value[1] / 255;
cube.material.uniforms.materialColor.value.z = value[2] / 255;
})
.name('materialColor')
.listen();
uniformsGUI.addColor(options, 'ambientLightColor').onChange(function(value) {
cube.material.uniforms.ambientLightColor.value.x = value[0] / 255;
cube.material.uniforms.ambientLightColor.value.y = value[1] / 255;
cube.material.uniforms.ambientLightColor.value.z = value[2] / 255;
});
uniformsGUI
.add(options, 'ambientLightStrength', 0.0, 1.0)
.onChange(function(value) {
cube.material.uniforms.ambientLightStrength.value = value;
});
uniformsGUI.open();
let customPointLightGUI = gui.addFolder('Custom Point Light');
customPointLightGUI
.add(customPointLight.position, 'x', -5, 5)
.onChange(function(value) {
cube.material.uniforms.customPointLightPos.value.x = value;
});
customPointLightGUI
.add(customPointLight.position, 'y', -5, 5)
.onChange(function(value) {
cube.material.uniforms.customPointLightPos.value.y = value;
});
customPointLightGUI
.add(customPointLight.position, 'z', -5, 5)
.onChange(function(value) {
cube.material.uniforms.customPointLightPos.value.z = value;
});
customPointLightGUI.open();
let box = gui.addFolder('Cube');
box
.add(cube.scale, 'x', 0, 3)
.name('Width')
.listen();
box
.add(cube.scale, 'y', 0, 3)
.name('Height')
.listen();
box
.add(cube.scale, 'z', 0, 3)
.name('Length')
.listen();
box.add(cube.material, 'wireframe').listen();
box.open();
}
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
html, body {
margin: 0;
height: 100%;
width: 100%;
font-size: 100%;
font-family: 'Roboto', sans-serif;
text-align: center;
font-weight: lighter;
background: grey;
overflow-y: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/loaders/GLTFLoader.js"></script>
<div id="container"></div>