创建遵循光源的日/夜着色器 [英] creating a day/night shader that follows a light souce

查看:53
本文介绍了创建遵循光源的日/夜着色器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个由 DirectionalLight 照亮的球体,以模拟照在地球上的太阳.我正在尝试添加一个着色器,该着色器将在夜间在地球上未照亮的部分显示地球,并在白天显示地球上照亮的部分.我计划最终让 DirectionalLight 围绕地球旋转,更新着色器以显示当前处于阴影中的地球部分.我遇到了以下代码笔,它部分满足了我的要求:https://codepen.io/acauamontiel/pen/yvJoVv

在上面的代码笔中,显示的日/夜纹理基于相机相对于地球的位置,我需要它们相对于光源的位置保持固定,而不是相机的位置.

 构造函数(选择器){this.selector = 选择器;this.width = window.innerWidth;this.height = window.innerHeight;this.frameEvent = new Event('frame');this.textureLoader = new THREE.TextureLoader();}设置场景(){this.scene = new THREE.Scene();this.scenary = new THREE.Object3D;this.scene.add(this.scenary);}设置相机(){this.camera = new THREE.PerspectiveCamera(50, this.width/this.height, 1, 20000);this.camera.position.y = 25;this.camera.position.z = 300;}设置渲染器(){this.renderer = new THREE.WebGLRenderer({抗锯齿:真});this.renderer.setSize(this.width, this.height);this.canvas = document.querySelector(this.selector).appendChild(this.renderer.domElement);}设置控件(){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);}添加灯光(){this.ambientLight = new THREE.AmbientLight(0x555555);this.directionLight = new THREE.DirectionalLight(0xffffff);this.directionalLight.position.set(10, 0, 10).normalize();this.scenary.add(this.ambientLight);this.scenary.add(this.directionalLight);}使成为() {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();}添加天空(){让半径 = 400,段 = 50;this.skyGeometry = new THREE.SphereGeometry(半径,线段,线段);this.skyMaterial = new THREE.MeshPhongMaterial({颜色:0x666666,侧面:三.BackSide,光泽度:0});this.sky = new THREE.Mesh(this.skyGeometry, this.skyMaterial);this.scenary.add(this.sky);this.loadSkyTextures();}加载天空纹理(){this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/sky-texture.jpg', 纹理 => {this.skyMaterial.map = 纹理;this.skyMaterial.needsUpdate = true;});}添加地球(){让半径 = 100,段 = 50;this.earthGeometry = new THREE.SphereGeometry(半径、段、段);this.earthMaterial = 新的 THREE.ShaderMaterial({凹凸比例:5,高光:新三色(0x333333),光泽度:50,制服:{太阳方向:{值:新的 THREE.Vector3(1, 1, .5)},日纹理:{值: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')}},vertexShader: this.dayNightShader.vertex,片段着色器:this.dayNightShader.fragment});this.earth = new THREE.Mesh(this.earthGeometry, this.earthMaterial);this.scenary.add(this.earth);this.loadEarthTextures();this.addAtmosphere();}加载地球纹理(){this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-texture.jpg', 纹理 => {this.earthMaterial.map = 纹理;this.earthMaterial.needsUpdate = true;});this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-bump.jpg', 纹理 => {this.earthMaterial.bumpMap = 纹理;this.earthMaterial.needsUpdate = true;});this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-specular.jpg', 纹理 => {this.earthMaterial.specularMap = 纹理;this.earthMaterial.needsUpdate = true;});}添加气氛(){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);}得到 dayNightShader() {返回 {顶点:`不同的 vec2 vUv;不同的 vec3 vNormal;无效主(){vUv = uv;vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);vNormal = normalMatrix * normal;gl_Position = 投影矩阵 * mvPosition;}`,片段:`统一的 sampler2D dayTexture;统一的 sampler2D nightTexture;统一 vec3 sunDirection;不同的 vec2 vUv;不同的 vec3 vNormal;无效主(无效){vec3 dayColor = texture2D(dayTexture, vUv).rgb;vec3 nightColor = texture2D(nightTexture, vUv).rgb;float cosineAngleSunToNormal = dot(normalize(vNormal), sunDirection);cosineAngleSunToNormal = 钳位(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);}`}}动画(){this.canvas.addEventListener('frame', () => {this.scenary.rotation.x += 0.0001;this.scenary.rotation.y -= 0.0005;});}在里面() {this.setScene();this.setCamera();this.setRenderer();this.setControls();this.addLights();this.render();this.addSky();this.addEarth();this.animate();}}let canvas = new Canvas('#canvas');画布.init();

