Why is polygonOffset not working in this case? - three.js

I have some text rendering over a background quad. Let's call this a 'label'. Both are positioned at the same point which causes z-fighting.
I'd like to promote the text to avoid z-fighting using polygon offset.
This is how I add the polygon offset to the text material:
const material = new THREE.RawShaderMaterial(
CreateMSDFShader({
map: this.glyphs,
opacity: opt.opacity ?? 1,
alphaTest: (opt.opacity ?? 1) < 1 ? 0.001 : 1,
color: opt.colour ?? '#ffffff',
transparent: opt.transparent ?? true,
glslVersion: opt.renderMode === 'webgl' ? THREE.GLSL1 : THREE.GLSL3,
side: opt.side ?? THREE.DoubleSide,
depthFunc: opt.depthFunc ?? THREE.LessEqualDepth,
depthTest: true,
depthWrite: false,
polygonOffset: true,
polygonOffsetUnits: -1.0,
polygonOffsetFactor: -4.0,
})
);
const mesh = new THREE.Mesh(geom, material);
and this is the background material:
if (tableOptions.background) {
const geometry = new THREE.PlaneGeometry(1, 1, 1, 1);
const backgroundMaterial = new ActivatableMaterial(
{
color: new THREE.Color(tableOptions.backgroundColour),
toneMapped: false,
opacity: 1,
alphaTest: 0.001,
transparent: true,
},
{
activationColour: new THREE.Color(tableOptions.activationColour),
}
);
this.background = buildUIObject(
Actions.ExpandLabel as Action,
geometry,
backgroundMaterial
);
setName(this.background, 'Background');
this.tableGroup.add(this.background);
}
The polygon offset just isn't working (using Chrome). The text disappears behind the background quad as I orbit the camera around, and reappears at random. The labels are always facing the camera (using lookAt).
What could stop the polygon offset from working?
The labels are rendering to a renderpass with a render taget set up as follows:
const pars = {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
stencilBuffer: false,
depthBuffer: false,
};
this.renderTargetBuffer = new THREE.WebGLRenderTarget(
resolution.x,
resolution.y,
pars
);
this.renderTargetBuffer.texture.name = 'RenderTargetBuffer';
this.renderTargetBuffer.texture.generateMipmaps = false;
I'm assuming that because the polygonOffset is a state thing it doesn't matter that this is a RawShaderMaterial. Is that a safe assumption?
Edit: I have added the opposite polygonOffset to the background mesh separately and again it doesn't work.

Polygon offset wasn't the best solution in this case. I switched to maintaining a custom order for the labels based on distance to the camera. That way I could force the text render order to be greater than the background.

Related

How to create a Points (Mesh) of TextGeometry?

I am creating a TextGeometry but not able to convert in Points like a sphere or like a detailed 3d object.
In the below image I have created a sphere using
var dotGeometry = new THREE.SphereBufferGeometry(2, 100, 100);
var dotMaterial = new THREE.PointsMaterial({ size: 1, sizeAttenuation: false });
var dot = new THREE.Points(dotGeometry, dotMaterial);//new THREE.MeshBasicMaterial()
scene.add(dot);
and the for text I use
const fontloader = new THREE.FontLoader();
fontloader.load('./models/font.json', function (font) {
const textgeometry = new THREE.TextGeometry('Hello', {
font: font,
size: 1,
height: 0.5,
curveSegments: 12,
bevelEnabled: false,
bevelThickness: 10,
bevelSize: 8,
bevelOffset: 0,
bevelSegments: 5
});
var dotMaterial = new THREE.PointsMaterial({ size: 1, sizeAttenuation: false });
let textmesh = new THREE.Points(textgeometry, dotMaterial) //new THREE.MeshBasicMaterial({
// color: 0xffffff,
// wireframe: true
// }));
textmesh.geometry.center();
scene.add(textmesh);
// textmesh.position.set(0, 2, 0)
});
So how to create a geometry having many points for text also, Why I am not able to use MeshBasicMaterial for Points in sphere?
What you see is actually the expected result. TextGeometry produces a geometry intended for meshes. If you use the same data for a point cloud, the result is probably not as expected since you just render each vertex as a point.
When doing this with TextGeometry, you will only see points at the front and back side of the text since points in between are not necessary for a mesh.
Consider to author the geometry in a tool like Blender instead and import it via GLTFLoader.

