3D rendering a 2D cylinder in blender or three.js? - three.js

I'm curious if it's possible to render this object, for the purposes of being exported and used in three.js
pic of cylinder
Seemed simple at the time, but I've been having a lot of trouble getting it to work. I was able to get close to what I wanted with Blender's "freestyle lines", but they aren't exportable.
cylinder in blender
It looks like I will have to make a custom material or something like that, but I was wondering if there was a more straightforward way I haven't seen. I tried Three's "wireframe" and "EdgeGeometry" tools, but I'm trying to avoid rendering the interior edges of the model.
And it seems really difficult to make a mathematically perfect cylinder outline from a UV, unless there's something I'm not seeing!
Anyways, thank you for your time

I'm not sure how correct this approach is, but its result is almost similar
to what is shown on the picture you provided.
Reference
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(2, 3, 5).setLength(400);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x999999);
renderer.setSize(window.innerWidth, window.innerHeight);
effect = new THREE.OutlineEffect(renderer);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var cylinder = new THREE.Mesh(new THREE.CylinderBufferGeometry(100, 100, 200, 32), new THREE.MeshBasicMaterial({
color: "white"
}));
scene.add(cylinder);
var lid = new THREE.Mesh(new THREE.CylinderBufferGeometry(99, 99, 0.001, 32), new THREE.MeshBasicMaterial({
color: "white"
}));
lid.geometry.translate(0, 101.0, 0);
scene.add(lid);
var lid2 = new THREE.Mesh(new THREE.CylinderBufferGeometry(99, 99, 0.001, 32), new THREE.MeshBasicMaterial({
color: "white"
}));
lid2.geometry.translate(0, -101.0, 0);
scene.add(lid2);
render();
function render() {
requestAnimationFrame(render);
effect.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/effects/OutlineEffect.js"></script>

Related

ThreeJS - Create cube where the surfaces are transparent instead of the cube volume

I am using the following code to create this 3D transparent cube.
// Create the cube itself
const cubeGeom = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( {color: 0x00ff00, opacity:0.4, transparent:true});
const cube = new THREE.Mesh( cubeGeom, material );
// Also add a wireframe to the cube to better see the depth
const _wireframe = new THREE.EdgesGeometry( cubeGeom ); // or WireframeGeometry( geometry )
const wireframe = new THREE.LineSegments( _wireframe);
// Rotate it a little for a better vantage point
cube.rotation.set(0.2, -0.2, -0.1)
wireframe.rotation.set(0.2, -0.2, -0.1)
// add to scene
scene.add( cube )
scene.add( wireframe );
As can been seen, the cube appears as a single volume that is transparent. Instead, I would want to create a hollow cube with 6 transparent faces. Think of a cube made out of 6 transparent and colored window-panes. See this example: my desired result would be example 1 for each of the 6 faces, but now it is like example 2.
Update
I tried to create individual 'window panes'. However the behavior is not as I would expect.
I create individual panes like so:
geometry = new THREE.PlaneGeometry( 1, 1 );
material = new THREE.MeshBasicMaterial( {color: 0x00ff00, side: THREE.DoubleSide, transparent:true, opacity:0.2});
planeX = new THREE.Mesh( geometry, material);
planeY = new THREE.Mesh( geometry, material);
planeZ = new THREE.Mesh( geometry, material);
And then I add all three planes to wireframe.
Then I rotate them a little, so they intersect at different orientations.
const RAD_TO_DEG = Math.PI * 2 / 360;
planeX.rotation.y = RAD_TO_DEG * 90
planeY.rotation.x = RAD_TO_DEG * 90
Now I can see the effect of 'stacking' the panes on top of each other, however it is not as it should be.
I would instead expect something like this based on real physics (made with terrible paint-skills). That is, the color depends on the number of overlapping panes.
EDIT
When transparent panes overlap from the viewing direciton, transparancy appears to work perfectly. However, when the panes intersect it breaks.
Here I have copied the snipped provided by #Anye and added one.rotation.y = Math.PI * 0.5 and commented out two.position.set(0.5, 0.5, 0.5); so that the panes intersect.
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 cube = new THREE.Group();
one = new Pane();
two = new Pane();
one.rotation.y = Math.PI * 0.5
one.position.z = 0.2;
// two.position.set(0.5, 0.5, 0.5);
cube.add(one);
cube.add(two);
cube.rotation.set(Math.PI / 4, Math.PI / 4, Math.PI / 4);
scene.add(cube);
function Pane() {
let geometry = new THREE.PlaneGeometry(1, 1);
let material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent: true, opacity: 0.4});
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 640px;
height: 360px;
}
<html>
<head>
<title>Demo</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
</body>
</html>
EDIT
The snipped looks pretty good; it clearly shows a different color where the panes overlap. However, it does not show this everywhere. See this image. The left is what the snippet generates, the right is what it should look like. Only the portion of overlap that is in front of the intersection shows the discoloration, while the section behind the intersection should, but does not show discoloration.
You might want to take a look at CSG, Constructive Solid Geometry. With CSG, you can create a hole in your original cube using a boolean. To start, you could take a look at this quick tutorial. Below are some examples of what you can do with CSG.
var cube = new CSG.cube();
var sphere = CSG.sphere({radius: 1.3, stacks: 16});
var geometry = cube.subtract(sphere);
=>
CSG, though, has some limitations, since it isn't made specifically for three.js. A cheap alternative would be to create six individual translucent panes, and format them to create a cube. Then you could group them:
var group = new THREE.Group();
group.add(pane1);
group.add(pane2);
group.add(pane3);
group.add(pane4);
group.add(pane5);
group.add(pane6);
Update
Something may be wrong with your code, which is why it isn't shading accordingly for you. See this minimal example, which shows how the panes shade appropriately based on overlaps.
Update 2
I updated the snippet so the 2 panes aren't touching at all... I am still able to see the shading. Maybe if you were to try to reproduce this example?
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 cube = new THREE.Group();
one = new Pane();
two = new Pane();
one.rotation.y = Math.PI * 0.5;
one.position.z = 0.2;
cube.add(one);
cube.add(two);
cube.rotation.set(Math.PI / 4, Math.PI / 4, Math.PI / 4);
scene.add(cube);
function Pane() {
let geometry = new THREE.PlaneGeometry(1, 1);
let material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent: true, opacity: 0.4});
material.depthWrite = false
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 640px;
height: 360px;
}
<html>
<head>
<title>Demo</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
</body>
</html>
Update 3
Below is a screenshot of what I see in your snippet... Seems to be working fine...
You're experiencing one of my first head-scratchers:
ShaderMaterial transparency
As the answer to that question states, the three.js transparency system performs order-dependent transparency. Normally, it will take whichever object is closest to the camera (by mesh position), but because all of your planes are centered at the same point, there is no winner, so you get some strange transparency effects.
If you move the plane meshes out to form the actual sides of the box, then you should see the effect you're looking for. But that won't be the end of strange transparency effects, And you would need to implement your own Order-Independent Transparency (or find an extension library that does it for you) to achieve more physically-accurate transparency effects.