据我所知,看起来着色器正在由 get dayNightShader() 内部的相机更新.看起来modelViewMatrix、projectionMatrix和normalMatrix都基于我在three.js文档中可以找到的内容的相机,我已经尝试将它们更改为固定的矢量位置,但我唯一所做的看到它所做的是隐藏地球并显示大气纹理.有没有办法使用光源的位置来确定着色器显示的内容,而不是相机?

解决方案

问题是线路

<块引用>

float cosineAngleSunToNormal = dot(normalize(vNormal), sunDirection);

在片段着色器中.
vNormal 是视图空间中的一个方向,因为它是由顶点着色器中的 normalMatrix 变换的,而 sunDirection 是一个世界空间方向.

要解决这个问题,您必须通过顶点着色器中的视图矩阵转换太阳光方向,并将转换后的方向向量传递给片段着色器.

vSunDir = mat3(viewMatrix) * sunDirection;

注意,viewMatrix 从世界空间转换到视图空间.使用 viewMatrix 而不是 normalMatrix 很重要,因为 normalMatrix 从模型空间转换到世界空间.

顶点着色器:

不同的 vec2 vUv;不同的 vec3 vNormal;不同的 vec3 vSunDir;统一 vec3 sunDirection;无效主(){vUv = uv;vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);vNormal = normalMatrix * normal;vSunDir = mat3(viewMatrix) * sunDirection;gl_Position = 投影矩阵 * mvPosition;}

片段着色器:

uniform sampler2D dayTexture;统一的 sampler2D nightTexture;不同的 vec2 vUv;不同的 vec3 vNormal;不同的 vec3 vSunDir;无效主(无效){vec3 dayColor = texture2D(dayTexture, vUv).rgb;vec3 nightColor = texture2D(nightTexture, vUv).rgb;float cosineAngleSunToNormal = dot(normalize(vNormal), normalize(vSunDir));cosineAngleSunToNormal = 钳位(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);}