How to set a part of a box object with a diffrent color in three.js and the part dimension will get dynamically?

I am new to Three.js. This may be very basic question. But I tried to get a solution, unfortunately i didn't get any even from google. I have a 'mesh' with 'box geometry' and applied a 'MeshStandardMaterial' material. Specified a color in the material. Now I want show the part of this box is filled with some other color.This percentage of part will be dynamically.For example half of water filled in the glass the quantity of water will be get dynamically.
I want to represent level of some filling.Another option to make some part of this box is transparent that is also fine.
let cube_geometry = new THREE.BoxGeometry(tankWidth, tankHeight, tankDepth);
let ct_material = new THREE.MeshStandardMaterial({
color: colourCode,
metalness: 1.0,
roughness: 0.5,
transparent: true,
opacity: params['CT Opacity'],
alphaTest: 0.1
});
cube.name = name;
let cube = new THREE.Mesh(cube_geometry , ct_material);
group_ct.add(cube);//will add set of boxes to this group
//---------------------------
function SetQty(qty)
{
group_ct.traverse(function (tank) {
if (tank instanceof THREE.Mesh) {
tankName = tank.name;
qty =GetQty(tankName);
if(qty > 0) {
//want to display filled level inside the box as per the quantity
}
}
There are a couple ways to do this.
The first and easiest is to create two cubes of your desired material, and scale them along the same axis. The difficulty there is that you would also need to recompute their position so they maintain the same barrier edge for the varying values of "percentage filled".
Another option that requires fewer calculations (but is more conceptually complex) is to employ Material.clippingPlanes (example), and render your geometry using two different materials/meshes.
In the example below, I use the same cube geometry for both Mesh objects, but I use different materials with clipping planes pointing in opposite directions. I update their displacement in the render loop, which has the effect of changing the "fill".
// three.js r110
const W = window.innerWidth
const H = window.innerHeight
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
})
renderer.setSize(W, H)
document.body.appendChild(renderer.domElement)
// turn on clipping!
renderer.localClippingEnabled = true
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(28, W / H, 1, 1000)
camera.position.set(10, 20, 50)
camera.lookAt(scene.position)
scene.add(camera)
const light = new THREE.PointLight(0xffffff, 1)
//scene.add(light)
camera.add(light)
// define the clipping planes
const emptyClip = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
const fillClip = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0)
const boxGeo = new THREE.BoxGeometry(10, 10, 10)
const emptyMaterial = new THREE.MeshStandardMaterial({
color: 0x55ddff,
metalness: 0,
roughness: 0.2,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide,
clippingPlanes: [emptyClip]
})
const filledMaterial = new THREE.MeshStandardMaterial({
color: 0xff5555,
metalness: 0,
roughness: 0.2,
side: THREE.DoubleSide,
clippingPlanes: [fillClip]
})
const emptyMesh = new THREE.Mesh(boxGeo, emptyMaterial)
scene.add(emptyMesh)
const filledMesh = new THREE.Mesh(boxGeo, filledMaterial)
scene.add(filledMesh)
// layer separator
const separatorGeo = new THREE.PlaneGeometry(10, 10)
const separatorMat = new THREE.MeshStandardMaterial({
color: 0xff5555,
metalness: 0,
roughness: 0.2,
side: THREE.DoubleSide
})
const separator = new THREE.Mesh(separatorGeo, separatorMat)
separator.rotation.x = Math.PI / 2
scene.add(separator)
let count = 0
function animate() {
requestAnimationFrame(animate)
// compute the clipping plane displacement
let displacement = Math.sin(count) * 0.5 // min = 25%, max = 75%
displacement *= 5 // each cube side is 5 units
// set the displacement of the planes (relative to their normals!)
emptyMaterial.clippingPlanes[0].constant = displacement
filledMaterial.clippingPlanes[0].constant = -displacement
// update the separator
separator.position.y = -displacement
renderer.render(scene, camera)
count += 0.05
}
animate()
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

ThreeJS: Some material texture faces / triangles not rendering

