【问题标题】:Adding night lights to a WebGL / Three.js earth将夜灯添加到 WebGL / Three.js 地球
【发布时间】:2017-08-24 09:49:02
【问题描述】:

我正在使用 Three.js 作为开发空间模拟器的框架,我正在尝试,但未能让夜灯工作。

模拟器可以在这里访问:

orbitingeden.com

运行下面代码sn-p的页面可以在这里找到:

orbitingeden.com/orrery/soloearth.html

示例页面的代码在这里。我什至不知道从哪里开始。我尝试渲染两个相隔几个单位的球体,一个靠近太阳(白天版本),一个更靠近太阳(夜间版本),但有很多问题,其中最重要的是它们开始以奇怪的十二面体形式相互重叠方法。我从orrery 中采用了 tDiffuse2 的想法,但无法让它发挥作用。

<!doctype html>
<html lang="en">
    <head>
        <title>three.js webgl - earth</title>
        <meta charset="utf-8">
        <script src="three.js/Detector.js"></script>
        <script src="three.js/Three.js"></script>
    </head>
    <body>
        <script>
            if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

            var radius = 6371;
            var tilt = 0.41;
            var rotationSpeed = 0.02;
            var cloudsScale = 1.005;
            var SCREEN_HEIGHT = window.innerHeight;
            var SCREEN_WIDTH  = window.innerWidth;
            var container, camera, scene, renderer;
            var meshPlanet, meshClouds, dirLight, ambientLight;
            var clock = new THREE.Clock();

            init();
            animate();

            function init() {
                container = document.createElement( 'div' );
                document.body.appendChild( container );

                scene = new THREE.Scene();
                scene.fog = new THREE.FogExp2( 0x000000, 0.00000025 );

                camera = new THREE.PerspectiveCamera( 25, SCREEN_WIDTH / SCREEN_HEIGHT, 50, 1e7 );
                camera.position.z = radius * 5;
                scene.add( camera );

                dirLight = new THREE.DirectionalLight( 0xffffff );
                dirLight.position.set( -20, 0, 2 ).normalize();
                scene.add( dirLight );

                ambientLight = new THREE.AmbientLight( 0x000000 );
                scene.add( ambientLight );

                //initialize the earth
                var planetTexture = THREE.ImageUtils.loadTexture( "textures/earth-day.jpg" ),
                nightTexture      = THREE.ImageUtils.loadTexture( "textures/earthNight.gif" ),
                cloudsTexture     = THREE.ImageUtils.loadTexture( "textures/clouds.gif" ),
                normalTexture     = THREE.ImageUtils.loadTexture( "textures/earth-map.jpg" ),
                specularTexture   = THREE.ImageUtils.loadTexture( "textures/earth-specular.jpg" );
                var shader = THREE.ShaderUtils.lib[ "normal" ];
                var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
                uniforms[ "tNormal" ].texture = normalTexture;
                uniforms[ "uNormalScale" ].value = 0.85;
                uniforms[ "tDiffuse" ].texture = planetTexture;
                uniforms[ "tDiffuse2" ].texture = nightTexture;
                uniforms[ "tSpecular" ].texture = specularTexture;
                uniforms[ "enableAO" ].value = false;
                uniforms[ "enableDiffuse" ].value = true;
                uniforms[ "enableSpecular" ].value = true;
                uniforms[ "uDiffuseColor" ].value.setHex( 0xffffff );
                uniforms[ "uSpecularColor" ].value.setHex( 0x333333 );
                uniforms[ "uAmbientColor" ].value.setHex( 0x000000 );
                uniforms[ "uShininess" ].value = 15;
                var parameters = {
                    fragmentShader: shader.fragmentShader,
                    vertexShader: shader.vertexShader,
                    uniforms: uniforms,
                    lights: true,
                    fog: true
                };
                var materialNormalMap = new THREE.ShaderMaterial( parameters );
                geometry = new THREE.SphereGeometry( radius, 100, 50 );
                geometry.computeTangents();
                meshPlanet = new THREE.Mesh( geometry, materialNormalMap );
                meshPlanet.rotation.y = 0;
                meshPlanet.rotation.z = tilt;
                scene.add( meshPlanet );

                // clouds
                var materialClouds = new THREE.MeshLambertMaterial( { color: 0xffffff, map: cloudsTexture, transparent: true } );
                meshClouds = new THREE.Mesh( geometry, materialClouds );
                meshClouds.scale.set( cloudsScale, cloudsScale, cloudsScale );
                meshClouds.rotation.z = tilt;
                scene.add( meshClouds );

                renderer = new THREE.WebGLRenderer( { clearColor: 0x000000, clearAlpha: 1 } );
                renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
                renderer.sortObjects = false;
                renderer.autoClear = false;
                container.appendChild( renderer.domElement );
            };

            function animate() {
                requestAnimationFrame( animate );
                render();
            };

            function render() {
                // rotate the planet and clouds
                var delta = clock.getDelta();
                meshPlanet.rotation.y += rotationSpeed * delta;
                meshClouds.rotation.y += 1.25 * rotationSpeed * delta;
                //render the scene
                renderer.clear();
                renderer.render( scene, camera );
            };
        </script>
    </body>
