javascript - 关于太阳/背景渲染的屏幕空间坐标问题
问题描述
我正在尝试使用自定义着色器渲染背景和太阳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>
解决方案
造成这个问题,是因为太阳是落后的观点。请注意,在透视投影中,查看体积是Frustum。每个点通过视口上的相机位置沿射线投影。如果该点位于视点后面,则它是镜像的,因为它是沿着这条射线投影的。
一般来说,这并不重要,因为近平面前面的所有几何图形都被剪裁了。
剪辑空间坐标是一个齐次坐标。您必须计算剪辑空间坐标,并评估 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) {
// [...]
}
推荐阅读
- javascript - 我正在尝试在循环中使用 window.location.href
- java - 谷歌地图指示时降低音量,完成后恢复音量
- c# - 有没有办法在 Unity 中翻转 Sprite?
- postgresql - Postgres 不使用聚合的覆盖索引
- java - 如何使用 kubernetes pod 进行 Java 远程调试?
- typescript - 调度:函数需要两个参数,而我的函数没有
- flutter - 在同一页面中将数据从未来传递到另一个未来
- python - discord.py 输入 none 而不是 @username
- express - 现有的 Repl.it Express Mongoose 应用程序现在拒绝连接到 MongoDB Atlas
- json - pandas json_normalize 嵌套 json,其中字典仅存在于某些记录上