WebGL - глобус с ShaderMaterial

У меня проблема при повороте дневного шара, созданного ShaderMaterial. Когда я поворачиваю шар, дневная зона не смещается в направлении солнца.

// Globe Sun setup
    start_time = getUTCTime() * rotateTime;

    var sunDirection = new THREE.Vector3(1.0, 0.0, 0.0);
    sunDirection.x = Math.cos(start_time);
    sunDirection.y = 0.0;
    sunDirection.z = Math.sin(start_time);

    uniforms = {
        sunDirection: {type: "v3", value: sunDirection},
        dayTexture: {type: "t", value: new THREE.TextureLoader().load("globe/img/earth-day.jpg")},
        nightTexture: {type: "t", value: new THREE.TextureLoader().load("globe/img/earth-night.jpg")}
    };

    var material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: document.getElementById('vertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent
    });

    var geometry = new THREE.SphereGeometry(globeRadius, 64, 64);

    mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

Вот код GSTL

<script id="fragmentShader" type="x-shader/x-fragment">
    uniform sampler2D dayTexture;
    uniform sampler2D nightTexture;

    uniform vec3 sunDirection;

    varying vec2 vUv;
    varying vec3 vNormal;

    void main( void ) {
        vec4 dayColor = texture2D(dayTexture, vUv);
        vec4 nightColor = texture2D(nightTexture, vUv);

        // 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 between the transition
        cosineAngleSunToNormal = clamp(cosineAngleSunToNormal * 10.0, -1.0, 3.0);

        // convert to 0 to 1 for mixing
        float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;

        // Select day or night texture based on mix.
        vec4 color = mix(nightColor, dayColor, mixAmount);

        gl_FragColor = color;
    }
</script>

<script id="vertexShader" type="x-shader/x-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;
    }
</script>

Область дневной ночи будет установлена ​​в соответствии с текущим временем UTC. Когда я поворачиваю глобус, вращается только глобус, область солнца не перемещается. Вот пример, который я хочу сделать как.

1 ответ

Чтобы сделать то, что вы хотите, вы должны преобразовать направление на солнце путем ориентации матрицы вида модели. Эта матрица ориентации 3*3 называется нормальной матрицей и может быть рассчитана путем обратного транспонирования верхних левых 3*3 элементов матрицы вида модели.

К счастью, THREE.js предоставляет встроенную форму, включая эту нормальную матрицу. См. WebGLProgram - Встроенная униформа.

Все, что вам нужно сделать, это объявить единую переменную с именем normalMatrix типа mat3 и преобразовать sunDirection по этой матрице во фрагментном шейдере:

uniform sampler2D dayTexture;
uniform sampler2D nightTexture;

uniform mat3 normalMatrix;
uniform vec3 sunDirection;

varying vec2 vUv;
varying vec3 vNormal;

void main( void ) {
    vec4 dayColor = texture2D(dayTexture, vUv);
    vec4 nightColor = texture2D(nightTexture, vUv);

    vec3 dir = normalize(normalMatrix * sunDirection);

    // compute cosine sun to normal so -1 is away from sun and +1 is toward sun.
    float cosineAngleSunToNormal = dot(normalize(vNormal), dir);

    // sharpen the edge between the transition
    cosineAngleSunToNormal = clamp(cosineAngleSunToNormal * 10.0, -1.0, 3.0);

    // convert to 0 to 1 for mixing
    float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;

    // Select day or night texture based on mix.
    vec4 color = mix(nightColor, dayColor, mixAmount);

    gl_FragColor = color;
} 

Если вы хотите изменить форму sunDirection само по себе, то достаточно изменить THREE.Vector3 что соответствует униформе.

например, создать анимацию дня и ночи, последовательно манипулируя направлением солнца в render функция:

var time = 0.0;
var sunDirection = new THREE.Vector3(1.0, 0.0, 0.0);

function render() {
    time += 0.03;
    sunDirection.set(Math.cos(time), 0.0, Math.sin(time))
    renderer.render(scene, camera);
}   

Смотрите пример:

(function onLoad() {
  var container, loader, camera, scene, renderer, orbitControls;

  var time = 0.0;
  var sunDirection = new THREE.Vector3(1.0, 0.0, 0.0);
  
  init();
  animate();

  function init() {
    container = document.getElementById('container');
    
    renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    container.appendChild(renderer.domElement);

    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 100);
    camera.position.set(0, 0, -2.5);

    loader = new THREE.TextureLoader();
    loader.setCrossOrigin("");

    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000);
    scene.add(camera);
    window.onresize = resize;
    
    orbitControls = new THREE.OrbitControls(camera);
     
    createModel();
  }

  function createModel() {
    var globeRadius = 1.0;
    
    uniforms = {
        sunDirection: {type: "v3", value: sunDirection},
        dayTexture: {type: "t", value: new THREE.TextureLoader().load("https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/worldmap_day.png")},
        nightTexture: {type: "t", value: new THREE.TextureLoader().load("https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/worldmap_night.png")}
    };

    var material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: document.getElementById('vertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent
    });

    var geometry = new THREE.SphereGeometry(globeRadius, 64, 64);

    mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
  }

  function resize() {
    
    var aspect = window.innerWidth / window.innerHeight;
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = aspect;
    camera.updateProjectionMatrix();
  }

  function animate() {
    requestAnimationFrame(animate);
    orbitControls.update();
    render();
  }

  function render() {
    time += 0.03;
    sunDirection.set(Math.cos(time), 0.0, Math.sin(time))
    renderer.render(scene, camera);
  }
})();
<script id="fragmentShader" type="x-shader/x-fragment">
    uniform sampler2D dayTexture;
    uniform sampler2D nightTexture;

    uniform mat3 normalMatrix;
    uniform vec3 sunDirection;

    varying vec2 vUv;
    varying vec3 vNormal;

    void main( void ) {
        vec4 dayColor = texture2D(dayTexture, vUv);
        vec4 nightColor = texture2D(nightTexture, vUv);

        vec3 dir = normalize(normalMatrix * sunDirection);

        // compute cosine sun to normal so -1 is away from sun and +1 is toward sun.
        float cosineAngleSunToNormal = dot(normalize(vNormal), dir);

        // sharpen the edge between the transition
        cosineAngleSunToNormal = clamp(cosineAngleSunToNormal * 10.0, -1.0, 3.0);

        // convert to 0 to 1 for mixing
        float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;

        // Select day or night texture based on mix.
        vec4 color = mix(nightColor, dayColor, mixAmount);

        gl_FragColor = color;
    }
</script>

<script id="vertexShader" type="x-shader/x-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;
}
</script>

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

Другие вопросы по тегам