How to put a transparent color mask on a 3D object in three.js?

I've been trying to 'submerge' a 3D object in a semi-transparent 3D plane of water (without the whole water plane showing), and after having experimented with custom blending modes for hours, I don't really get how to do it.
Fiddle here: https://jsfiddle.net/mglonnro/p2ju4qbk/34/
var camera, scene, renderer, geometry, material, mesh,
surface_geometry, surface_material, surface_mesh,
bottom_geometry, bottom_material, bottom_mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 200, 500);
camera.lookAt(0, 0, 0);
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
bottom_geometry = new THREE.PlaneBufferGeometry(10000, 10000);
bottom_material = new THREE.MeshBasicMaterial({
color: 0xFFAAAA,
side: THREE.DoubleSide
});
bottom_mesh = new THREE.Mesh(bottom_geometry, bottom_material);
bottom_mesh.rotation.set(Math.PI / 2, 0, 0);
bottom_mesh.position.set(0, -200, 0);
scene.add(bottom_mesh);
surface_geometry = new THREE.PlaneBufferGeometry(400, 400);
surface_material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.6
});
surface_mesh = new THREE.Mesh(surface_geometry, surface_material);
surface_mesh.rotation.set(Math.PI / 2, 0, 0);
scene.add(surface_mesh);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
The cube is submerged, as it should be, and the parts covered by the transparent water look like I want them to look.
The problem, however, is that I want ONLY the cube and its submerged parts to be rendered, NOT the rest of the water plane.
In other words:
There are three objects in the scene:
the redish "bottom" farthest away
the cube, partly above, partly
below the water
the water
Is there some way to blend these together so that the water pixels are rendered only when they are on top of a cube pixel, not when they are just on top of the background/bottom?
EDIT: SOLUTION
Add stencil write functionality to the cube:
const stencilId = 1;
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial({
stencilWrite: true,
stencilFunc: THREE.AlwaysStencilFunc,
stencilZPass: THREE.ReplaceStencilOp,
stencilRef: stencilId
});
Add stencil test functionality to the surface:
surface_material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.6,
stencilWrite: true,
stencilFunc: THREE.EqualStencilFunc,
stencilRef: stencilId
});
Realize that the three.js version in jsfiddle is too old to support stencils and move to codepen :)
Stencil Test is a natural way to achieve that.
All objects which should have water on top of them write some stencil value.
Water plane has stencil test set to this value.
three.js has stencil example, but it uses IncrementWrap and DecrementWrap logic which is not needed for your case.
I recommend trying ReplaceStencilOp for cube and EqualStencilFunc for water.

