我正在尝试使用three.js
中的自定义着色器渲染背景和太阳。这个想法是计算太阳的屏幕空间位置,并使用片段着色器中的这些坐标进行渲染。预期的行为是太阳总是在(0,1000,-1000)
处的地平线处渲染。当您运行实时示例并进行查找时,似乎确实是这种情况。
但是,当您四处移动相机时(它沿(0,-1,1)
矢量看),您会注意到太阳突然被镜像并沿XY平面翻转。为什么会这样呢?这是否与在着色器中如何计算和评估屏幕空间坐标的方法有关。
实际示例实际上是此GitHub issue的简化测试用例。
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/[email protected]/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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。每个点都沿着光线投射通过视口上的摄影机位置。如果该点在视点后方,则它是镜像的,因为它是沿着该光线投影的。通常,这无关紧要,因为会剪切近平面前面的所有几何图形。在您的情况下,screenSpacePosition
的z分量将被忽略,并且太阳不会被修剪。太阳的位置不是点,它定义了方向。因此,如果screenSpacePosition.z
小于0.0,则可以省略(修剪)太阳,因为它位于相机的背面。