Three.js jagged edges even with FXAA shader and anti aliasing - three.js

I'm trying to render a cube in the center of my scene with white edges that rotates. I got this to work but the edges of the cube are very jagged when it moves.
I tried enabling anti-aliasing and adding a FXAA shader but the lines are still jagged.
I'm using this FXAA shader and react-three-fiber instead of vanilla Three.js.
This is my effects composer:
const Effects = ({ factor }) => {
const composer = useRef();
const {
scene, gl, size, camera,
} = useThree();
useEffect(() => void composer.current.setSize(size.width, size.height), [size]);
useRender(({ gl }) => void ((gl.autoClear = true), composer.current.render()), true);
return (
<effectComposer ref={composer} args={[gl]}>
<renderPass attachArray="passes" scene={scene} camera={camera} />
<shaderPass
attachArray="passes"
args={[fxaa()]}
material-uniforms-resolution-value={[1 / size.width, 1 / size.height]}
renderToScreen
/>
</effectComposer>
);
};
Image here of the top edge of my cube with anti aliasing and FXAA applied

I ended up not using LineSegments to draw my edges and instead using the LineMaterial method from the fat lines example, which has the added benefit of variable line width.
In case anyone comes across this and is wondering how to use edges with LineMaterial here is the code:
const edges = new THREE.EdgesGeometry(geometry.current);
const geo = new LineSegmentsGeometry().fromEdgesGeometry(edges);
const matLine = new LineMaterial({
color: 'white',
linewidth: 2,
dashed: false,
});
matLine.resolution.set(window.innerWidth, window.innerHeight);
const wireframe = new Wireframe(geo, matLine);
wireframe.computeLineDistances();
wireframe.scale.set(1, 1, 1);
scene.add(wireframe);

Related

Add edge line to 3D object with Three.js

I am trying to add the edge lines to the 3D object with Three.js. Here is the object loaded:
Here is the code I used to draw the edges:
// geometry is the object loaded from remote server
const edgeGeometry = new EdgesGeometry( geometry );
const edgeMaterial = new LineBasicMaterial( { color: 0x333333, linewidth: 0.5 } );
const edgeWireframe = new LineSegments( edgeGeometry, edgeMaterial );
scene.add(edgeWireframe );
The result is like this:
This the goal I am trying to achieve:
Only the edges have the lines. The question is: How can I draw the edge lines without these lines on the curve surfaces? Thank you for your time.

Mangled rendering when transforming scene coordinates instead of camera coordinates

I've been learning how to integrate ThreeJS with Mapbox, using this example. It struck me as weird that the approach is to leave the loaded model in its own coordinate system, and transform the camera location on render. So I attempted to rewrite the code, so that the GLTF model is transformed when loaded, then the ThreeJS camera is just synchronised with the Mapbox camera, with no further modifications.
The code now looks like this:
function newScene() {
const scene = new THREE.Scene();
// create two three.js lights to illuminate the model
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
scene.add(directionalLight);
const directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
scene.add(directionalLight2);
return scene;
}
function newRenderer(map, gl) {
// use the Mapbox GL JS map canvas for three.js
const renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
renderer.autoClear = false;
return renderer;
}
// create a custom layer for a 3D model per the CustomLayerInterface
export function addModel(modelPath, origin, altitude = 0, orientation = [Math.PI / 2, 0, 0]) {
const coords = mapboxgl.MercatorCoordinate.fromLngLat(origin, altitude);
// transformation parameters to position, rotate and scale the 3D model onto the map
const modelTransform = {
translateX: coords.x,
translateY: coords.y,
translateZ: coords.z,
rotateX: orientation[0],
rotateY: orientation[1],
rotateZ: orientation[2],
/* Since our 3D model is in real world meters, a scale transform needs to be
* applied since the CustomLayerInterface expects units in MercatorCoordinates.
*/
scale: coords.meterInMercatorCoordinateUnits()
};
const scaleVector = new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale)
return {
id: "3d-model",
type: "custom",
renderingMode: "3d",
onAdd: function(map, gl) {
this.map = map;
this.camera = new THREE.Camera();
this.scene = newScene();
this.renderer = newRenderer(map, gl);
// use the three.js GLTF loader to add the 3D model to the three.js scene
new THREE.GLTFLoader()
.load(modelPath, gltf => {
gltf.scene.position.fromArray([coords.x, coords.y, coords.z]);
gltf.scene.setRotationFromEuler(new THREE.Euler().fromArray(orientation));
gltf.scene.scale.copy(scaleVector);
this.scene.add(gltf.scene);
const bbox = new THREE.Box3().setFromObject(gltf.scene);
console.log(bbox);
this.scene.add(new THREE.Box3Helper(bbox, 'blue'));
});
},
render: function(gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
// this.map.triggerRepaint();
}
}
}
It basically works, in that a model is loaded and drawn in the right location in the Mapbox world. However, instead of looking like this:
It now looks like this, a mangled mess that jitters around chaotically as the camera moves:
I'm not yet familiar enough with ThreeJS to have any idea what I did wrong.
Here's a side-by-side comparison of the old, functional code on the right, vs the new broken code on the left.
Further investigation
I suspect possibly the cause is to do with shrinking all the coordinates down to within the [0..1] range of the projected coordinate system, and losing mathematical precision, perhaps. When I scale the model up by 100 times, it renders like this - messy and glitchy, but at least recognisable as something.

