Im trying to add two textures. One for the wall and another for the floor. After rendering Im just getting a solid color instead of the texture.
Here is my scene & camera configuration:
const tempScene = new THREE.Scene();
tempScene.background = new THREE.Color(0x021D49);
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
tempScene.add(ambient);
const directionalLight = new THREE.DirectionalLight(0xfafafa, 0.6);
directionalLight.position.set(0, 300, 300);
tempScene.add(directionalLight);
const tempCamera = new THREE.PerspectiveCamera(
60,
el.clientWidth / el.clientHeight,
0.1,
10000,
);
tempCamera.up.set(0, 0, 1);
tempCamera.position.set(0, 300, 300);
tempCamera.lookAt(0, 0, 0);
const tempControls = new OrbitControls(tempCamera, el);
tempControls.maxPolarAngle = Math.PI / 2;
tempControls.minDistance = 10;
tempControls.maxDistance = 500;
tempControls.enablePan = false;
const tempRenderer = new THREE.WebGLRenderer({ canvas: el, antialias: true });
tempRenderer.setSize(el.clientWidth, el.clientHeight);
tempRenderer.setPixelRatio(window.devicePixelRatio);
tempRenderer.shadowMap.enabled = true;
tempRenderer.shadowMap.type = THREE.PCFSoftShadowMap;
tempRenderer.outputEncoding = THREE.sRGBEncoding;
Here is the code for texture:
const floorTex = new THREE.TextureLoader().load(floorTexture);
const wallMaterial = new THREE.MeshBasicMaterial({ map: wallTex, side: THREE.DoubleSide });
const floorMaterial = new THREE.MeshStandardMaterial({ map: floorTex });
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 });
const wallGeometry = new THREE.BufferGeometry();
const wallVertices = new Float32Array([
-x1,
y1,
0, // vertex 1
-x1,
y1,
10, // vertex 2
-x2,
y2,
0, // vertex 3
-x2,
y2,
10, // vertex 4
]);
wallGeometry.setAttribute('position', new THREE.BufferAttribute(wallVertices, 3));
wallGeometry.setIndex([0, 2, 1, 2, 3, 1]);
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
const geo = new THREE.EdgesGeometry(wall.geometry);
const wireframe = new THREE.LineSegments(geo, lineMaterial);
wall.add(wireframe);
wall.castShadow = true;
wall.receiveShadow = true;
scene!.add(wall);
const floorGeometry = new THREE.Shape(floorPoints.map((point) => new THREE.Vector2(point[0], point[1])));
const shapeGeometry = new THREE.ShapeGeometry(floorGeometry);
const floor = new THREE.Mesh(shapeGeometry, floorMaterial);
floor.receiveShadow = true;
scene!.add(floor);
This is how it looks
But in real the texture look like this:
floorTexture
wallTexture
Your wall does not get a texture applied since you define no texture coordinates for wallGeometry. Next to position you need a uv buffer attribute holding these data.
Unfortunately, the code snippet does not provide enough information to investigate why the floor has no texture. Instances of ShapeGeometry do have a texture coordinates so I could imagine there is a loading issue with your texture.
BTW: If you add tempRenderer.outputEncoding = THREE.sRGBEncoding; to your code, you have to set the encoding property of all color textures to THREE.sRGBEncoding as well. Otherwise your sRGB workflow is incomplete.
Related
I have 3D models as such:
I want to add a cast shadow similar to this:
And I have the following piece of code responsible for the model:
var ambLight = new THREE.AmbientLight( 0x404040 );
this.scene.add(ambLight)
var loader = new THREE.GLTFLoader();
loader.load(path,function (gltf) {
gltf.scene.traverse( function( model ) {
if (model.isMesh){
model.castShadow = true;
}
});
this.scene.add(gltf.scene);
}
I added the castSHadow part as seen in this StackOverflow post.
I've tried model.castShadow = true and I've tried removing the if condition and just leave the castShadow but that doesn't work either. Am I missing a step? The full custom layer code is here if it helps.
You only have an instance of AmbientLight in your scene which is no shadow-casting light.
3D objects can only receive shadow if they set Object3D.receiveShadow to true and if the material is not unlit. Meaning MeshBasicMaterial would not work as the ground's material.
You have to globally enable shadows via: renderer.shadowMap.enabled = true;
I suggest you have a closer look to the shadow setup of this official example.
Based on your code, you're trying to add a shadow on top of Mapbox.
For that, apart from the suggestions from #Mugen87, you'll need to create a surface to receive the shadow, and place it exactly below the model you're loading, considering also the size of the object you're loading to avoid the shadow goes out of the plane surface... and then you'll get this.
Relevant code in this fiddle I have created. I slightly changed the light and I added a light helper for clarity.
var customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function(map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(0, 70, 100);
let d = 1000;
let r = 2;
let mapSize = 8192;
dirLight.castShadow = true;
dirLight.shadow.radius = r;
dirLight.shadow.mapSize.width = mapSize;
dirLight.shadow.mapSize.height = mapSize;
dirLight.shadow.camera.top = dirLight.shadow.camera.right = d;
dirLight.shadow.camera.bottom = dirLight.shadow.camera.left = -d;
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 400000000;
//dirLight.shadow.camera.visible = true;
this.scene.add(dirLight);
this.scene.add(new THREE.DirectionalLightHelper(dirLight, 10));
// use the three.js GLTF loader to add the 3D model to the three.js scene
var loader = new THREE.GLTFLoader();
loader.load(
'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
function(gltf) {
gltf.scene.traverse(function(model) {
if (model.isMesh) {
model.castShadow = true;
}
});
this.scene.add(gltf.scene);
// we add the shadow plane automatically
const s = new THREE.Box3().setFromObject(gltf.scene).getSize(new THREE.Vector3(0, 0, 0));
const sizes = [s.x, s.y, s.z];
const planeSize = Math.max(...sizes) * 10;
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
const planeMat = new THREE.ShadowMaterial();
planeMat.opacity = 0.5;
let plane = new THREE.Mesh(planeGeo, planeMat);
plane.rotateX(-Math.PI / 2);
plane.receiveShadow = true;
this.scene.add(plane);
}.bind(this)
);
this.map = map;
// use the Mapbox GL JS map canvas for three.js
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
this.renderer.shadowMap.enabled = true;
},
render: function(gl, matrix) {
var rotationX = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(1, 0, 0),
modelTransform.rotateX
);
var rotationY = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 1, 0),
modelTransform.rotateY
);
var rotationZ = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 0, 1),
modelTransform.rotateZ
);
var m = new THREE.Matrix4().fromArray(matrix);
var l = new THREE.Matrix4()
.makeTranslation(
modelTransform.translateX,
modelTransform.translateY,
modelTransform.translateZ
)
.scale(
new THREE.Vector3(
modelTransform.scale,
-modelTransform.scale,
modelTransform.scale
)
)
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
this.camera.projectionMatrix = m.multiply(l);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
};
I have a 3D gltf model of a building which I'm trying to put on a Mapbox Map, but when using MeshPhongMaterial or any other variation it just won't change the color.
Everything else works except the color.
Here's my code:
var modelOrigin = [17.1956,44.7871];
var modelAltitude = 0;
var modelRotate = [Math.PI / 2, 0, 0];
var modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);
// transformation parameters to position, rotate and scale the 3D model onto the map
var modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[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: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
var THREE = window.THREE;
// configuration of the custom layer for a 3D model per the CustomLayerInterface
var customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
// create two three.js lights to illuminate the model
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
this.scene.add(directionalLight);
var directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
this.scene.add(directionalLight2);
// use the three.js GLTF loader to add the 3D model to the three.js scene
var loader = new THREE.GLTFLoader();
loader.load(
'Apartment Building_17_gltf.gltf',
function (gltf) {
this.scene.add(gltf.scene);
}.bind(this)
);
this.map = map;
// use the Mapbox GL JS map canvas for three.js
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
},
render: function (gl, matrix) {
var material = new THREE.MeshStandardMaterial({color: 0x5da8de});
material.needsUpdate = true;
material.colorsNeedUpdate=true;
var rotationX = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(1, 0, 0),
modelTransform.rotateX
);
var rotationY = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 1, 0),
modelTransform.rotateY
);
var rotationZ = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 0, 1),
modelTransform.rotateZ
);
var m = new THREE.Matrix4().fromArray(matrix);
var l = new THREE.Matrix4()
.makeTranslation(
modelTransform.translateX,
modelTransform.translateY,
modelTransform.translateZ
)
.scale(
new THREE.Vector3(
modelTransform.scale/9,
-modelTransform.scale/9,
modelTransform.scale/9
)
)
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
//var pink = new THREE.Color(0xfca4c5);
//gltf.scene.Color = pink;
//var boja =
//var vel = new THREE.Matrix4.scale(new THREE.Vector3(0.5,0.5,0.5));
this.camera.projectionMatrix = m.multiply(l);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
};
map.on('style.load', function () {
map.addLayer(customLayer, 'waterway-label');
If you want to change the material of a loaded glTF asset, do it like so:
gltf.scene.traverse( ( object ) => {
if ( object.isMesh ) object.material = newMaterial;
} );
I have scene with elements size over 500 units and i want to create mirror effect for them. to Reach descripted effect i used Reflector library from three.js webgl_mirror example.
I placed mirror on ground and most of meshes disappears or showing only small parts of surface when i set background hdri without its displayes normally. I builded other scene for tests and it looks like this unexpected effect begins when distance between mirror and obiect is over around 75 units (sometimes its less i dont know what its depends).
Image to preview on that effect
Is there any possibility that i could increase range of this clipping box size for that mirror? (i really want to avoid of scaling my actual created scene)
What i already tryed:
-changing my perspective camera far and near distances. - no effect
-manipulate paramets for clipBias and recursion or even increasing texture size. -no effect
-adding multiple lights around elements. -no effect
code that i used for experiment:
sceneSetup = () => {
//initialize
const width = this.mount.clientWidth;
const height = this.mount.clientHeight;
this.scene = new THREE.Scene();
let helperScene = this.scene;
this.camera = new THREE.PerspectiveCamera(60, width / height, 1, 500);
this.camera.position.z = 200;
this.controls = new OrbitControls(this.camera, document.body);
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(width, height);
this.mount.appendChild(this.renderer.domElement); //render to container (React staff)
///Load HDR map
new RGBELoader()
.setDataType(THREE.UnsignedByteType)
.load(HdrFile, function(texture) {
var envMap = pmremGenerator.fromEquirectangular(texture).texture;
helperScene.background = envMap; // comment to see issue
helperScene.environment = envMap;
texture.dispose();
pmremGenerator.dispose();
});
var pmremGenerator = new THREE.PMREMGenerator(this.renderer);
pmremGenerator.compileEquirectangularShader();
//create ground mirror
let geometry = new THREE.PlaneBufferGeometry(200, 200);
let groundMirror = new Reflector(geometry, {
clipBias: 0,
textureWidth: 1024,
textureHeight: 1024,
color: 0x889999,
recursion: 1
});
groundMirror .position.z = -20;
groundMirror .rotation.x = Math.PI * -0.5;
//change groundMirror .position.y to -104 and evrything looks fine;
groundMirror .position.y = -105;
this.scene.add(groundMirror );
};
addCustomSceneObjects = () => {
//create cube for reflect
const geometry = new THREE.BoxGeometry(50, 50, 50);
const material = new THREE.MeshPhongMaterial({
color: 0x156289,
emissive: 0x072534,
side: THREE.DoubleSide,
depthTest: true,
depthWrite: true
});
this.cube = new THREE.Mesh(geometry, material);
this.cube.position.y = 0;
this.scene.add(this.cube);
//radding lights
const lights = [];
lights[0] = new THREE.PointLight(0xffffff, 1, 0);
lights[1] = new THREE.PointLight(0xffffff, 1, 0);
lights[2] = new THREE.PointLight(0xffffff, 1, 0);
lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);
this.scene.add(lights[0]);
this.scene.add(lights[1]);
this.scene.add(lights[2]);
};
startAnimationLoop = () => {
//rotate cube
this.cube.rotation.x += 0.01;
this.cube.rotation.y += 0.01;
this.requestID = window.requestAnimationFrame(this.startAnimationLoop);
this.renderer.render(this.scene, this.camera);
};
I've been using the Add a 3D Model Mapbox GL example as a template to add 3D objects to my Mapbox map. It works but sometimes has issues with textures.
I'm loading an OBJ file and adding it to the scene. Here's what the model looks like in a plain THREE.js scene:
Here's a distilled version of the code to render that scene (see gist for full details):
async function addOBJ(path, mtlPath) {
const loader = new OBJLoader2();
const matLoader = new MTLLoader();
matLoader.load(mtlPath, mtlParseResult => {
const materials = MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult);
loader.addMaterials(materials);
loader.load(path, group => {
scene.add(group);
});
});
}
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.001, 1000 );
camera.position.set(86, 100, -5);
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
addOBJ('silo_p2.obj', 'silo_p2.mtl');
}
When I try to add the same model in my Mapbox map, the texture comes out oddly:
What's going on in my THREE.js + Mapbox scene? It looks like the texture has turned to noise.
Full source available here, but this is the gist:
function getSpriteMatrix(coord, sceneCenter) {
const rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), 90 * Math.PI / 180);
const s = sceneCenter.meterInMercatorCoordinateUnits();
return new THREE.Matrix4()
.makeTranslation(coord.x - sceneCenter.x, coord.y - sceneCenter.y, coord.z - sceneCenter.z)
.scale(new THREE.Vector3(s, -s, s))
.multiply(rotationX);
}
class SpriteCustomLayer {
type = 'custom';
renderingMode = '3d';
constructor(id) {
this.id = id;
this.model = loadObj('silo_p2.obj', 'silo_p2.mtl');
}
async onAdd(map, gl) {
this.camera = new THREE.Camera();
const centerLngLat = map.getCenter();
this.center = MercatorCoordinate.fromLngLat(centerLngLat, 0);
const {x, y, z} = this.center;
this.cameraTransform = new THREE.Matrix4().makeTranslation(x, y, z);
this.map = map;
this.scene = this.makeScene();
// use the Mapbox GL JS map canvas for three.js
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true,
});
// From https://threejs.org/docs/#examples/en/loaders/GLTFLoader
this.renderer.gammaOutput = true;
this.renderer.gammaFactor = 2.2;
this.renderer.autoClear = false;
this.addModel();
}
makeScene() {
const scene = new THREE.Scene();
scene.add(new THREE.AmbientLight( 0xffffff, 0.25 ));
for (const y of [-70, 70]) {
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, y, 100).normalize();
scene.add(directionalLight);
}
return scene;
}
async addModel() {
const model = await this.model;
flipSides(model);
const scene = model.clone();
const matrix = getSpriteMatrix(
MercatorCoordinate.fromLngLat([-74.0445, 40.6892], 0),
this.center,
);
scene.applyMatrix(matrix);
this.scene.add(scene);
}
render(gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4()
.fromArray(matrix)
.multiply(this.cameraTransform);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
}
map.on('load', () => {
const layer = new SpriteCustomLayer('statue');
map.addLayer(layer);
});
How can I get the texture to look right on this Object in the Mapbox view? I'm using THREE.js r109 and Mapbox GL JS v1.4.0. Because of where these models are coming from, using a different format like GLTF isn't an option for me.
I create a closed spline and extrude geometry with rectangle shape to create a track for my game. I use spline to calculate the position of my ship, but when I check, the spline does not match completely with the geometry. Is there something wrong?
shape used to extrude example
extrude geometry and spline not match
Here is my code:
var genTrack = function (lanes, controlPoints, material, isClose) {
//extrude setting
var extrudeSettings = {
bevelEnabled: true,
bevelSegments: 2,
steps: 200,
material: 1,
extrudeMaterial: 1
};
//track shape
var trackShape = new THREE.Shape([
new THREE.Vector2(-config.laneHeight/2, config.laneWidth/2),
new THREE.Vector2(-config.laneHeight/2, - config.laneWidth/2),
new THREE.Vector2(config.laneHeight/2, - config.laneWidth/2),
new THREE.Vector2(config.laneHeight/2, config.laneWidth/2),
]);
//spline
if (isClose)
var spline = new THREE.ClosedSplineCurve3(controlPoints);
else
var spline = new THREE.SplineCurve3(controlPoints);
utils.public('spline', spline);
//set path
extrudeSettings.extrudePath = spline;
//frenet
var frenet = new THREE.TubeGeometry.FrenetFrames(spline, 200, false)
extrudeSettings.frames = frenet;
utils.public('extrudeSettings', extrudeSettings);
//create geometry
var geometry = new THREE.ExtrudeGeometry(trackShape, extrudeSettings);
var splineGeo = new THREE.TubeGeometry(spline, 200, 1, 4, true, false);
utils.public('frenet', frenet);
var angle = 0;
var track = new THREE.Object3D();
track.matrixAutoUpdate = false;
track.receiveShadow = true;
for (var i=0; i< lanes; i++){
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(i * (config.laneSpan + config.laneWidth), -30, 0);
//mesh.rotation.set(0, 0,angle);
//angle += Math.PI/180 * 20;
track.add(mesh);
}
track.scale.set(1.5, 1.5, 1.5);
var splineMesh = new THREE.Mesh(splineGeo, new THREE.MeshBasicMaterial({color: 0xffffff}));
return {
'geometry': geometry,
'mesh': track,
'spline': spline,
'frenet': frenet,
'splineMesh': splineMesh,
'controlPoints' : controlPoints
};
}
I believe your problem is with this line:
mesh.position.set(i * (config.laneSpan + config.laneWidth), -30, 0);
I think what you want is something more like:
mesh.position.set(i * config.laneWidth / lanes, config.laneHeight / 2, 0);
I haven't tested it, but I think you need something like this.