关于太阳/背景渲染的屏幕空间坐标的问题

4
我正在尝试在three.js中使用自定义着色器渲染背景和太阳。 我的想法是计算出太阳的屏幕空间位置,并在片段着色器中使用这些坐标进行渲染。期望的行为是太阳始终呈现在地平线上,位于(0,1000,-1000)处。当你运行实时示例并抬头看时,似乎确实是这种情况。
然而,当你移动相机(使其沿着(0,-1,1)向量看),你会注意到太阳突然被镜像和翻转,并且沿XY平面翻转。为什么会发生这种情况?这是否与在着色器中计算和评估屏幕空间坐标的方法有关?
实时示例实际上是此GitHub问题的缩小测试用例。

var container;

var camera, cameraFX, scene, sceneFX, renderer;

var uniforms;

var sunPosition = new THREE.Vector3( 0, 1000, - 1000 );
var screenSpacePosition = new THREE.Vector3();

init();
animate();

function init() {

 container = document.getElementById( 'container' );

 camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 2000 );
 camera.position.set( 0, 0, 10 );
 
 cameraFX = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );

 scene = new THREE.Scene();
 
 scene.add( new THREE.AxesHelper( 5 ) );
 
 sceneFX = new THREE.Scene();

 var geometry = new THREE.PlaneBufferGeometry( 2, 2 );

 uniforms = {
  "aspect": { value: window.innerWidth / window.innerHeight },
  "sunPositionScreenSpace": { value: new THREE.Vector2() }
 };

 var material = new THREE.ShaderMaterial( {

  uniforms: uniforms,
  vertexShader: document.getElementById( 'vertexShader' ).textContent,
  fragmentShader: document.getElementById( 'fragmentShader' ).textContent

 } );

 var quad = new THREE.Mesh( geometry, material );
 sceneFX.add( quad );

 renderer = new THREE.WebGLRenderer();
 renderer.setSize( window.innerWidth, window.innerHeight );
 renderer.autoClear = false;
 container.appendChild( renderer.domElement );

 var controls = new THREE.OrbitControls( camera, renderer.domElement );

}


//

function animate( timestamp ) {

 requestAnimationFrame( animate );
 
 renderer.clear();
 
 // background/sun pass

 screenSpacePosition.copy( sunPosition ).project( camera );
 
 screenSpacePosition.x = ( screenSpacePosition.x + 1 ) / 2;
 screenSpacePosition.y = ( screenSpacePosition.y + 1 ) / 2;

 uniforms[ "sunPositionScreenSpace" ].value.copy( screenSpacePosition );

 renderer.render( sceneFX, cameraFX );
 
 // beauty pass
 
 renderer.clearDepth();
 renderer.render( scene, camera );

}
body {
  margin: 0;
}
canvas {
  display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three@0.116.1/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.116.1/examples/js/controls/OrbitControls.js"></script>

<div id="container">

</div>
<script id="vertexShader" type="x-shader/x-vertex">

   varying vec2 vUv;

   void main() {

    vUv = uv;

    gl_Position = vec4( position, 1.0 );

   }

</script>

<script id="fragmentShader" type="x-shader/x-fragment">

   varying vec2 vUv;

   uniform vec2 sunPositionScreenSpace;
   uniform float aspect;

   const vec3 sunColor = vec3( 1.0, 0.0, 0.0 );
   const vec3 bgColor = vec3( 1.0, 1.0, 1.0 );

   void main() {

    vec2 diff = vUv - sunPositionScreenSpace;
    diff.x *= aspect;

    // background/sun drawing

    float prop = clamp( length( diff ) / 0.5, 0.0, 1.0 );
    prop = 0.35 * pow( 1.0 - prop, 3.0 );

    gl_FragColor.rgb = mix( sunColor, bgColor, 1.0 - prop );
    gl_FragColor.a = 1.0;

   }

</script>


https://github.com/mrdoob/three.js/pull/15457。 :-) - WestLangley
1个回答

4
问题是由于太阳在视角之后,造成了这种情况。请注意,在透视投影中,观察体是一个棱锥体。每个点沿着通过摄像机位置和视口的光线进行投影。如果该点在视角之后,则会被镜像,因为它是沿着这条光线投影的。
一般来说,这并不重要,因为所有在近平面前的几何图形都将被裁剪掉。
剪辑空间坐标是齐次坐标。你需要计算剪辑空间坐标,并评估z分量是否小于0。
请注意,你不能使用Vector3.project,因为project会计算标准化设备空间坐标。在NDC中,无法区分位置是在相机前面还是后面,因为在透视除法之后,z分量的正负号被丢失了。裁剪是在剪辑空间中进行的,剪辑规则是:
-w <= x, y, z <= w. 

定义太阳在齐次坐标系中的方向:

var sunPosition = new THREE.Vector4( 0, 1, - 1, 0 );

计算剪裁空间坐标并评估z分量是否为负:

let clipPosition = sunPosition
   .clone()
   .applyMatrix4(camera.matrixWorldInverse)
   .applyMatrix4(camera.projectionMatrix);

screenSpacePosition.x = ( clipPosition.x / clipPosition.w + 1 ) / 2;
screenSpacePosition.y = ( clipPosition.y / clipPosition.w + 1 ) / 2;

if (clipPosition.z < 0.0) {
    // [...]
}

感谢您的反馈!然而,在记录screenSpacePosition.z时,似乎值永远不会变成负数(请参见https://jsfiddle.net/zpgv362e/1/)。 - Mugen87
这完全解释了问题!这是修复后的版本:https://jsfiddle.net/0kd8Lt9u/ - Mugen87
1
@Mugen87 在我完成我的建议之前,您已经接受了我的答案。我已经扩展了答案。我刚刚看到,您已经找到了一个类似的解决方案。无论如何,谢谢。 - Rabbid76
顺便问一下:.applyMatrix4(camera.matrixWorld) 不应该是 .applyMatrix4(camera.matrixWorldInverse) 吗? - Mugen87
@Mugen87 当然可以。如果 camera.matrixWorld 是从相机空间到世界空间的变换矩阵,那么 matrixWorldInverse 就是视图矩阵。 - Rabbid76

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接