</html>

【问题讨论】:

    标签: webgl three.js


    【解决方案1】:

    如果我明白你的问题....

    我不知道three.js,但一般来说,我会通过让一个已经通过白天和夜间纹理的着色器然后在着色器中选择一个或另一个来做到这一点。例如

    uniform sampler2D dayTexture;
    uniform sampler2D nightTexture;
    varying vec3 v_surfaceToLight;  // assumes this gets passed in from vertex shader
    varying vec4 v_normal;          // assumes this gets passed in from vertex shader
    varying vec2 v_texCoord;        // assumes this gets passed in from vertex shader
    
    void main () {
       vec3 normal = normalize(v_normal);
       vec3 surfaceToLight = normalize(v_surfaceToLight);
       float angle = dot(normal, surfaceToLight);
       vec4 dayColor = texture2D(dayTexture, v_texCoords);
       vec4 nightColor = texture2D(nightTexture, v_texCoord);
       vec4 color = angle < 0.0 ? dayColor : nightColor;
    
       ...
    
       gl_FragColor = color * ...;
    }
    

    基本上,您进行光照计算,而不是将其用于光照,而是使用它来选择纹理。光照计算通常使用表面法线与来自表面的光(太阳)方向之间的点积。这为您提供了这些与向量之间夹角的余弦值。余弦从 -1 到 1,所以如果值是从 -1 到 0,则它背向太阳,如果它是 0 到 +1,则它面向太阳。

    线

       vec4 color = angle < 0.0 ? dayColor : nightColor;
    

    选择白天或夜晚。这将是一个严厉的截止日期。你可以尝试一些更模糊的东西,比如

       // convert from -1 <-> +1 to 0 <-> +1
       float lerp0To1 = angle * 0.5 + 0.5; 
    
       // mix between night and day
       vec4 color = mix(nightColor, dayColor, lerp0to1);
    

    这将使您 100% 在直接面对太阳的地点享受白天,在直接面对太阳的地点享受 100% 的夜晚,以及两者之间的混合。可能不是你想要的,但你可以对数字大发雷霆。例如

       // sharpen the mix
       angle = clamp(angle * 10.0, -1.0, 1.0);
    
       // convert from -1 <-> +1 to 0 <-> +1
       float lerp0To1 = angle * 0.5 + 0.5; 
    
       // mix between night and day
       vec4 color = mix(nightColor, dayColor, lerp0to1);
    

    希望这是有道理的。


    所以我花了一点时间编写 Three.js 示例,部分是为了学习 Three.js。样品在这里。

    const vs = `
    varying vec2 vUv;
    varying vec3 vNormal;
    
    void main() {
      vUv = uv;
      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
      vNormal = normalMatrix * normal;
      gl_Position = projectionMatrix * mvPosition;
    }
    `;
    
    const fs = `
    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;
    
      // compute cosine sun to normal so -1 is away from sun and +1 is toward sun.
      float cosineAngleSunToNormal = dot(normalize(vNormal), sunDirection);
    
      // sharpen the edge beween the transition
      cosineAngleSunToNormal = clamp( cosineAngleSunToNormal * 10.0, -1.0, 1.0);
    
      // convert to 0 to 1 for mixing
      float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;
    
      // Select day or night texture based on mix.
      vec3 color = mix( nightColor, dayColor, mixAmount );
    
      gl_FragColor = vec4( color, 1.0 );
    }
    
    `;
    
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(40, 1, 1, 3000);
    camera.position.z = 4;
    scene.add( camera );
    
    const directionalLight = new THREE.DirectionalLight( 0xaaff33, 0 );
    directionalLight.position.set(-1, 1, 0.5).normalize();
    scene.add( directionalLight );
    
    const textureLoader = new THREE.TextureLoader();
    
    const uniforms = {
      sunDirection: {value: new THREE.Vector3(0,1,0) },
      dayTexture: { value: textureLoader.load( "https://i.imgur.com/dfLCd19.jpg" ) },
      nightTexture: { value: textureLoader.load( "https://i.imgur.com/MeKgLts.jpg" ) }
    };
    
    const material = new THREE.ShaderMaterial({
      uniforms: uniforms,
      vertexShader: vs,
      fragmentShader: fs,
    });
    
    const mesh = new THREE.Mesh( new THREE.SphereGeometry( 0.75, 32, 16 ), material );
    scene.add( mesh );
    
    renderer = new THREE.WebGLRenderer();
    document.body.appendChild(renderer.domElement);
    resize(true);
    requestAnimationFrame(render);
    
    function resize(force) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      if (force || canvas.width !== width || canvas.height !== height) {
        renderer.setSize(width, height, false);
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
      }
    }
    
    function render(time) {
      time *= 0.001;  // seconds
      
      resize();
      
      uniforms.sunDirection.value.x = Math.sin(time);
      uniforms.sunDirection.value.y = Math.cos(time);
    
      // Note: Since the earth is at 0,0,0 you can set the normal for the sun
      // with
      //
      // uniforms.sunDirection.value.copy(sunPosition);
      // uniforms.sunDirection.value.normalize();
    
    
      mesh.rotation.y = time * .3
      mesh.rotation.x = time * .7;
    
      renderer.render(scene, camera);
    
      requestAnimationFrame(render);
    }
    body { margin: 0; }
    canvas { width: 100vw; height: 100vh; display: block; }
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"&gt;&lt;/script&gt;

    我用的shader是这个

    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;
    
        // compute cosine sun to normal so -1 is away from sun and +1 is toward sun.
        float cosineAngleSunToNormal = dot(normalize(vNormal), sunDirection);
    
        // sharpen the edge beween the transition
        cosineAngleSunToNormal = clamp( cosineAngleSunToNormal * 10.0, -1.0, 1.0);
    
        // convert to 0 to 1 for mixing
        float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;
    
        // Select day or night texture based on mixAmount.
        vec3 color = mix( nightColor, dayColor, mixAmount );
    
        gl_FragColor = vec4( color, 1.0 );
    
        // comment in the next line to see the mixAmount
        //gl_FragColor = vec4( mixAmount, mixAmount, mixAmount, 1.0 );
    }
    

    与上面的最大区别在于,由于太阳通常被认为是定向光,因为它是如此遥远,那么您所需要的只是它的方向。换句话说,它相对于地球指向哪个方向。

    【讨论】:

    • 您会因出色的 webgl 教程而获得 +1。最终我还是需要进入 webgl,所以很高兴有人能很好地解释我的一个问题。如果没有其他人提出直接的 THREE.js 解决方案,我将尝试合并您的解决方案,如果可行,我会给您绿色检查。你知道如何将它整合到我目前的 THREE.js 工作中,还是我需要直接使用 webgl 定义整个地球?
    • 您会因为发布该样本而获得一张当之无愧的绿色支票。现在由我来实施。非常非常感谢!
    • 嗨,我正在尝试在 THREE.js 中通过修改他们现有的法线贴图着色器来实现这一点。出于某种原因,我只能看到除 1.0 以外的所有值的夜间纹理,它显示了白天纹理。换句话说,我似乎无法顺利混音。任何想法为什么?
    • 我在这里发了一个问题:stackoverflow.com/questions/11084269/…
    • 该示例适用于您使用的 Three.js 版本(版本 49),但不适用于更新的版本(版本 62 或 69)。地球从四面八方全是黑色的。我需要在您的代码中进行哪些更改才能使其与当前版本的 Three.js 一起工作?
    【解决方案2】:

    感谢您的分享 - 非常有用。虽然我现在确定为什么当相机旋转时阴影不会背向太阳(它相对于相机保持静止)。这是我用来设置 sunDirection 统一的代码:

    this.uniforms.sunDirection.value.copy(this.sunPosition); this.uniforms.sunDirection.value.normalize();

    不知道为什么……

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-21
      • 1970-01-01
      • 2018-12-08
      • 1970-01-01
      • 2017-01-18
      • 1970-01-01
      • 1970-01-01
      • 2013-02-01
      相关资源
      最近更新 更多