Question about screen space coordinates for sun/background rendering - javascript

I'm trying to render a background and sun with a custom shader in three.js. The idea is to compute a screen space position for the sun and use these coordinates in the fragment shader for rendering. The expected behavior is that the sun is always rendered at the horizon at (0,1000,-1000). When you run the live example and just look up, it seems this is actually the case.
However, when you move the camera around (so it looks along the (0,-1,1) vector), you will notice that the sun is suddenly mirrored and flipped along the XY plane. Why is this happening? Is this related to the approach how screen space coordinates are computed and evaluated in the shader.
The live example is actually a reduced test case of the this 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/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>

The issue is caused, because the sun is behind the point of view. Note at perspective projection the the viewing volume is a Frustum. Each point is projected along a ray through the camera position on the viewport. If the point is behind the point of view, it is mirrored, because it projected along this ray.
In general that doesn't matter, because all the geometry in front of the near plane is clipped.
The clipspace coordinate is a Homogeneous coordinate. You have to compute the clipspace cooridante, and evaluate if the z component is less than 0.
Note, you cannot use Vector3.project, because project computes the normalized device space coordinate. In NDC it cannot be distinguished if the position is in front of or behind the camera, because after the Perspective divide the singe of the z component is lost. Clipping is performed in clip space and the clipping rule is:
-w <= x, y, z <= w.
Define the sun position in by Homogeneous direction:
var sunPosition = new THREE.Vector4( 0, 1, - 1, 0 );
Compute the clip space coordinate and evaluate if the z component is negative:
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) {
// [...]
}

Related

Three.js uniform dashed line relative to camera