I have a .obj and .mtl which load textures from .tga files. This all seems to work / look fine in 3D modeling software, but when loaded with three.js (THREE.WebGLRenderer({ antialias: true })) some of the object's children, (specifically between the knees and the calves, but not, for example, the pockets and legs), seem to have a transparent jagged gap / empty triangles.
(Tried turning on antialiasing, changing the overdraw value, turning off transparency, etc.)
Loader function:
threejsHelper.helpers.loadObject('objects/pants/MaleBaggyPants.obj', 'objects/pants/MaleBaggyPants.mtl', {
blinn1SG: (new THREE.MeshPhongMaterial({ color: 0xffffff, transparent: false, opacity: 1.0, overdraw: 0.5, map: threejsHelper.helpers.loadTexture(app.manager, 'objects/pants/button.tga') })),
blinn2SG: (new THREE.MeshPhongMaterial({ color: 0xffffff, transparent: false, opacity: 1.0, overdraw: 0.5, map: threejsHelper.helpers.loadTexture(app.manager, 'objects/pants/back.tga') })),
blinn4SG: (new THREE.MeshPhongMaterial({ color: 0xffffff, transparent: false, opacity: 1.0, overdraw: 0.5, map: threejsHelper.helpers.loadTexture(app.manager, 'objects/pants/front.tga') })),
blinn5SG: (new THREE.MeshPhongMaterial({ color: 0xffffff, transparent: false, opacity: 1.0, overdraw: 0.5, map: threejsHelper.helpers.loadTexture(app.manager, 'objects/pants/pocket.tga') })),
blinn6SG: (new THREE.MeshPhongMaterial({ color: 0xffffff, transparent: false, opacity: 1.0, overdraw: 0.5, map: threejsHelper.helpers.loadTexture(app.manager, 'objects/pants/back.tga') })),
blinn7SG: (new THREE.MeshPhongMaterial({ color: 0xffffff, transparent: false, opacity: 1.0, overdraw: 0.5, map: threejsHelper.helpers.loadTexture(app.manager, 'objects/pants/front.tga') }))
}, function (object) {
object.rotation.x = (-90*Math.PI/180);
object.rotation.z = (-90*Math.PI/180);
app.scene.add(object);
app.ready = true;
});
Helper functions:
...
loadTexture: function (manager, path) {
var texture;
if (path.split('.').pop() == 'tga') {
var loader = new THREE.TGALoader();
texture = loader.load(path);
} else {
texture = new THREE.Texture();
var loader = new THREE.ImageLoader(manager);
loader.load(path, function (image) {
texture.image = image;
texture.needsUpdate = true;
});
}
return texture;
},
loadMaterial: function (mtlPath, textures, complete) {
var loader = new THREE.MTLLoader();
loader.load(mtlPath, function (materials) {
materials.preload();
if (!!materials.materials) {
for (var key in materials.materials) {
if (key in textures) {
materials.materials[key] = textures[key];
}
}
}
complete(materials);
});
},
loadObject: function (objPath, mtlPath, textures, complete) {
app.helpers.loadMaterial(mtlPath, textures, function (materials) {
var loader = new THREE.OBJLoader();
loader.setMaterials(materials);
loader.load(objPath, function (object) {
complete(object);
});
});
},
...
You seem to have extra vertices along the seem on both the objects which I think will screw with the triangulation.
Remove these vertices that are not needed and I'm 99% sure it will fix your problem.
I also noticed the model is at a very small scale.
I had the same problem. Some triangle didn't properly render on Threejs while it properly rendered on my OpenGL app.
I solved this trouble by using Uint32Array instead of Uint16Array to define the index of the geometry. I just put this line of code and everything worked like a charm.
const indices = new Uint32Array(faceDict.index);

Three.js png texture - alpha renders as white instead as transparent