How to set opacity in drawing lines use three.js, use LineBasicMaterial?

I want to change lines' width and opacity,and my code as this:
var geometry_link = new THREE.Geometry();
geometry_link.vertices.push(
new THREE.Vector3(item[0].x *1,item[0].y *1,item[0].z),
new THREE.Vector3(item[1].x *1,item[1].y *1,item[1].z)
);
geometry_link.colors.push(
new THREE.Color(0x000000),
new THREE.Color(0xffffff)
);
var line = new THREE.Line(geometry_link, new THREE.LineBasicMaterial({
vertexColors: true,
linewidth: 8,
transparent: true,
opacity: 0.1
})
but it dose not work, both linewidth and opacity. I have found the similar question,said linewidth does not work on windows, that's 5 years ago, how about now?
Thanks for your idea
LineBasicMaterial does support transparent lines as demonstrated by the following live example:
let camera, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.z = 1;
scene = new THREE.Scene();
const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), new THREE.Vector3(1, 0, 0)]);
const material = new THREE.LineBasicMaterial({
transparent: true,
opacity: 0.5
});
const mesh = new THREE.Line(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
body {
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.119.1/build/three.js"></script>
Wide lines however are not supported. So setting linewidth to a different value like 1 has no effect in most browsers. This is a WebGL limitation in context of line primitives.
However, three.js provides a wide line implementation based on triangles (sometimes called mesh lines or ribbons). Check out the following example for more information:
https://threejs.org/examples/webgl_lines_fat

How to generate a shape with 3 faces in three.js

I am trying to generate a shape like this, 3d rectangle with a transparent face or just 3 panels in a 3d way.
I am new to three.js and I wanted to know if this is possible and if you could guide me.
Thanks a lot!
You can find an image of what I want to generate here:
https://i.stack.imgur.com/h0ja7.png
As an option, you can do it this way:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(2, 2, 3);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
var contorls = new THREE.OrbitControls(camera, renderer.domElement);
var boxGeom = new THREE.BoxGeometry(2, 1, 1);
var mat1 = new THREE.MeshBasicMaterial({
color: "red",
side: THREE.DoubleSide
});
var mat2 = new THREE.MeshBasicMaterial({
color: "aqua",
side: THREE.DoubleSide,
transparent: true,
opacity: 0.5
});
var boxMat = [mat1, mat1, null, null, mat2, null];
var box = new THREE.Mesh(boxGeom, boxMat);
scene.add(box);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

How to draw a cylinder along x-axis using three js?

The default axis of the cylinder is y-axis, forms a verticle cylinder. I need to draw a horizontal cylinder.
You can use .rotateZ() method of your cylinder geometry:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 5, 10);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var geom = new THREE.CylinderGeometry(1, 1, 7, 16, 1);
geom.rotateZ(-Math.PI * 0.5); // rotate 90 degrees clockwise around z-axis
var cylinder = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
}));
scene.add(cylinder);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Resources