WebGL-跟随旋转的物体和/或摄像机运动的静态定向光的问题

时间:2019-06-16 03:25:13

标签: three.js glsl shader

我正在努力通过使用自定义着色器在Earth模型中使用定向光设置昼夜周期。只要我不触摸相机,夜间和白天的地图以及灯光都可以,也就是说,地球在光源保持静止的状态下旋转,并且正确地更新了夜晚和白天。但是,当我使用鼠标旋转照相机时,光线似乎跟随照相机,因此您始终会看到地球的照明部分。

这是我设置光源的方式:

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

当我更换相机时,这阻止了光的移动,但是,夜晚的阴影总是与光开始与地球本身一起旋转的位置完全相同。

我觉得我即将解决此问题,因此任何提示或帮助将不胜感激。谢谢您的宝贵时间。

Blockquote

1 个答案:

答案 0 :(得分:1)

您所说的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_vertToLightv_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>