I'm creating a cube and I apply 6 different textures to each of it's faces. Each texture is a .png file and contains transparent parts. I'm also applying a color to the cube - I want to see that color trough png transparency.
Problem: Transparency renders as white color so I cannot see the base color of the cube (which renders ok if I remove the png texture)
How can I make the png transparency work? I tried playing with some material settings but none make it transparent.
Code for creating the cube and materials:
var geometry = new THREE.CubeGeometry(150, 200, 150, 2, 2, 2);
var materials = [];
// create textures array for all cube sides
for (var i = 1; i < 7; i++) {
var img = new Image();
img.src = 'img/s' + i + '.png';
var tex = new THREE.Texture(img);
img.tex = tex;
img.onload = function () {
this.tex.needsUpdate = true;
};
var mat = new THREE.MeshBasicMaterial({color: 0x00ff00, map: tex, transparent: true, overdraw: true });
materials.push(mat);
}
cube = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
cube.position.y = 150;
scene.add(cube);
EDIT:
Picture below shows the problem - with senthanal solution the left texture now renders ok - it is a png image without transparency - I set the transparency in code with
materialArray.push(new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture('img/s2.png'), transparent: true, opacity: 0.9, color: 0xFF0000 }));
The right texture is also a png image - only that it has a transparent area (all that renders white should be pure red since it is transparent and should take the color from the cube?). How can I make that white part transparent?
the opacity attribute of material does the trick for you. Follows, example code snippet:
var materialArray = [];
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/xpos.png' ), transparent: true, opacity: 0.5, color: 0xFF0000 }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/xneg.png' ), transparent: true, opacity: 0.5, color: 0xFF0000 }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/ypos.png' ), transparent: true, opacity: 0.5, color: 0xFF0000 }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/yneg.png' ), transparent: true, opacity: 0.5, color: 0xFF0000 }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/zpos.png' ), transparent: true, opacity: 0.5, color: 0xFF0000 }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/zneg.png' ), transparent: true, opacity: 0.5, color: 0xFF0000 }));
var MovingCubeMat = new THREE.MeshFaceMaterial(materialArray);
var MovingCubeGeom = new THREE.CubeGeometry( 50, 50, 50, 1, 1, 1, materialArray );
MovingCube = new THREE.Mesh( MovingCubeGeom, MovingCubeMat );
MovingCube.position.set(0, 25.1, 0);
scene.add( MovingCube );
http://threejs.org/docs/#Reference/Materials/Material The key is to set transparent attribute true and set opacity to 0.5(for example).
Add the second the cube which fits inside exactly with no transparency, idea from #WestLangley ( Three.js canvas render and transparency )
backCube = new THREE.Mesh( MovingCubeGeom, new THREE.MeshBasicMaterial( { color: 0xFF0000 }) );
backCube.position.set(0, 25.1, 0);
backCube.scale.set( 0.99, 0.99, 0.99 );
scene.add( backCube );
for those looking for a simple transparent png import helper:
import { MeshBasicMaterial, TextureLoader } from 'three'
export const importTexture = async(url, material) => {
const loader = new TextureLoader()
const texture = await loader.loadAsync(url)
material.map = texture
material.transparent = true
material.needsUpdate = true
return texture
}
//usage
const geo = new PlaneGeometry(1, 1)
const mat = new MeshBasicMaterial()
const mesh = new Mesh(geo, mat)
scene.add(mesh)
//this is asynchronous
importTexture('path/to/texture.png', mat)

How to set transparency to single face of the custom geometry using Three.js?

I've the custom geometry with sqaure-base and it looks like a cone. here is jsfiddle link: http://jsfiddle.net/suvKg/18/
I've obtained transparency to the whole object at here:
var meshMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, opacity: 0.6, depthWrite: false, depthTest: false, vertexColors: THREE.VertexColors } );
But I don't want transparency to be applied to base of the cone, but only side faces should have it. How to do that?
you need to use THREE.MeshFaceMaterial() for your entire mesh. For example if your geometry have X faces and 2 differents materials :
var materials = [
new THREE.MeshLambertMaterial( { color: 0xffffff, opacity: 0.6, depthWrite: false, depthTest: false, vertexColors: THREE.VertexColors } ),
new THREE.MeshLambertMaterial( { color: 0xffffff, opacity: 1, depthWrite: false, depthTest: false, vertexColors: THREE.VertexColors } )
]; // the two materials
var mesh = new THREE.Mesh(yourGeometry, new THREE.MeshFaceMaterial(materials)); //tell three.js that you will have several materials in your geometry
Then, you will need to determine materialIndex manualy in each of your faces based on the materials indexes
yourGeometry.faces[0].materialIndex = 0;
yourGeometry.faces[1].materialIndex = 0;
yourGeometry.faces[2].materialIndex = 1; // <= the cone base
...
yourGeometry.faces[lastFaceIndex].materialIndex = 0;
NB: default parameter for materialIndex is 0 so you will need to determine only one face to its material index in your case

Resources