I'm working to display geometric figures in 3D, using three.js.
When you draw (by hand) hidden lines as dashed lines, the 'dashes' are regular for all them. This means that a line parallel to the camera plane or a line (nearly) perpendicular to the camera plane should do have the same length and gap.
But this seems to not work with LineDashedMaterial.
For the attached example, I'm using this (very) basic code:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var geometry = new THREE.BoxGeometry( 2, 2, 2 );
var LINES_DASHED = new THREE.LineSegments(
new THREE.EdgesGeometry(geometry),
new THREE.LineDashedMaterial({
linewidth: 2,
color: 0x000000,
dashSize: 0.2,
gapSize: 0.1,
depthTest: false,
polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1
})
);
LINES_DASHED.computeLineDistances();
scene.add( LINES_DASHED );
scene.background = new THREE.Color( 0xffffff);
camera.position.z = 5;
var animate = function () {
requestAnimationFrame( animate );
LINES_DASHED.rotation.x += 0.01;
LINES_DASHED.rotation.y += 0.01;
renderer.render( scene, camera );
};
animate();
body { margin: 0; }
canvas { width: 100%; height: 100% }
<script src="https://threejs.org/build/three.min.js"></script>
Working example:
https://bs4.scolcours.ch/_dev/3js_ex.php
I thougth that using:
line.computeLineDistance();
will solve the problem. But it seems to calculate the line length in 3D space (which seems to be logical).
Is there something I missed ?
Thanks for your help!
That's abroad task. It seems that THREE.LineDashedMaterial does not support this.
But it is possible to write a shader and to use a THREE.ShaderMaterial.
The trick is to know the start of a line in the fragment shader. In general this easy by using a flat interpolation qualifier.
Sadly WebGL 1.0 / GLSL ES 1.00 doesn't support this. So we have to use WebGL 2.0 / GLSL ES 3.00.
In OpenGL ES there exists the extension GL_NV_shader_noperspective_interpolation. Unfortunately there doesn't seem to be a corresponding WebGL extension. (See WebGL Extension Registry)
So lets cerate a THREE.WebGLRenderer with a WebGL2 context. See How to use WebGL2:
var canvas = document.createElement( 'canvas' );
var context = canvas.getContext( 'webgl2' );
var renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );
The vertex shader has to pass the normalized device coordinate to the fragment shader. Once with default interpolation and once with no (flat) interpolation. This causes that in the fragment shade the first input parameter contains the NDC coordinate of the actual position on the line and th later the NDC coordinate of the start of the line.
flat out vec3 startPos;
out vec3 vertPos;
void main() {
vec4 pos = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_Position = pos;
vertPos = pos.xyz / pos.w;
startPos = vertPos;
}
Additionally the varying inputs, the fragment shader has uniform variables. u_resolution contains the width and the height of the viewport. u_dashSize contains the length of line and u_gapSize the length of a gap in pixel.
So the length of the line from the start to the actual fragment can be calculated:
vec2 dir = (vertPos.xy-startPos.xy) * u_resolution/2.0;
float dist = length(dir);
And fragment on the gab can be discarded, by the discard command.
if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
discard;
Fragment shader:
precision highp float;
flat in vec3 startPos;
in vec3 vertPos;
uniform vec3 u_color;
uniform vec2 u_resolution;
uniform float u_dashSize;
uniform float u_gapSize;
void main(){
vec2 dir = (vertPos.xy-startPos.xy) * u_resolution/2.0;
float dist = length(dir);
if ( fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize) )
discard;
gl_FragColor = vec4(u_color.rgb, 1.0);
}
Setup the THREE.ShaderMaterial and the uniforms:
var uniforms = {
u_resolution: {type: 'v2', value: {x: vpSize[0], y: vpSize[1]}},
u_dashSize : {type:'f', value: 10.0},
u_gapSize : {type:'f', value: 5.0},
u_color : {type: 'v3', value: {x:0.0, y:0.0, z:0.0} }
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var LINES_DASHED = new THREE.LineSegments(
new THREE.EdgesGeometry(geometry),
material);
Note, if the resolution of the canvas changes, the values of the u_resolution have to be set:
e.g.
LINES_DASHED.material.uniforms.u_resolution.value.x = window.innerWidth;
LINES_DASHED.material.uniforms.u_resolution.value.y = window.innerHeight;
I applied the suggestions to your original code. See the preview and the example:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 60, window.innerWidth/window.innerHeight, 0.1, 1000 );
var canvas = document.createElement( 'canvas' );
var context = canvas.getContext( 'webgl2' );
var renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );
var vpSize = [window.innerWidth, window.innerHeight];
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var geometry = new THREE.BoxGeometry( 2, 2, 2 );
var uniforms = {
u_resolution: {type: 'v2', value: {x: vpSize[0], y: vpSize[1]}},
u_dashSize : {type:'f', value: 10.0},
u_gapSize : {type:'f', value: 5.0},
u_color : {type: 'v3', value: {x:0.0, y:0.0, z:0.0} }
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var LINES_DASHED = new THREE.LineSegments(
new THREE.EdgesGeometry(geometry),
material);
LINES_DASHED.computeLineDistances();
scene.add( LINES_DASHED );
scene.background = new THREE.Color( 0xffffff);
camera.position.z = 5;
var animate = function () {
requestAnimationFrame( animate );
LINES_DASHED.rotation.x += 0.01;
LINES_DASHED.rotation.y += 0.01;
renderer.render( scene, camera );
};
window.onresize = function() {
vpSize = [window.innerWidth, window.innerHeight];
LINES_DASHED.material.uniforms.u_resolution.value.x = window.innerWidth;
LINES_DASHED.material.uniforms.u_resolution.value.y = window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
animate();
<script type='x-shader/x-vertex' id='vertex-shader'>
flat out vec3 startPos;
out vec3 vertPos;
void main() {
vec4 pos = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_Position = pos;
vertPos = pos.xyz / pos.w;
startPos = vertPos;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
flat in vec3 startPos;
in vec3 vertPos;
uniform vec3 u_color;
uniform vec2 u_resolution;
uniform float u_dashSize;
uniform float u_gapSize;
void main(){
vec2 dir = (vertPos.xy-startPos.xy) * u_resolution.xy/2.0;
float dist = length(dir);
if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
discard;
gl_FragColor = vec4(u_color.rgb, 1.0);
}
</script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/r128/build/three.js"></script>

BufferGeometry create particles z-index

I created two particles with a threejs BufferGeometry, I want to click on each particle shows the corresponding image.
But when I clicked on the particle the image was shown and another particle covered it.
I want to know how to get the particles out of the control of the hierarchy and keep the clicked particles always on top.
code:`
var scene, camera, renderer,controls;
var points;
var shaderMaterial;
var particleCount = 2;
function init () {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 15;
camera.position.y = 16;
camera.position.z = 35;
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
var light = new THREE.AmbientLight( 0xcccccc );
scene.add(light);
document.body.appendChild(renderer.domElement);
createParticles();
createGrid();
render();
document.querySelector('canvas').addEventListener( 'click', interactive, false );
}
function createParticles () {
var geometry = new THREE.BufferGeometry();
var positions = new Float32Array( particleCount * 3 );
var sizes = new Float32Array( particleCount );
var pop = new Float32Array( particleCount);
for (var i = 0, i3 = 0; i < particleCount; i ++, i3 += 3) {
positions[i3 + 0] = i* 10;
positions[i3 + 1] = 0.1;
positions[i3 + 2] = 1;
sizes[i] = 15;
pop[i] = 0.0;
}
geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.addAttribute( 'size', new THREE.BufferAttribute( sizes, 1 ) );
geometry.addAttribute( 'pop', new THREE.BufferAttribute( pop, 1 ) );
shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
'u_time': {type: 'f', value: 1.0},
'u_texture_0': { value: new THREE.TextureLoader().load('https://avatars2.githubusercontent.com/u/5829050?s=256&v=4') }},
vertexShader: document.getElementById( 'vs' ).textContent,
fragmentShader: document.getElementById( 'fs' ).textContent,
// blending: THREE.AdditiveBlending,
depthTest: false,
transparent: true
});
shaderMaterial.uniforms['u_texture_0'].value.flipY = false;
points = new THREE.Points(geometry, shaderMaterial);
scene.add(points);
}
var raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 5;
var touch = new THREE.Vector2();
var intersects, INTERSECTED;
var beforeIndex;
function interactive (event) {
touch.x = ( event.clientX / window.innerWidth ) * 2 - 1;
touch.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
points.geometry.computeBoundingSphere();
camera.updateMatrixWorld();
var vector = new THREE.Vector3(touch.x, touch.y, 0.5 ).unproject(camera);
raycaster.set(camera.position, vector.sub(camera.position ).normalize());
raycaster.setFromCamera( touch, camera );
intersects = raycaster.intersectObject(points);
if ( intersects.length > 0 ) {
if ( INTERSECTED != intersects[ 0 ].index ) {
INTERSECTED = intersects[ 0 ].index;
if (beforeIndex != INTERSECTED) {
points.geometry.attributes.pop.array[ beforeIndex ] = 0.0;
}
points.geometry.attributes.pop.array[ INTERSECTED ] = 1.0;
beforeIndex = INTERSECTED;
}
}
points.geometry.attributes.size.needsUpdate = true;
points.geometry.attributes.pop.needsUpdate = true;
}
function createGrid () {
var helper = new THREE.GridHelper( 100, 20, 0x303030, 0x303030 );
scene.add( helper );
}
function render () {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
init();
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
background: #000;
}
canvas {
display: block;
}
<script src="https://threejs.org/build/three.js"></script>
<script id="fs" type="x-shader/x-fragment">
precision highp float;
uniform sampler2D u_texture_0;
uniform float u_time;
varying float u_pop;
void main () {
vec2 uv = gl_PointCoord.xy;
vec4 rval = texture2D(u_texture_0,uv);
vec2 posToCenter = (uv - vec2(.5, .5)) * 2.0;
float distanceToCenter = length(posToCenter);
float fadeOpacity = 1. - smoothstep(0.8, 1., distanceToCenter);
float opacity = (1. - step(0.8, distanceToCenter)) + fadeOpacity;
vec3 bgColor = mix(vec3(255., 255., 255.), vec3(252., 222., 184.), distanceToCenter) / 255.;
vec4 color = vec4(mix(bgColor, rval.rgb, u_pop), 1.);
color.a = opacity;
gl_FragColor = color;
}
</script>
<script type="x-shader/x-vertex" id="vs">
attribute float size;
attribute float pop;
varying float u_pop;
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 300.0 / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
u_pop = pop;
}
</script>
`
You have misunderstanding about how 3D works, therefore, you use wrong concepts and terminology. There is no "Z-Index" in 3D. There is Z-buffer or Depth-buffer (two names, same thing), which reflects object distance from the render's point of view (camera, virtual observer). Naturarly, the purpose of the depth-buffer with depth-testing is to prevent farest objects to be rendered in front of closest ones (this also allow to optimize, by preventing unseen pixels to be computed).
Also, the background to foreground display is not controled by any hierarchy (unless the engine deliberately implements such feature), objects are simply rendered in order they are supplied. If the Depth-testing is disabled, the latest rendered object will be displayed in front of all previously rendered ones. In 3D scene, the hierarchy is relative to transformations, not display order (unless objects are rendered in scene's hierarchy order without depth-testing).
To achieve what you want in robust way, you'll have to disable the depth-desting and manually control the order which of sprites are rendered, to ensure the one which must be in "front", is the last rendered one. This is a pretty low-level manipulation, and unless Three.JS allow you to control that (which I doubt), you'll probably have to changes your tactic, or implements your own WebGL engine.

Three.js vertices position not updating with custom shader

I've created a custom shader in three.js to allow me to set individual vertices color, size and position.
Color and size work fine but when I update the vertex x,y or z it's not updated on screen.
JSFiddle
What am I missing?
Vertex shaders:
<script type="x-shader/x-vertex" id="vertexshader">
attribute float size;
attribute vec3 color;
varying vec3 vColor;
varying vec2 vUv;
void main()
{
vColor = color;
gl_PointSize = size;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
Fragment shader
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D texture;
varying vec2 vUv;
varying vec3 vColor;
void main()
{
vec4 color = vec4(vColor, 1);
gl_FragColor = color;
}
</script>
JS:
var container = document.getElementById('container');
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 100;
var scene = new THREE.Scene();
var vShader = document.getElementById('vertexshader').textContent;
var fShader = document.getElementById('fragmentshader').textContent;
var uniforms = {};
var attributes = {
color: { type: 'c', value: [] },
size: { type: 'f', value: [] }
};
var geometry = new THREE.Geometry();
for ( var i = 0; i < 100; i ++ )
{
var angle = Math.random() * Math.PI * 2;
var radius = 40 + (Math.random() * 5);
var vertex = new THREE.Vector3();
vertex.x = Math.cos(angle) * radius;
vertex.y = Math.sin(angle) * radius;
vertex.z = 0;
attributes.size.value[i] = Math.random() * 10;
attributes.color.value[i] = new THREE.Color( 0xff0000 );
geometry.vertices.push( vertex );
}
var material = new THREE.ShaderMaterial(
{
uniforms: uniforms,
attributes: attributes,
vertexShader: vShader,
fragmentShader: fShader,
transparent: true
});
var particleSystem = new THREE.PointCloud(geometry, material);
scene.add( particleSystem );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x292B30, 1 );
container.appendChild( renderer.domElement );
animate();
function animate()
{
requestAnimationFrame( animate );
render();
}
function render()
{
for (var i = geometry.vertices.length - 1; i >= 0; i--)
{
geometry.vertices[i].z = geometry.vertices[i].z - 0.5 ;
}
camera.lookAt( scene.position );
//particleSystem.geometry.__dirtyVertices = true;
renderer.render( scene, camera );
}
When modifying the vertex position, set the verticesNeedUpdate flag:
geometry.verticesNeedUpdate = true;
Updated fiddle: https://jsfiddle.net/1cmg0z2f/2/
Three.js r71, due to changes in ShaderMaterial the fiddle doesnt work with the new version.
I added a comment to the previous accepted answer but just in case here's an updated answer
geometry.attributes[attributeName].needsUpdate = true
eg
geometry.attributes.position.needsUpdate = true
keep in mind that this works for custom / shader attributes that you've added using setAttribute, which is what I was searching for before I found this QA.

WebGl scanner effect

Hey I have just seen this short video: https://vine.co/v/hxHz5H2Q07q and I am wondering how to achieve this scanning effect.
For start there are 2 groups needed: one holding cubes with material and a corresponding one with the same cubes but just wireframed, so it is possible to get these layers.
But how to make whe wired one appear in such "scanner" way? Is it via shaders or maybe there is some masking method in threejs for moving the mask across the rendered and displaying the given object linked with the mask?
Copied from the description of the original demo
This experiment was inspired by real-world projection mapping onto physical shapes. The field is created by random generation and merged into a single geometry. It is duplicated and the copy is rendered with a ShaderMaterial that looks at a uniform point and makes most pixels transparent other than those near the light point. Moving the point through the field creates the appearance of scanning.
So, make some geometry, draw it twice, once flat shaded, ones wireframe. For the wireframe version make a custom shader that takes a single point uniform. If the vertex (or pixel) is close to that point draw a color, if not draw transparent (or discard).
var camera = new THREE.PerspectiveCamera( 20, 1, 1, 10000 );
camera.position.z = 1800;
var scene = new THREE.Scene();
var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
var flatMaterial = new THREE.MeshLambertMaterial( { color: 0x606060, shading: THREE.FlatShading } );
var geometry = new THREE.IcosahedronGeometry( 200, 1 );
//var wireMaterial = new THREE.MeshBasicMaterial( { color: 0x00FF00, wireframe:true } );
var uniforms = {
color: { type: "c", value: new THREE.Color(0x00FF00) },
lightPos: { type: "v3", value: new THREE.Vector3(0, 0, 0) },
range: { type: "f", value: 150, },
};
var wireMaterial = new THREE.ShaderMaterial({
wireframe: true,
uniforms: uniforms,
attributes: {
},
vertexShader: document.getElementById('vertexshader').text,
fragmentShader: document.getElementById('fragmentshader').text,
depthTest: true,
transparent: true,
});
var mesh = new THREE.Mesh( geometry, flatMaterial );
scene.add( mesh );
var mesh = new THREE.Mesh( geometry, wireMaterial );
scene.add( mesh );
renderer = new THREE.WebGLRenderer( { antialias: true } );
document.body.appendChild( renderer.domElement );
function resize() {
var canvas = renderer.context.canvas
var width = canvas.clientWidth;
var height = canvas.clientHeight;
if (width != canvas.width || height != canvas.height) {
renderer.setSize( width, height, false );
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
}
function render() {
resize();
var time = Date.now() * 0.001;
uniforms.lightPos.value.x = Math.sin(time) * 200;
uniforms.lightPos.value.y = Math.cos(time) * 200;
camera.lookAt( scene.position );
renderer.render( scene, camera );
requestAnimationFrame( render );
}
render();
html, body {
margin: 0px;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<body>
</body>
<script type="not-js" id="vertexshader">
varying vec4 v_position;
void main() {
vec4 pos = vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewMatrix * pos;
v_position = modelMatrix * pos;
}
</script>
<script type="not-js" id="fragmentshader">
uniform vec3 color;
uniform vec3 lightPos;
uniform float range;
varying vec4 v_position;
void main() {
float distanceToLight = distance(lightPos, v_position.xyz);
gl_FragColor = mix(vec4(color, 1), vec4(0,0,0,0), step(range, distanceToLight));
}
</script>
You have a bunch of parallel rays maybe 100 or however many you want, that shoot towards your scene and intersect it and at the same time move up the y direction.

Mesh suddenly disappears in three.js. Clipping?

Scenario:
In my scene I implemented a vertex shader that positions a plane mesh on the xz-axis at the position of the camera.
So if the camera moves, the plane mesh moves with it. This leads to the visual effect that, while moving the camera, the plane mesh seems to stay fixed in place. This seems to work correctly.
Problem:
If I move the camera (and therefore the plane mesh) to a certain extend, the mesh suddenly disappears.
I realized that there seems to be a relationship between the disappearance and the size of the plane, i.e. the larger the plane, the more I can move the camera before the plane mesh disappears.
Also, in my test scene the plane mesh only disappears when moving on the negative x-axis, positive x-axis or negative z-axis. It does NOT disappear when moving on the positive z-axis.
I assume it has something to do with some kind of clipping, but may be wrong. Recomputing the bounding box of the plane mesh had no effect.
Any ideas?
Cheers
Fiddle:
I created a fiddle that shows the problem: http://jsfiddle.net/p8wZ6/10/
In the fiddle I added an additional box mesh to better visualize that the camera actually moves.
- To change the axis the camera moves on (negative z-axis by default) (un-)comment the appropriate code line in the tick method.
- To change the size of the plane change the size value in the createPlane method.
Sourcecode Shader:
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
vec4 pos = vec4( position, 1.0 );
vec4 wPos = modelMatrix * pos;
wPos.x += cameraPosition.x;
wPos.z += cameraPosition.z;
// standard
// vec4 pPos = projectionMatrix * modelViewMatrix * pos;
// keep fixed
vec4 pPos = projectionMatrix * viewMatrix * wPos;
gl_Position = pPos;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
void main() {
gl_FragColor.rgb = vec3(0.7, 0.7, 0.7);
gl_FragColor.a = 1.0;
}
</script>
Sourcecode JS:
var scene;
var camera;
var light;
var renderer;
var controls;
var onTick;
var planeMesh;
var boxMesh;
var heightmap;
var clock;
function createPlane(){
// disappearance seems related to size of geometry.
// the larger the longer it takes until disappearance.
var size = 20;
var geom = new THREE.PlaneGeometry(size, size, 20, 20);
return geom;
}
function createBox(){
var geom = new THREE.CubeGeometry(2, 2, 4);
return geom;
}
function createMesh(){
// plane
var geom = createPlane();
var shaderMaterial = new THREE.ShaderMaterial({
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
side: THREE.DoubleSide,
wireframe: true
});
planeMesh = new THREE.Mesh(geom, shaderMaterial);
var axis = new THREE.AxisHelper(4);
planeMesh.rotation.x = -90 * (Math.PI / 180);
planeMesh.add(axis);
scene.add(planeMesh);
// box
geom = createBox();
var material = new THREE.MeshBasicMaterial( {
color: 0xff00ff,
});
boxMesh = new THREE.Mesh(geom, material);
boxMesh.position.x = 5;
boxMesh.position.z = -15;
axis = new THREE.AxisHelper(4);
boxMesh.add(axis);
scene.add(boxMesh);
}
function startRendering(){
onTick();
};
function onTick(){
// move camera
// causes disappearance
// neg. z
camera.position.z -= .1;
// pos. x
// camera.position.x += .1;
// neg. x
// camera.position.x -= .1;
// causes no disappearance
// pos. z
// camera.position.z += .1;
requestAnimationFrame(onTick);
//controls.update(clock.getDelta());
renderer.render(scene, camera);
}
function init(){
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0xffffff, 1 );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
scene.add(new THREE.AxisHelper(4));
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1, 0);
light = new THREE.DirectionalLight(0xffffff, 1);
light.shadowCameraVisible = true;
light.position.set(0, 0, 100);
scene.add(light);
//clock = new THREE.Clock();
//controls = new THREE.FirstPersonControls(camera);
//controls.movementSpeed = 20;
//controls.lookSpeed = .1;
}
init();
createMesh();
startRendering();
You have a fundamental misunderstanding.
You are moving the camera in the CPU. You are moving the vertices of the plane in the GPU.
The camera's frustum calculation knows nothing about the vertex displacements in the vertex shader.
As a work-around, you can set
planeMesh.frustumCulled = false;
A better solution is to just add the plane as a child of the camera, and omit vertex displacements.
planeMesh.position.set( 0, -1, 0 );
camera.add( planeMesh );
scene.add( camera );
You must add the camera to the scene graph it you use the second approach.
three.js r.65
When you define your camera in r73 the last two parameters allow you to specify your camera's near and far z clipping distance.
Taken from this link: http://threejs.org/docs/#Manual/Introduction/Creating_a_scene
var camera =
new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
The third parameter of Three.PerspectiveCamera defines the camera's near clipping distance and the fourth parameter defines the camera's far clipping distance.

Categories