Colors in THREE.WebGLRenderTarget with alpha channel are darker than expected

I'm trying to render some graphics with transparency into a WebGLRenderTarget. The rendered image is then used as texture for a second material.
I have an issue with alpha blending. The color that I obtain when alpha=0.5 is darker than expected.
The image below shows the issue:
Circle on top is what I expect. This is obtained with an HTML DIV with rounder corners and opacity=0.5
Circle on bottom is what I obtain with with a shader that renders the circle inside a texture.
I think that I'm missing something!
Part of the code is reported below. You can find the complete code in the following jsbin: https://jsbin.com/zukoyaziqe/1/edit?html,js,output
Thank you for your help!!
Shader:
const texFrag = `
varying vec2 vUv;
void main() {
vec2 center = vec2(0.5, 0.2);
float d = length(vUv - center);
if (d < 0.1) {
gl_FragColor = vec4(1.0,0.0,1.0,0.5);
}
else {
discard;
}
}
`;
Texture:
const makeTexture = (renderer, width, height) => {
const target = new THREE.WebGLRenderTarget(width, height, {minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, type: THREE.FloatType});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(90, 1, 0.1, 100000);
const geometry = new THREE.PlaneGeometry(2, 2);
const material = new THREE.ShaderMaterial({
transparent : true,
vertexShader : simpleVert,
fragmentShader : texFrag,
});
const mesh = new THREE.Mesh(geometry, material);
camera.position.set(0, 0, 1);
scene.add(camera);
scene.add(mesh);
renderer.render(scene, camera, target, true);
return target.texture;
}
Main view:
const renderer = new THREE.WebGLRenderer({canvas});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(90, 1, 0.1, 100000);
const geometry = new THREE.PlaneGeometry( 2, 2 );
const material = new THREE.MeshBasicMaterial({
transparent : true,
map : makeTexture(renderer, canvas.width, canvas.height)
});
const mesh = new THREE.Mesh(geometry, material);
First of all, in the example you linked, your main function is called twice, so there are two CSS circles stacked on top of each other, resulting in a less transparent circle.
Then, you're drawing a circle with color (1,0,1,0.5) on a blank render target, which, using the default blend mode (SRC_ALPHA, ONE_MINUS_SRC_ALPHA), results in (0.5,0,0.5,0.5) color, which is then used as a texture. If you want the original color in your texture, you should disable alpha blending or use a different blend mode. Simply setting transparent to false inside makeTexture does the trick.

Weld edge vertices of BoxBufferGeometry