class Canvas {构造函数(选择器){this.selector = 选择器;this.width = window.innerWidth;this.height = window.innerHeight;this.frameEvent = new Event('frame');this.textureLoader = new THREE.TextureLoader();}设置场景(){this.scene = new THREE.Scene();this.scenary = new THREE.Object3D;this.scene.add(this.scenary);}设置相机(){this.camera = new THREE.PerspectiveCamera(50, this.width/this.height, 1, 20000);this.camera.position.y = 25;this.camera.position.z = 300;}设置渲染器(){this.renderer = new THREE.WebGLRenderer({抗锯齿:真});this.renderer.setSize(this.width, this.height);var container = document.getElementById(this.selector);this.canvas = container.appendChild(this.renderer.domElement);//this.canvas = document.querySelector(this.selector).appendChild(this.renderer.domElement);}设置控件(){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);}添加灯光(){this.ambientLight = new THREE.AmbientLight(0x555555);this.directionLight = new THREE.DirectionalLight(0xffffff);this.directionalLight.position.set(10, 0, 10).normalize();this.scenary.add(this.ambientLight);this.scenary.add(this.directionalLight);}使成为() {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();}添加天空(){让半径 = 400,段 = 50;this.skyGeometry = new THREE.SphereGeometry(半径,线段,线段);this.skyMaterial = new THREE.MeshPhongMaterial({颜色:0x666666,侧面:三.BackSide,光泽度:0});this.sky = new THREE.Mesh(this.skyGeometry, this.skyMaterial);this.scenary.add(this.sky);this.loadSkyTextures();}加载天空纹理(){this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/sky-texture.jpg', 纹理 => {this.skyMaterial.map = 纹理;this.skyMaterial.needsUpdate = true;});}添加地球(){让半径 = 100,段 = 50;this.earthGeometry = new THREE.SphereGeometry(半径、段、段);this.earthMaterial = 新的 THREE.ShaderMaterial({凹凸比例:5,高光:新三色(0x333333),光泽度:50,制服:{太阳方向:{值:新的 THREE.Vector3(1, 1, .5)},日纹理:{值: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')}},vertexShader: this.dayNightShader.vertex,片段着色器:this.dayNightShader.fragment});this.earth = new THREE.Mesh(this.earthGeometry, this.earthMaterial);this.scenary.add(this.earth);this.loadEarthTextures();this.addAtmosphere();}加载地球纹理(){this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-texture.jpg', 纹理 => {this.earthMaterial.map = 纹理;this.earthMaterial.needsUpdate = true;});this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-bump.jpg', 纹理 => {this.earthMaterial.bumpMap = 纹理;this.earthMaterial.needsUpdate = true;});this.textureLoader.load('https://acaua.gitlab.io/webgl-with-threejs/img/textures/earth/earth-specular.jpg', 纹理 => {this.earthMaterial.specularMap = 纹理;this.earthMaterial.needsUpdate = true;});}添加气氛(){/*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);*/}得到 dayNightShader() {返回 {顶点:`不同的 vec2 vUv;不同的 vec3 vNormal;不同的 vec3 vSunDir;统一 vec3 sunDirection;无效主(){vUv = uv;vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);vNormal = normalMatrix * normal;vSunDir = mat3(viewMatrix) * sunDirection;gl_Position = 投影矩阵 * mvPosition;}`,片段:`统一的 sampler2D dayTexture;统一的 sampler2D nightTexture;不同的 vec2 vUv;不同的 vec3 vNormal;不同的 vec3 vSunDir;无效主(无效){vec3 dayColor = texture2D(dayTexture, vUv).rgb;vec3 nightColor = texture2D(nightTexture, vUv).rgb;float cosineAngleSunToNormal = dot(normalize(vNormal), normalize(vSunDir));cosineAngleSunToNormal = 钳位(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);}`}}动画(){this.canvas.addEventListener('frame', () => {this.scenary.rotation.x += 0.0001;this.scenary.rotation.y -= 0.0005;});}在里面() {this.setScene();this.setCamera();this.setRenderer();this.setControls();this.addLights();this.render();this.addSky();this.addEarth();this.animate();}}let canvas = new Canvas('container');canvas.init();

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js"></script><script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script><div id="container"></div>

I have a sphere lit by a DirectionalLight to mimic the sun shining on the earth. I am trying to add a shader that will show the earth at night on the unlit parts of the globe, and the earth during the day for the lit parts. I'm planning on eventually having the DirectionalLight rotate around the globe, updating the shader to show the parts of the earth that are currently in shadow. I came across the following codepen that partially does what I want: https://codepen.io/acauamontiel/pen/yvJoVv

In the codepen above, the day/night textures shown are based on the camera's position in relation to the globe, and I need those to stay fixed relative to the light source's position, rather than the camera's.

    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();

From what I can tell, it looks like the shader is being updated by the camera inside of get dayNightShader(). It looks like the modelViewMatrix, projectionMatrix, and normalMatrix are all based on the camera based on what I could find in the documentation for three.js, and I've tried changing these to a fixed vector position, but the only thing I've seen it do is hide the globe and show the atmosphere texture. Is there a way to use the light source's position to determine what the shader shows, rather than the camera?

解决方案

The issue is the line

float cosineAngleSunToNormal = dot(normalize(vNormal), sunDirection); 

in the fragment shader.
vNormal is a direction in view space, because it is transformed by the normalMatrix in the vertex shader, but sunDirection is a world space direction.

To solve the issue you've to transform the sun light direction by the view matrix in the vertex shader and to pass the transformed direction vector to the fragment shader.

vSunDir = mat3(viewMatrix) * sunDirection;

Note, the viewMatrix transforms from world space to view space. It is important to use the viewMatrix rather than the normalMatrix, because the normalMatrix transforms from model space to world space.

Vertex shader:

varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vSunDir;

uniform vec3 sunDirection;

void main() {
    vUv = uv;
    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);

    vNormal = normalMatrix * normal;
    vSunDir = mat3(viewMatrix) * sunDirection;

    gl_Position = projectionMatrix * mvPosition;
}

Fragment shader:

uniform sampler2D dayTexture;
uniform sampler2D nightTexture;

varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vSunDir;

void main(void) {
    vec3 dayColor = texture2D(dayTexture, vUv).rgb;
    vec3 nightColor = texture2D(nightTexture, vUv).rgb;

    float cosineAngleSunToNormal = dot(normalize(vNormal), normalize(vSunDir));

    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);
}

class Canvas {
	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);
    var container = document.getElementById(this.selector);
    this.canvas = container.appendChild(this.renderer.domElement);
		//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;
        varying vec3 vSunDir;

        uniform vec3 sunDirection;

				void main() {
					vUv = uv;
					vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
          vNormal = normalMatrix * normal;
          vSunDir = mat3(viewMatrix) * sunDirection;
					gl_Position = projectionMatrix * mvPosition;
				}
			`,
			fragment: `
				uniform sampler2D dayTexture;
				uniform sampler2D nightTexture;

				varying vec2 vUv;
        varying vec3 vNormal;
        varying vec3 vSunDir;

				void main(void) {
					vec3 dayColor = texture2D(dayTexture, vUv).rgb;
					vec3 nightColor = texture2D(nightTexture, vUv).rgb;

					float cosineAngleSunToNormal = dot(normalize(vNormal), normalize(vSunDir));

					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('container');
canvas.init();

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div id="container"></div>

这篇关于创建遵循光源的日/夜着色器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