Javascript 创建跟随光源的昼/夜着色器
我有一个由定向光照亮的球体来模拟太阳照射地球。我正在尝试添加一个着色器,该着色器将在地球的未照明部分显示夜间的地球,并在白天为照明部分显示地球。我计划最终让方向光围绕地球旋转,更新着色器以显示当前处于阴影中的地球部分。我发现以下代码笔部分满足了我的要求: 在上面的代码笔中,显示的昼夜纹理基于相机相对于地球仪的位置,我需要这些纹理相对于光源的位置而不是相机的位置保持固定Javascript 创建跟随光源的昼/夜着色器,javascript,three.js,shader,Javascript,Three.js,Shader,我有一个由定向光照亮的球体来模拟太阳照射地球。我正在尝试添加一个着色器,该着色器将在地球的未照明部分显示夜间的地球,并在白天为照明部分显示地球。我计划最终让方向光围绕地球旋转,更新着色器以显示当前处于阴影中的地球部分。我发现以下代码笔部分满足了我的要求: 在上面的代码笔中,显示的昼夜纹理基于相机相对于地球仪的位置,我需要这些纹理相对于光源的位置而不是相机的位置保持固定 constructor(selector) { this.selector = selector;
constructor(selector) {
this.selector = selector;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.frameEvent = new Event('frame');
this.textureLoader = new THREE.TextureLoader();
}
setScene() {
this.scene = new THREE.Scene();
this.scenary = new THREE.Object3D;
this.scene.add(this.scenary);
}
setCamera() {
this.camera = new THREE.PerspectiveCamera(50, this.width/this.height, 1, 20000);
this.camera.position.y = 25;
this.camera.position.z = 300;
}
setRenderer() {
this.renderer = new THREE.WebGLRenderer({
antialias: true
});
this.renderer.setSize(this.width, this.height);
this.canvas = document.querySelector(this.selector).appendChild(this.renderer.domElement);
}
setControls() {
this.controls = new THREE.OrbitControls(this.camera, this.canvas);
this.controls.maxDistance = 500;
this.controls.minDistance = 200;
}
addHelpers() {
this.axes = new THREE.AxesHelper(500);
this.scenary.add(this.axes);
}
addLights() {
this.ambientLight = new THREE.AmbientLight(0x555555);
this.directionalLight = new THREE.DirectionalLight(0xffffff);
this.directionalLight.position.set(10, 0, 10).normalize();
this.scenary.add(this.ambientLight);
this.scenary.add(this.directionalLight);
}
render() {
this.renderer.render(this.scene, this.camera);
this.canvas.dispatchEvent(this.frameEvent);
this.frameRequest = window.requestAnimationFrame(this.render.bind(this));
}
destroy() {
window.cancelAnimationFrame(this.frameRequest);
this.scene.children = [];
this.canvas.remove();
}
addSky() {
let radius = 400,
segments = 50;
this.skyGeometry = new THREE.SphereGeometry(radius, segments, segments);
this.skyMaterial = new THREE.MeshPhongMaterial({
color: 0x666666,
side: THREE.BackSide,
shininess: 0
});
this.sky = new THREE.Mesh(this.skyGeometry, this.skyMaterial);
this.scenary.add(this.sky);
this.loadSkyTextures();
}
loadSkyTextures() {
this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/sky-texture.jpg', texture => {
this.skyMaterial.map = texture;
this.skyMaterial.needsUpdate = true;
});
}
addEarth() {
let radius = 100,
segments = 50;
this.earthGeometry = new THREE.SphereGeometry(radius, segments, segments);
this.earthMaterial = new THREE.ShaderMaterial({
bumpScale: 5,
specular: new THREE.Color(0x333333),
shininess: 50,
uniforms: {
sunDirection: {
value: new THREE.Vector3(1, 1, .5)
},
dayTexture: {
value: this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-texture.jpg')
},
nightTexture: {
value: this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-night.jpg')
}
},
vertexShader: this.dayNightShader.vertex,
fragmentShader: this.dayNightShader.fragment
});
this.earth = new THREE.Mesh(this.earthGeometry, this.earthMaterial);
this.scenary.add(this.earth);
this.loadEarthTextures();
this.addAtmosphere();
}
loadEarthTextures() {
this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-texture.jpg', texture => {
this.earthMaterial.map = texture;
this.earthMaterial.needsUpdate = true;
});
this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-bump.jpg', texture => {
this.earthMaterial.bumpMap = texture;
this.earthMaterial.needsUpdate = true;
});
this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-specular.jpg', texture => {
this.earthMaterial.specularMap = texture;
this.earthMaterial.needsUpdate = true;
});
}
addAtmosphere() {
this.innerAtmosphereGeometry = this.earthGeometry.clone();
this.innerAtmosphereMaterial = THREEx.createAtmosphereMaterial();
this.innerAtmosphereMaterial.uniforms.glowColor.value.set(0x88ffff);
this.innerAtmosphereMaterial.uniforms.coeficient.value = 1;
this.innerAtmosphereMaterial.uniforms.power.value = 5;
this.innerAtmosphere = new THREE.Mesh(this.innerAtmosphereGeometry, this.innerAtmosphereMaterial);
this.innerAtmosphere.scale.multiplyScalar(1.008);
this.outerAtmosphereGeometry = this.earthGeometry.clone();
this.outerAtmosphereMaterial = THREEx.createAtmosphereMaterial();
this.outerAtmosphereMaterial.side = THREE.BackSide;
this.outerAtmosphereMaterial.uniforms.glowColor.value.set(0x0088ff);
this.outerAtmosphereMaterial.uniforms.coeficient.value = .68;
this.outerAtmosphereMaterial.uniforms.power.value = 10;
this.outerAtmosphere = new THREE.Mesh(this.outerAtmosphereGeometry, this.outerAtmosphereMaterial);
this.outerAtmosphere.scale.multiplyScalar(1.06);
this.earth.add(this.innerAtmosphere);
this.earth.add(this.outerAtmosphere);
}
get dayNightShader() {
return {
vertex: `
varying vec2 vUv;
varying vec3 vNormal;
void main() {
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vNormal = normalMatrix * normal;
gl_Position = projectionMatrix * mvPosition;
}
`,
fragment: `
uniform sampler2D dayTexture;
uniform sampler2D nightTexture;
uniform vec3 sunDirection;
varying vec2 vUv;
varying vec3 vNormal;
void main(void) {
vec3 dayColor = texture2D(dayTexture, vUv).rgb;
vec3 nightColor = texture2D(nightTexture, vUv).rgb;
float cosineAngleSunToNormal = dot(normalize(vNormal), sunDirection);
cosineAngleSunToNormal = clamp(cosineAngleSunToNormal * 5.0, -1.0, 1.0);
float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;
vec3 color = mix(nightColor, dayColor, mixAmount);
gl_FragColor = vec4(color, 1.0);
}
`
}
}
animate() {
this.canvas.addEventListener('frame', () => {
this.scenary.rotation.x += 0.0001;
this.scenary.rotation.y -= 0.0005;
});
}
init() {
this.setScene();
this.setCamera();
this.setRenderer();
this.setControls();
this.addLights();
this.render();
this.addSky();
this.addEarth();
this.animate();
}
}
let canvas = new Canvas('#canvas');
canvas.init();
据我所知,该着色器似乎正在由get dayNightShader()中的摄影机更新。看起来modelViewMatrix、projectionMatrix和normalMatrix都是基于相机的,基于我在three.js的文档中可以找到的内容,我尝试将它们更改为固定的矢量位置,但我看到它所做的唯一一件事就是隐藏球体并显示大气纹理。有没有办法使用光源的位置来确定着色器显示的内容,而不是摄影机?问题在于线条
float cosinanglesuntonormal=dot(归一化(vNormal),sunddirection);
在片段着色器中。vNormal
是视图空间中的一个方向,因为它由顶点着色器中的normalMatrix
变换,但sunddirection
是世界空间方向
要解决此问题,必须通过顶点着色器中的视图矩阵变换日光方向,并将变换后的方向向量传递给片段着色器
vSunDir=mat3(viewMatrix)*sunDirection;
注意,viewMatrix
从世界空间转换到视图空间。使用viewMatrix
而不是normalMatrix
很重要,因为normalMatrix
从模型空间转换到世界空间
顶点着色器:
可变vec2vuv;
可变vec3 vNormal;
可变vec3-vSunDir;
均匀的vec3太阳方向;
void main(){
vUv=紫外线;
vec4 mvPosition=modelViewMatrix*vec4(位置,1.0);
vNormal=法线矩阵*法线;
vSunDir=mat3(视图矩阵)*太阳方向;
gl_位置=投影矩阵*mvPosition;
}
片段着色器:
均匀纹理;
纹理均匀;
可变vec2 vUv;
可变vec3 vNormal;
可变vec3-vSunDir;
真空总管(真空){
vec3 dayColor=texture2D(dayTexture,vUv).rgb;
vec3 nightColor=texture2D(nightTexture,vUv).rgb;
浮点余弦角SUNTONORMAL=点(规格化(VNOMAL),规格化(vSunDir));
cosinanglesuntonormal=夹具(cosinanglesuntonormal*5.0,-1.0,1.0);
浮动混合量=余弦角SUNTONORMAL*0.5+0.5;
vec3颜色=混合(夜色、日色、混合量);
gl_FragColor=vec4(颜色,1.0);
}
类画布{
构造函数(选择器){
this.selector=选择器;
this.width=window.innerWidth;
this.height=window.innerHeight;
this.frameEvent=新事件(“帧”);
this.textureLoader=新的三个.textureLoader();
}
setScene(){
this.scene=新的三个.scene();
this.scenary=new THREE.Object3D;
this.scene.add(this.sceneary);
}
setCamera(){
this.camera=新的三视角摄像机(50,this.width/this.height,12000);
这个.camera.position.y=25;
这个.camera.position.z=300;
}
setRenderer(){
this.renderer=new THREE.WebGLRenderer({
反别名:对
});
this.renderer.setSize(this.width,this.height);
var container=document.getElementById(this.selector);
this.canvas=container.appendChild(this.renderer.doElement);
//this.canvas=document.querySelector(this.selector).appendChild(this.renderer.domeElement);
}
setControls(){
this.controls=新的三个.OrbitControls(this.camera,this.canvas);
this.controls.maxDistance=500;
this.controls.minDistance=200;
}
addHelpers(){
this.axes=新的三轴辅助(500);
this.scenary.add(this.axes);
}
addLights(){
this.ambientLight=新的三个ambientLight(0x555555);
this.directionalLight=新的三个方向灯(0xffffff);
this.directionalLight.position.set(10,0,10).normalize();
this.scenary.add(this.ambientLight);
this.scenary.add(this.directionalLight);
}
render(){
this.renderer.render(this.scene,this.camera);
this.canvas.dispatchEvent(this.frameEvent);
this.frameRequest=window.requestAnimationFrame(this.render.bind(this));
}
销毁{
window.cancelAnimationFrame(this.frameRequest);
this.scene.children=[];
this.canvas.remove();
}
addSky(){
假设半径=400,
分段=50;
this.skyGeometry=新的三个。球面测量法(半径、分段、分段);
this.skyMaterial=新的3.MeshPhongMaterial({
颜色:0x666666,
侧面:三,背面,
光泽度:0
});
this.sky=new THREE.Mesh(this.skyGeometry,this.skyMaterial);
this.sceneary.add(this.sky);
这是loadSkyTextures();
}
loadSkyTextures(){
此.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/sky-texture.jpg,纹理=>{
this.skymater.map=纹理;
this.skymater.needsUpdate=true;
});
}
addEarth(){
假设半径=100,
分段=50;
this.earthGeometry=新的三种球墨测量法(半径、分段、分段);
this.earthMaterial=新的三个.ShaderMaterial({
比例:5,
镜面反射:新三色(0x333333),
光泽度:50,,
制服:{
太阳方向:{
值:新的3.Vector3(1,1,5)
},
dayTexture:{
值:this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-texture.jpg')
},
夜纹理:{
值:this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-night.jpg')
}
},
版本