WebGL - проблема со статическим направленным светом после вращающегося объекта и / или движения камеры - PullRequest
1 голос
/ 16 июня 2019

Я пытаюсь установить дневной / ночной цикл с направленным светом в модели Земли с помощью пользовательских шейдеров.Ночные и дневные карты, а также свет в порядке, пока я не касаюсь камеры, т. Е. Земля вращается, когда источник света остается неподвижным, а ночи и дни обновляются корректно.Однако, когда я поворачиваю камеру с помощью мыши, кажется, что свет следует за камерой, поэтому вы всегда видите освещенную часть Земли.

Вот как я устанавливаю источник света:

    var light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(5,3,5);
    scene.add(light);

Вот как я передаю параметры шейдеру:

    uniforms_earth = {
    sunPosition: { type: "v3", value: light.position },
    dayTexture: { type: "t", value: THREE.ImageUtils.loadTexture( "daymap.jpg" ) },
    nightTexture: { type: "t", value: THREE.ImageUtils.loadTexture( "images/nightmap.jpg" ) }
    };

Это вершинный шейдер:

    varying vec2 v_Uv;
    varying vec3 v_Normal;

    uniform vec3 sunPosition;
    varying vec3 v_vertToLight;

    void main() {

        v_Uv = uv;
        v_Normal = normalMatrix * normal;

        vec4 worldPosition = modelViewMatrix * vec4(position, 1.0);

        v_vertToLight = normalize(sunPosition - worldPosition.xyz);

        gl_Position = projectionMatrix * worldPosition;

    }

А вот фрагментный шейдер:

    uniform sampler2D dayTexture;
    uniform sampler2D nightTexture;

    varying vec2 v_Uv;
    varying vec3 v_Normal;

    varying vec3 v_vertToLight;

    void main( void ) {

        vec3 dayColor = texture2D(dayTexture, v_Uv).rgb;
        vec3 nightColor = texture2D(nightTexture, v_Uv).rgb;

        vec3 fragToLight = normalize(v_vertToLight);

        float cosineAngleSunToNormal = dot(normalize(v_Normal), fragToLight);


        cosineAngleSunToNormal = clamp(cosineAngleSunToNormal * 10.0, -1.0, 1.0);

        float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;

        vec3 color = mix(nightColor, dayColor, mixAmount);

        gl_FragColor = vec4( color, 1.0 );

    }

Наконец, я использую ТРИ библиотеки для элементов управления камерой:

var controls = new THREE.TrackballControls(camera);

И я обновляю вращение Земли внутри функции рендеринга следующим образом:

    function render() {
        controls.update();
        earth.rotation.y += rotation_speed; 
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }

У меня уже естьпопытался изменить способ вычисления v_vertToLight так, чтобы и вершина, и положение источника света находились в одном мире с:

    v_vertToLight = normalize((modelViewMatrix*vec4(sunPosition, 1.0)).xyz - worldPosition.xyz);

. Это останавливает движение света при смене камеры, но затемтень ночного дня всегда остается в том же месте, в котором свет начинает вращаться вместе с самой Землей.

Я чувствую, что близок к решению этого вопроса, поэтому любая подсказка или помощь будут высоко оценены.Спасибо за ваше время.

Цитата

1 Ответ

1 голос
/ 16 июня 2019

То, что вы называете worldPosition - это не позиция в мировом пространстве, это позиция в пространстве зрения. Переименуйте переименованную переменную:

vec4 worldPosition = modelViewMatrix * vec4(position, 1.0);

vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);

sunPosition - позиция в мировом пространстве. Он должен быть преобразован для просмотра пространства, прежде чем его можно будет использовать для вычисления светового вектора пространства просмотра. Это должно быть сделано viewMatrix, а не modelViewMatrix. Обратите внимание, что modelViewMatrix из пространства модели в пространство просмотра и viewMatrix преобразуется из пространства миров в пространство просмотра (см. three.js - WebGLProgram ):

vec4 viewSunPos = viewMatrix * vec4(sunPosition, 1.0);
v_vertToLight = normalize(viewSunPos.xyz - viewPosition.xyz);

Обратите внимание, что v_vertToLight и v_Normal должны быть либо для просмотра пространственных векторов, либо для мировых космических векторов, они должны иметь одинаковую систему отсчета. В противном случае не имеет смысла вычислять скалярное произведение обоих векторов.

Вершинный шейдер:

varying vec2 v_Uv;
varying vec3 v_Normal;

uniform vec3 sunPosition;
varying vec3 v_vertToLight;

void main() {

    vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);
    vec4 viewSunPos   = viewMatrix * vec4(sunPosition, 1.0);

    v_Uv = uv;

    v_Normal      = normalMatrix * normal;
    v_vertToLight = normalize(viewSunPos.xyz - viewPosition.xyz);

    gl_Position = projectionMatrix * viewPosition;
}

Смотрите очень простой пример, в котором используется вершинный шейдер:

(function onLoad() {
  var loader, camera, scene, renderer, orbitControls, mesh;
  
  init();
  animate();

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

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

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

    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xffffff);
    scene.add(camera);
    window.onresize = resize;
    
    var ambientLight = new THREE.AmbientLight(0x404040);
    scene.add(ambientLight);

    var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
    directionalLight.position.set(1,2,1.5);
    scene.add( directionalLight );

    orbitControls = new THREE.OrbitControls(camera);
    
    addGridHelper();
    createModel();

  }

  function createModel() {

    var uniforms = {
          u_time : {type:'f', value:0.0},
          u_resolution: {type: 'v2', value: {x:2048.,y:1024.}},
          u_color : {type: 'v3', value: {x:1.0, y:0.0, z:0.0} },
          sunPosition : {type: 'v3', value: {x:5.0, y:5.0, z:5.0} }
    };
        
    var material = new THREE.ShaderMaterial({  
          uniforms: uniforms,
          vertexShader: document.getElementById('vertex-shader').textContent,
          fragmentShader: document.getElementById('fragment-shader').textContent,
    });

    var geometry = new THREE.BoxGeometry( 1, 1, 1 );

    mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(0, 0, -1);

    scene.add(mesh);
  }

  function addGridHelper() {
    
    var helper = new THREE.GridHelper(100, 100);
    helper.material.opacity = 0.25;
    helper.material.transparent = true;
    scene.add(helper);

    var axis = new THREE.AxesHelper(1000);
    scene.add(axis);
  }

  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() {
    mesh.rotation.y += 0.01;
    renderer.render(scene, camera);
  }
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

<script type='x-shader/x-vertex' id='vertex-shader'>
varying vec2 v_Uv;
varying vec3 v_Normal;

uniform vec3 sunPosition;
varying vec3 v_vertToLight;

void main() {

    vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);
    vec4 viewSunPos   = viewMatrix * vec4(sunPosition, 1.0);

    v_Uv = uv;
    
    v_Normal      = normalMatrix * normal;
    v_vertToLight = normalize(viewSunPos.xyz - viewPosition.xyz);

    gl_Position = projectionMatrix * viewPosition;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
varying vec2 v_Uv;
varying vec3 v_Normal;
varying vec3 v_vertToLight;
uniform vec3 u_color;
void main(){
    float kd = max(0.0, dot(v_vertToLight, v_Normal));
    gl_FragColor = vec4(u_color.rgb * kd + 0.1, 1.0);
}
</script>
...