I am trying to create terrain in the shape of a cube which will allow for vertex displacement along the y‑axis of those on the top plane. All vertices adjacent to those of the top plane need to be connected.
In a performant manner, user input from either desktop or mobile would move them up or down.
From what I have read it is better to offload expensive operations to the GPU. I thought achieving the vertex displacement in a ShaderMaterial with a displacement attribute seemed like a perfect fit until I read the following:
As of THREE r72, directly assigning attributes in a ShaderMaterial is no longer supported. A BufferGeometry instance (instead of a Geometry instance) must be used instead.
So it seems that using attribute for my Geometry is out of the question?
My attempt at displacing the vertices along the top plane using BufferGeometry in the ShaderMaterial however results in the following:
The top plane's vertices of the BufferGeometry are not connected to the other planes, contrary to those of the Geometry, which are connected by using its mergeVertices method. To my knowledge that method is not available for BufferGeometry objects?
Basically what started my fear, uncertainty and doubt concerning Geometry was a post I read by mrdoob.
Summary
I already have this working for Geometry, but would like to make use of the GPU with ShaderMaterial's attributes, seemingly only supported by BufferGeometry, if it offers performance benefits for mobile and if Geometry might be deprecated in the future.
Here is a small snippet illustrating the issue:
let winX = window.innerWidth;
let winY = window.innerHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, winX / winY, 0.1, 100);
camera.position.set(2, 1, 2);
camera.lookAt(scene.position);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(winX, winY);
document.body.appendChild(renderer.domElement);
const terrainGeo = new THREE.BoxBufferGeometry(1, 1, 1);
const terrainMat = new THREE.ShaderMaterial({
vertexShader: `
attribute float displacement;
varying vec3 dPosition;
void main() {
dPosition = position;
dPosition.y += displacement;
gl_Position = projectionMatrix * modelViewMatrix * vec4(dPosition, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}
`
});
const terrainObj = new THREE.Mesh(terrainGeo, terrainMat);
let displacement = new Float32Array(terrainObj.geometry.attributes.position.count);
displacement.forEach((elem, index) => {
// Select vertex 8 - 11, the top of the cube
if (index >= 8 && index <= 11) {
displacement[index] = Math.random() * 0.1 + 0.25;
}
});
terrainObj.geometry.addAttribute('displacement',
new THREE.BufferAttribute(displacement, 1)
);
scene.add(camera);
scene.add(terrainObj);
const render = () => {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
const gui = new dat.GUI();
const updateBufferAttribute = () => {
terrainObj.geometry.attributes.displacement.needsUpdate = true;
};
gui.add(displacement, 8).min(0).max(2).step(0.05).onChange(updateBufferAttribute);
gui.add(displacement, 9).min(0).max(2).step(0.05).onChange(updateBufferAttribute);
gui.add(displacement, 10).min(0).max(2).step(0.05).onChange(updateBufferAttribute);
gui.add(displacement, 11).min(0).max(2).step(0.05).onChange(updateBufferAttribute);
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r76/three.min.js"></script>
<style type="text/css">body { margin: 0 } canvas { display: block }</style>

Threejs Raycasting a Sprite-Canvas Object is inaccurate [duplicate]

I am trying to use THREE.Raycaster to show an html label when the user hover an object. It works fine if I use THREE.Mesh but with THREE.Sprite it looks like that there is a space that increases with the scale of the object.
The creation process is the same for both scenario, I only change the type based on USE_SPRITE variable.
if ( USE_SPRITE ) {
// using SpriteMaterial / Sprite
m = new THREE.SpriteMaterial( { color: 0xff0000 } );
o = new THREE.Sprite( m );
} else {
// using MeshBasicMaterial / Material
m = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
o = new THREE.Mesh(new THREE.PlaneGeometry( 1, 1, 1 ), m );
}
https://plnkr.co/edit/J0HHFMpDB5INYLSCTWHG?p=preview
I am not sure if it is a bug with THREE.Sprite or if I am doing something wrong.
Thanks in advance.
three.js r73
I would consider this a bug in three.js r.75.
Raycasting with meshes in three.js is exact. However, with sprites, it is an approximation.
Sprites always face the camera, can have different x-scale and y-scale applied (be non-square), and can be rotated (sprite.material.rotation = Math.random()).
In THREE.Sprite.prototype.raycast(), make this change:
var guessSizeSq = this.scale.x * this.scale.y / 4;
That should work much better for square sprites. The corners of the sprite will be missed, as the sprite is treated as a disk.
three.js r.75

Resources