I would like to render a translucent PNG texture behind a see-through Physical Material. However, THREE.js doesn't render the translucent texture through the Physical Material at all.
I figured out that setting transparent: false on the material makes it visible, but that obviously makes my texture opaque, which is not what I want.
How can I make a translucent PNG texture visible through Physical Material?
const renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('canvas'),
});
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0000ff);
scene.add(new THREE.AmbientLight(0xffffff, 1));
const texture = new THREE.TextureLoader().load('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE4AAABOCAYAAACOqiAdAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADdSURBVHhe7dAhCgIBFADR1SgYLDatHsv7n2ItXzDILg7Y3ivT57Cu63P57jHdcpvuuU63XKZ7ztO30/TTffo3xyk/Mi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi5ZlhcxLwSYJ4qy6wAAAABJRU5ErkJggg==');
const camera = new THREE.PerspectiveCamera(65, renderer.domElement.clientWidth / renderer.domElement.clientHeight, 0.1, 1000);
{
const geometry = new THREE.PlaneBufferGeometry(4, 4);
const material = new THREE.MeshStandardMaterial({
color: 0xffff00,
side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(geometry, material);
plane.position.z = 1;
scene.add(plane);
}
{
const geometry = new THREE.PlaneBufferGeometry(3, 3);
const material = new THREE.MeshStandardMaterial({
color: 0xff0000,
side: THREE.DoubleSide,
map: texture,
transparent: true,
});
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
document.getElementById('transparent').onchange = (evt) => {
material.transparent = evt.target.checked;
material.needsUpdate = true;
};
}
{
const geometry = new THREE.PlaneBufferGeometry(3, 3);
const material = new THREE.MeshPhysicalMaterial({
side: THREE.DoubleSide,
roughness: 0.3,
transmission: 0.4,
color: 0xffffff,
});
const plane = new THREE.Mesh(geometry, material);
plane.position.z = -2;
plane.position.x = 1;
scene.add(plane);
}
const startTime = performance.now();
function render() {
const now = performance.now();
const angle = (now - startTime) * 0.002;
camera.position.set(Math.cos(angle) * 2, Math.sin(angle) * 2, -5);
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://cdn.jsdelivr.net/npm/three#0.139.2/build/three.min.js"></script>
<div style="position: absolute; left: 0; top: 0; z-index: 1; color: white;">
<input type="checkbox" id="transparent" name="transparent" checked /><label for="transparent"> Transparent</label>
</div>
<canvas id="canvas" width="350" height="150" style="position: absolute; left: 0; top: 0;" />
The "transmission" feature of THREE.MeshPhysicalMaterial is a more advanced type of physically-based transparency that requires additional rendering passes. Currently three.js (and most other realtime engines I'm aware of) cannot display alpha-blending (.transparent=true) or transmissive (.transmission>0) materials through a transmissive material. If you need multiple layers of transparency you'll need to stick with alpha blending and not transmission, provided by .transparent=true and opacity or a texture with an alpha channel.
Related discussion: https://github.com/mrdoob/three.js/issues/22009
Related
I'm just trying out Three.js. As I want to use CanvasRenderer as a fallback for devices where WebGL is not supported, I've noticed two issues:
Distored line / Bad geometry?
Additional pixel
Here's my current code
var width = 960;
var height = 500;
var aspect = width / height;
var D = 1;
var CanvasRenderer = true;
var scene = new THREE.Scene();
var camera = new THREE.OrthographicCamera(-D * aspect, D * aspect, D, -D, 1, 1000);
if(CanvasRenderer) {
var renderer = new THREE.CanvasRenderer({
alpha: true,
antialias: true
});
} else {
var renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
});
}
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry(0.1, 1, 0.5);
var cube = new THREE.Mesh(geometry, [
new THREE.MeshPhongMaterial({
color: 0x222222, // right
overdraw: true
}),
new THREE.MeshPhongMaterial({
color: 0xffffff,
overdraw: true
}),
new THREE.MeshPhongMaterial({
color: 0xEDE6F5, // Top
overdraw: true
}),
new THREE.MeshPhongMaterial({
color: 0xffffff,
overdraw: true
}),
new THREE.MeshPhongMaterial({
color: 0xFFFFFF, // Left
overdraw: true
}),
new THREE.MeshPhongMaterial({
color: 0xffffff,
overdraw: true
})
]);
scene.add(cube);
scene.add(new THREE.AmbientLight(0xFFFFFF));
camera.position.set(20, 20, 20);
camera.lookAt(scene.position);
renderer.render(scene, camera);
body {
margin: 0px;
overflow: hidden;
background: #EEE;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/renderers/Projector.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/renderers/CanvasRenderer.js"></script>
WebGL difference
You can see the difference with WebGL renderer:
JSFiddle
http://jsfiddle.net/4tbs4tpL/2/
When using CanvasRenderer, the Material.overdraw property takes values between 0 and 1. A reasonable value is 0.5.
The artifacts you refer to will likely diminish with lower values of overdraw.
three.js r.86
I have created a globe in three.js and am using an image mapped to the sphere.
On top of this, I'm using the ThreeGeoJSON library to render geojson data.
However the geographies don't match up.
I need to rotate the globe with the mapped image so that they align, but I can't figure out how to do so. I tried setting a quaternion variable and rotating based on that, but can't get it working. Any help or pointers would be very much appreciated.
Here you can see a working version of what I've done so far:
http://bl.ocks.org/jhubley/8450d7b0df0a4a9fd8ce52d1775515d5
All of the code, images, data here:
https://gist.github.com/jhubley/8450d7b0df0a4a9fd8ce52d1775515d5
I've also pasted the index.html below.
<html>
<head>
<title>ThreeGeoJSON</title>
<script src="threeGeoJSON.js"></script>
<!-- Three.js library, movement controls, and jquery for the geojson-->
<script src="three.min.js"></script>
<script src="TrackballControls.js"></script>
<script src="jquery-1.10.2.min.js"></script>
</head>
<body>
<script type="text/JavaScript">
var width = window.innerWidth,
height = window.innerHeight;
// Earth params
var radius = 9.99,
segments = 32,
rotation = 0 ;
//New scene and camera
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(55, width / height, 0.01, 1000);
camera.position.z = 1;
camera.position.x = -.2;
camera.position.y = .5;
//New Renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
//Add lighting
scene.add(new THREE.AmbientLight(0x333333));
var light = new THREE.DirectionalLight(0xe4eef9, .7);
light.position.set(12,12,8);
scene.add(light);
var quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 );
var sphere = createSphere(radius, segments);
//sphere.rotation.y = rotation;
sphere.rotation = new THREE.Euler().setFromQuaternion( quaternion );
scene.add(sphere)
//Create a sphere to make visualization easier.
var geometry = new THREE.SphereGeometry(10, 32, 32);
var material = new THREE.MeshPhongMaterial({
//wireframe: true,
//transparent: true
});
function createSphere(radius, segments) {
return new THREE.Mesh(
new THREE.SphereGeometry(radius, segments, segments),
new THREE.MeshPhongMaterial({
map: THREE.ImageUtils.loadTexture('relief.jpg'),
bumpMap: THREE.ImageUtils.loadTexture('elev_bump_4k.jpg'),
bumpScale: 0.005,
specularMap: THREE.ImageUtils.loadTexture('wateretopo.png'),
specular: new THREE.Color('grey')
})
);
}
var clouds = createClouds(radius, segments);
clouds.rotation.y = rotation;
scene.add(clouds)
function createClouds(radius, segments) {
return new THREE.Mesh(
new THREE.SphereGeometry(radius + .003, segments, segments),
new THREE.MeshPhongMaterial({
map: THREE.ImageUtils.loadTexture('n_amer_clouds.png'),
transparent: true
})
);
}
//Draw the GeoJSON
var test_json = $.getJSON("countries_states.geojson", function(data) {
drawThreeGeo(data, 10, 'sphere', {
color: 'red'
})
});
//Draw the GeoJSON loggerhead data
var test_json = $.getJSON("loggerhead-distro-cec-any.json", function(data) {
drawThreeGeo(data, 10, 'sphere', {
color: 'blue'
})
});
//Set the camera position
camera.position.z = 30;
//Enable controls
var controls = new THREE.TrackballControls(camera);
//Render the image
function render() {
controls.update();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
</script>
</body>
</html>
Instead of ancient r66, I used r81 (just replaced three.min.js). I modified your createSphere() function a bit, and seems it's working.
function createSphere(radius, segments) {
var sphGeom = new THREE.SphereGeometry(radius, segments, segments);
sphGeom.rotateY(THREE.Math.degToRad(-90));
return new THREE.Mesh(
sphGeom,
new THREE.MeshPhongMaterial({
map: new THREE.TextureLoader().load('relief.jpg'),
bumpMap: new THREE.TextureLoader().load('elev_bump_4k.jpg'),
bumpScale: 0.005,
specularMap: new THREE.TextureLoader().load('wateretopo.png'),
specular: new THREE.Color('grey')
})
);
}
The only thing I did was to rotate the sphere's geometry around Y-axis at -90 degrees. The result is here
So I am working on a project by using the webvr-boilder-plate
It is weird that the text is not rendered in VR mode on phone, but it is rendered in web browser(Firefox). I also added a cube to test, the cube is rendered in both iPhone and web browser(Firefox)
Here is code for both cube and the text:
// Create 3D objects.
var geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
var material = new THREE.MeshNormalMaterial();
var cube = new THREE.Mesh(geometry, material);
// Position cube mesh
cube.position.z = -1;
scene.add(cube);
function displayCurrentStory(name) {
scene.remove(currentStoryTextMesh);
console.log("currentStoryText:",name);
currentStoryText = new THREE.TextGeometry(name, {
size: .25,
height: .05,
curveSegments: 12,
font: "helvetiker"
});
currentStoryTextMesh = new THREE.Mesh(currentStoryText,
new THREE.MeshBasicMaterial({color: 0xFF00FF}
));
currentStoryTextMesh.position.y = 0.2;
currentStoryTextMesh.position.z = -1;
currentStoryTextMesh.rotation.x = 0.0;
//currentStoryTextMesh.rotation.y = -180;
scene.add(currentStoryTextMesh);}
Any insights?
I am trying to create folding effect like folding a paper in half.
I am using Three.js Library to help me manipulate the image.
I have created 2 face paper using 2 PlaneGeometry and loaded on them texture with ImageUtils.loadTexture.
I am curious if there is a way to split the image in half and then rotate one half on his edge so you create the folding effect.
I guess this can be done since the create plane function THREE.PlaneGeometry(width, height, widthSegments, heightSegments); has widthSegments and heightSegments parameters.
<html>
<head>
<title>My first Three.js app</title>
<style>
body {
background-color: #ffffff;
margin: 0px;
overflow: hidden;
}
container {
background-color: #ffffff;
}
</style>
</head>
<body>
<div id="container" ></div>
<script src="three.min.js"></script>
<script src="OrbitControls.js"></script>
<script src="stats.min.js"></script>
<script>
var renderer, scene, camera, card;
var container, stats;
init();
animate();
function init() {
container = document.getElementById('container');
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xffffff, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild(stats.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 300;
controls = new THREE.OrbitControls(camera, container);
camera.lookAt(scene.position);
// geometry
var geometry1 = new THREE.PlaneGeometry(90, 110, 3, 1);
var geometry2 = new THREE.PlaneGeometry(90, 110, 3, 1);
geometry2.applyMatrix(new THREE.Matrix4().makeRotationY(Math.PI));
// textures
var textureFront = new THREE.ImageUtils.loadTexture('Flyer2pag1.png'); // Flyer has image sorce in project folder
var textureBack = new THREE.ImageUtils.loadTexture('Flyer2pag2.png');
// material
var material1 = new THREE.MeshBasicMaterial({ color: 0xffffff, map: textureFront });
var material2 = new THREE.MeshBasicMaterial({ color: 0xffffff, map: textureBack });
// card
card = new THREE.Object3D();
scene.add(card);
// mesh
var mesh1 = new THREE.Mesh(geometry1, material1);
card.add(mesh1);
var mesh2 = new THREE.Mesh(geometry2, material2);
card.add(mesh2);
}
function animate() {
requestAnimationFrame(animate);
//card.rotation.y += 0.01;
stats.update();
renderer.render(scene, camera);
}
</script>
</body>
</html>
I am using ThreeJS/WebGL to demonstrate a rotating planet inside of an iframe. It is working fine except for a warning and an issue:
Warning: THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.
Issue: Uncaught Error: THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.
I have included the following assets into the body of the iframe:
<div id="webgl"></div>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/bubblin/The-Solar-System/master/jupiter/js/detector.js">
</script>
<script type="text/javascript" src="https://cdn.rawgit.com/bubblin/The-Solar-System/master/jupiter/js/trackBallControls.js"> </script>
<script type="text/javascript" src="https://cdn.rawgit.com/bubblin/The-Solar-System/master/jupiter/js/xRingGeometry.js"> </script>
As you can see I'm using trackBallControls.js to rotate the sphere based on user input/touch and I have overridden a RingGeometry method to layer the ring_image along the circumference instead of emanating it radially.
Here's my complete code to simulate Jupiter, for example:
(function() {
var webglEl = document.getElementById('webgl');
if (!Detector.webgl) {
Detector.addGetWebGLMessage(webglEl);
return;
}
THREE.ImageUtils.crossOrigin = '';
var width = window.innerWidth,
height = window.innerHeight;
var radius = 0.45,
segments = 32,
rotation = 5;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, width / height, 0.01, 1000);
camera.position.z = 3;
camera.position.y = 1;
camera.position.x = -1;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
scene.add(new THREE.AmbientLight(0x553333));
var light = new THREE.DirectionalLight(0xffffff, .5);
light.position.set(5, 3, 5);
scene.add(light);
var sphere = createSphere(radius, segments);
sphere.rotation.y = rotation;
scene.add(sphere);
var rings = createRings(radius, segments);
rings.rotation.y = rotation;
scene.add(rings);
var stars = createStars(90, 64);
scene.add(stars);
var controls = new THREE.TrackballControls(camera);
webglEl.appendChild(renderer.domElement);
render();
function render() {
controls.update();
sphere.rotation.y += 0.08;
rings.rotation.y += 0.05;
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function createSphere(radius, segments) {
return new THREE.Mesh(new THREE.SphereGeometry(radius, segments, segments), new THREE.MeshPhongMaterial({
map: THREE.ImageUtils.loadTexture('https://cdn.rawgit.com/marvindanig/Images/master/jupiter/jupitermap.jpg'),
bumpScale: 0.05,
specular: new THREE.Color('#190909')
}));
}
function createRings(radius, segments) {
return new THREE.Mesh(new THREE.XRingGeometry(1.3 * radius, 1.6 * radius, 2 * segments, 5, 0, Math.PI * 2), new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('https://cdn.rawgit.com/bubblin/The-Solar-System/master/jupiter/images/rings.png'),
side: THREE.DoubleSide,
transparent: true,
opacity: 0.2,
specular: new THREE.Color('#495909')
}));
}
function createStars(radius, segments) {
return new THREE.Mesh(new THREE.SphereGeometry(radius, segments, segments), new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('https://cdn.rawgit.com/marvindanig/Images/master/galaxy_starfield.png'),
side: THREE.BackSide
}));
}
}());
It works (See the output here), but I get those warnings and an error. The errors and warnings eventually build up, and my app crashes on the iPad/iPhone.
Please help, I am new to ThreeJS.
Regarding:
Warning: THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.
In computer graphics, textures that are powers of two can be processed faster than those that are not which is why you get the warning. I suggest you resize both the sphere texture and rings texture to square powers of two using your favourite image editor. Alternatively, you can set the minFilter as suggested by the warning:
function createSphere(radius, segments) {
var texture = THREE.ImageUtils.loadTexture('https://cdn.rawgit.com/marvindanig/Images/master/jupiter/jupitermap.jpg');
texture.minFilter = THREE.NearestFilter;
return new THREE.Mesh(new THREE.SphereGeometry(radius, segments, segments), new THREE.MeshPhongMaterial({
map: texture,
bumpScale: 0.05,
specular: new THREE.Color('#190909')
}));
}
As for the second error, I'm not seeing it on Chrome 42 but it is caused because one of your scripts (that likely isn't shown here as I could not find the problem in any of them) uses Quaternion.setFromEuler like this:
quat.setFromEuler(new THREE.Vector3(pitch, yaw, roll));
when it should it be using like this:
quat.setFromEuler(new THREE.Euler(pitch, yaw, roll));
Do a quick CTRL + F in your code and you'll find the faulty script.