How to create a soft light using three js - javascript

i am new to three js, i'm trying to create soft lighting for a gltf model using AmbientLight and DirectionalLight, but the color contrast comes out too sharp
what i'm trying to achieve
What i got
my code
const main = document.querySelector('#main');
let WIDTH = main.offsetWidth;
let HEIGHT = main.offsetHeight;
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(15, WIDTH / HEIGHT, 0.1, 1000);
camera.position.z = 5;
let renderer = new THREE.WebGLRenderer();
renderer.setClearColor('rgb(99, 115, 107)');
renderer.setSize(WIDTH, HEIGHT);
renderer.domElement.setAttribute("id", "Church3DObj");
main.insertBefore(renderer.domElement, main.firstChild);
let controls = new THREE.OrbitControls(camera, renderer.domElement);
const ambientLight = new THREE.AmbientLight('white', 1);
const mainLight = new THREE.DirectionalLight('white', .8);
mainLight.position.set(10, 10, 10);
scene.add(ambientLight)
scene.add(mainLight)
let loader = new THREE.GLTFLoader();
let obj = null;
loader.load('https://raw.githubusercontent.com/aaadraniki/projects/web-pages/assets/models/fender_br/scene.gltf', function(gltf) {
obj = gltf.scene;
scene.add(obj);
});
renderer.setAnimationLoop(_ => {
renderer.render(scene, camera);
})

You'll usually get the best lighting in three.js by using some kind of environment map. THREE.RoomEnvironment is one of the easier ways to do that, or you can load external .hdr or .exr files. Enabling some tone mapping (e.g. ACES Filmic) may also help, see renderer.toneMapping.

Related

Why doesn't my three.js code show the light effect?

Following the manual, I made a three.js example, but the light effect doesn't appear, why the light source doesn't appear without any error about the light source?
import * as THREE from "/assets/threejs/build/three.module.js"
class App {
// 생성 초기화
constructor(){
const divContainer = document.querySelector("#webgl-container");
this._divContainer = divContainer;
const renderer = new THREE.WebGLRenderer({antialias: true})
renderer.setPixelRatio(window.devicePixelRatio);
divContainer.appendChild(renderer.domElement);
this._renderer = renderer;
const scene = new THREE.Scene();
this._scene = scene;
this._setupCamera();
this._setupLight();
this._setupModel();
window.onresize = this.resize.bind(this);
this.resize();
requestAnimationFrame(this.render.bind(this));
}
// 카메라
_setupCamera() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
const camera = new THREE.PerspectiveCamera(
75,
width / height,
0.1,
100,
)
camera.position.z = 2
this._camera = camera;
}
// 광원
_setupLight(){
const color = 0xffffff;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
this._scene.add(light);
}
// 모델
_setupModel(){
const geometry = new THREE.BoxGeometry(1,1,1);
const material = new THREE.MeshBasicMaterial( { color: 0x44a88 } );
const cube = new THREE.Mesh( geometry, material );
this._scene.add(cube);
this._cube = cube;
}
// 창크기 번경
resize(){
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
this._camera.aspect = width / height;
this._camera.updateProjectionMatrix();
this._renderer.setSize(width, height);
}
// 랜더링
render(time){
this._renderer.render(this._scene, this._camera);
this.update(time);
requestAnimationFrame(this.render.bind(this));
}
// 업데이트
update(time){
time *= 0.001;
this._cube.rotation.x = time;
this._cube.rotation.y = time;
}
}
window.onload = function(){
new App();
}
The screenshot below is the result of my example code.
The screenshot below is the result of the manual.
I'm sorry for posting multiple questions with a low difficulty problem, but, I'm learning three.js for the first time. In vscode, there is no three.js code hint library, and it's hard. How can I express the light effect normally in my example code? Will there be? thanks for reading the problem
Do not use MeshBasicMaterial, but MeshStandardMaterial. MeshBasicMaterial for drawing geometries in a flat or wireframe way. MeshStandardMaterial is a physically based material that makes the surface interact with the light.

Trying to access function arguments from inner function

I'm using three.js and a script similar to OrbitControls as my controller. In my main.js file I have a THREE.Group() that is being passed to the controller as an argument. From there, I'm attempting to rotate the entire group.
Problem 1: once the group is passed to the controller, I can no longer access its properties without making a copy
Problem 2: a copy does not contain the entire THREE.Group(), rather only the first child
I've been working on this for hours now and I've tried about 50 different things including anything relevant on stackoverflow. I'm completely out of ideas on how to combat this problem.
main.js
let container;
let camera;
let controls;
let game;
let renderer;
let scene;
function init() {
container = document.querySelector('#scene-container');
scene = new THREE.Scene();
const fov = 35;
const aspect = container.clientWidth / container.clientHeight;
const near = 0.1;
const far = 100;
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
//***** This is the important line ******
controls = new THREE.ObjectControls(camera, container, game);
game = new THREE.Group();
scene.add(game);
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial();
var mesh1 = new THREE.Mesh(geometry, material);
game.add(mesh1);
var mesh2 = new THREE.Mesh(geometry, material);
mesh2.position.set(0,1,0);
game.add(mesh2);
renderer = new THREE.WebGLRenderer();
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
container.appendChild(renderer.domElement);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
}
init();
ObjectControls.js
THREE.ObjectControls = function (camera, domElement, objectToMove) {
mesh = objectToMove;
domElement.addEventListener('mousemove', mouseMove, false);
function mouseMove(e) {
//** objectToMove is undefined :( **
mesh.rotation.y += 3;
}
};
Expected result is that the entire THREE.Group() game will be rotated, but the result that I get is that only the first child of game is rotated, in this case mesh1.
controls = new THREE.ObjectControls(camera, container, game);
game = new THREE.Group();
There is an error in your code since you pass the undefined variable game to the ctor of ObjectControls. If you assign a new object to game one line later, ObjectControls does not have a reference to this variable.
The idea is to assign the group object to game first and then create ObjectControls. You essentially switch both lines.
three.js R105

Getting THREE.EventDispatcher is undefined when instantiating a THREE.OrbitControls class with THREE.js, THREE.OrbitControls and typescript

I'm trying to learn typescript with three.js and can't figure out why I'm getting this error. In the init function I'm creating a new THREE.OrbitControls controller. Using https://github.com/pinqy520/three-typescript-starter as setup.
Error recieved: Uncaught TypeError: Cannot read property 'prototype' of undefined
// three.
import * as THREE from 'three';
import * as ColladaLoader from 'three-collada-loader';
import * as OrbitControls from 'three-orbit-controls';
// Set the scene size
const WIDTH: number = window.innerWidth;
const HEIGHT: number = window.innerHeight;
// Set camera attributes
const VIEW_ANGLE: number = 45;
const ASPECT: number = WIDTH / HEIGHT;
const NEAR: number = 0.1;
const FAR: number = 10000;
// Create a WebGL renderer, camera
// and a scene
const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer();
const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
const scene: THREE.Scene = new THREE.Scene();
// Load a model into the scene
let monster: any;
const loader: ColladaLoader = new ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(
'/src/monster.dae',
(collada: THREE.ColladaModel): void => {
monster = collada.scene;
monster.scale.set(0.001, 0.001, 0.001);
monster.position.set(0, 0.1, 0);
scene.add(monster);
init();
animate()
});
const init = (): void => {
// Get the DOM element to attach to
const container = document.querySelector('#canvas');
// Set the position of the camera
// and add it to the scene
camera.position.set(0, 2, 10)
scene.add(camera);
//
const gridHelper: THREE.GridHelper = new THREE.GridHelper(10, 20);
scene.add(gridHelper);
//
const ambientLight: THREE.AmbientLight = new THREE.AmbientLight(0xcccccc)
scene.add(ambientLight);
const directionalLight: THREE.DirectionalLight = new THREE.DirectionalLight((0xffffff));
directionalLight.position.set(0, 1, -1).normalize;
scene.add(directionalLight);
// Start the renderer
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(WIDTH, HEIGHT);
// Attach the renderer-supplied
// DOM element.
container.appendChild(renderer.domElement);
// Create a controll class around
// the camera
debugger
let controls: THREE.OrbitControls = new OrbitControls(camera, renderer.domElement);
}
// Recursive function
let animate = (): void => {
requestAnimationFrame(animate);
render();
}
// Recursive rendering function
let render = (): void => {
renderer.render(scene, camera);
}
According to this
post on GITHUB
that syntax no longer works. So change OrbitControls like this:
THREE.EventDispatcher.apply( this );
There was a change recent versions of the threejs to the way you use the default exports of a module.
So, instead of using:
import THREE from 'three';
You should use:
import * as THREE from 'three';
I ended up using this:
https://www.npmjs.com/package/orbit-controls-es6
One slight difference to make note of:
Do new OrbitControls instead of new THREE.OrbitControls
import OrbitControls from 'orbit-controls-es6';
const controls = new OrbitControls(camera, renderer.domElement);

three.js skybox: obvious corners and lots of distortion

Today I've been experimenting with building my first ever skybox in three.js. I've read a lot of tutorials and the code I've ended up with is based on this one: http://learningthreejs.com/blog/2011/08/15/lets-do-a-sky/
I did make a few changes in order to allow for the images to load first, and to make it compatible with the version of three.js which I am using.
I've overcome a lot of small problems to get to the point I am currently at, but cannot find any answer to my current issue despite having searched quite hard. My problem is that despite using purpose-built skybox textures downloaded from the internet, it is glaringly obvious that my skybox is a cube with corners and edges. The textures appear heavily distorted and are not at all convincing.
Here is a screenshot of how my skybox looks:
And here is a link to the site from which I downloaded the images:
http://www.humus.name/index.php?page=Cubemap&item=Yokohama3
As you can see, in their preview it looks much better.
I've tried this with a few different downloaded textures and every time it is very obvious that you are looking at the inside of a cube.
Here's my code (I'm including all my code, not just the section which creates the skybox):
var scene;
var camera;
var renderer;
function createRenderer () {
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000, 1.0)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.shadowMapEnabled = true;
//renderer.shadowCameraNear = 0.5;
//renderer.shadowCameraFar = 500;
}
function createCamera () {
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth/window.innerHeight,
0.1, 1000
);
camera.position.x = 50;
camera.position.y = 30;
camera.position.z = 40;
camera.lookAt(scene.position);
}
function createPlane () {
var material = new THREE.MeshLambertMaterial({
color: 0xcccccc,
})
var geometry = new THREE.PlaneGeometry(40, 40)
var plane = new THREE.Mesh(geometry, material)
plane.receiveShadow = true;
plane.rotation.x = -Math.PI/2
plane.position.y = -6;
scene.add(plane)
}
function createLight () {
var spotLight = new THREE.DirectionalLight(0xffffff);
spotLight.position.set( 0, 50, 20 );
spotLight.shadowCameraVisible = true;
spotLight.shadowDarkness = 0.5
spotLight.shadowCameraNear = 0;
spotLight.shadowCameraFar = 100;
spotLight.shadowCameraLeft = -50;
spotLight.shadowCameraRight = 50;
spotLight.shadowCameraTop = 50;
spotLight.shadowCameraBottom = -50;
spotLight.castShadow = true;
scene.add(spotLight);
}
function createSkyboxAndSphere () {
var urlPrefix = "Yokohama3/";
var urls = [ urlPrefix + "posx.jpg", urlPrefix + "negx.jpg",
urlPrefix + "posy.jpg", urlPrefix + "negy.jpg",
urlPrefix + "posz.jpg", urlPrefix + "negz.jpg" ];
var textureCube = THREE.ImageUtils.loadTextureCube( urls , undefined, function () {;
var shader = THREE.ShaderLib["cube"];
var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
shader.uniforms['tCube'].value = textureCube; // textureCube has been init before
var material = new THREE.ShaderMaterial({
fragmentShader : shader.fragmentShader,
vertexShader : shader.vertexShader,
uniforms : shader.uniforms,
depthWrite : false,
side: THREE.BackSide,
});
var geometry = new THREE.BoxGeometry(100, 100, 100)
var skybox = new THREE.Mesh(geometry, material)
scene.add(skybox)
var material = new THREE.MeshPhongMaterial({
color: "red",
envMap: textureCube,
reflectivity: 0.3,
})
var geometry = new THREE.SphereGeometry(6, 30, 15)
var sphere = new THREE.Mesh(geometry, material)
sphere.castShadow = true;
sphere.receiveShadow = true;
scene.add(sphere)
});
}
function init () {
scene = new THREE.Scene();
createRenderer();
createCamera();
createLight();
createPlane ();
createSkyboxAndSphere ();
document.getElementById("container").appendChild(renderer.domElement)
render ()
}
function render () {
renderer.render(scene, camera)
requestAnimationFrame(render);
}
window.onload = function () {
init ();
}
I suspect I am fundamentally misunderstanding something about how cubemapping and skyboxes work - I am very new to this in particular and javascript in general and am aware of huge gaps in my knowledge.
My apologies if the answer to this is obvious and/or the question has been asked before, and a pre-emptive thanks for your help!
Your camera needs to be in the center of the skybox -- or at least near the center.
So either move your camera very close to the box center, or update the box position every frame to match the camera position.
Or make the skybox much bigger relative to the camera offset from the origin.
Or place the skybox in a separate scene and have two cameras and two render passes, as in this example.
three.js r.74

Three.js update scale object-size via view

i'm new in three.js and have a problem by scaling a object. I already looked at the documentation (https://github.com/mrdoob/three.js/wiki/Updates), but i don't really understand it at all.
My Problem: By changing a HTML-select-element the CubeGeometry should be scaled in the x-direction. That is already working BUT the "old" Cube do not disappear. So i have 2 Cubes. But i want only one Cube with the current size. I hope you can understand my problem ;-)
So here is my Code from the View:
$(document).on('change',".configurator > form select",function(event){
// update 3D-object - is that a nice way???
$.getScript("/javascripts/3D-animation.js.erb", function(){
// here comes the update
OBJECT3D.updateObject();
});
})
And here is my 3D-animation.js.erb:
var drawing_area;
var renderer;
var camera;
var obj;
var scene;
var OBJECT3D = {};
$(function() {
// get drawing_area
drawing_area = document.getElementById("canvas_wrapper");
// initialize renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(drawing_area.clientWidth, drawing_area.clientHeight);
renderer.setClearColor( 0xffffff, 1);
// add renderer to drawing_area
drawing_area.appendChild(renderer.domElement);
// initialize camera
camera = new THREE.PerspectiveCamera(45, drawing_area.clientWidth/drawing_area.clientHeight, 1, 100000);
camera.position.z = 1000;
camera.position.y = 100;
camera.position.x = 300;//-0.78;
// create texture
var texture = THREE.ImageUtils.loadTexture( "/images/materials/texture_1.jpg" );
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 1, 1 );
// create object
var obj_form = new THREE.CubeGeometry(250,250,250);
var obj_material = new THREE.MeshLambertMaterial( { map: texture,ambient: 0x999999 } );
OBJECT3D.obj = new THREE.Mesh(obj_form, obj_material);
// so what do i need here?
OBJECT3D.obj.geometry.dynamic = true;
// OBJECT3D.obj.geometry.__dirtyVertices = true;
// OBJECT3D.obj.geometry.__dirtyNormals = true;
// OBJECT3D.obj.geometry.verticesNeedUpdate = true;
// create scene
scene = new THREE.Scene();
scene.add(camera);
scene.add(OBJECT3D.obj);
// create lights
pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = 400;
pointLight.position.y = 200;
pointLight.position.z = 1300;
scene.add(pointLight);
ambientLight = new THREE.AmbientLight( 0xffffff);
scene.add( ambientLight );
requestAnimationFrame(render);
function render(){
requestAnimationFrame(render);
OBJECT3D.obj.rotation.y += 0.005;
OBJECT3D.obj.rotation.z += 0.005;
renderer.render(scene, camera);
};
// update object
OBJECT3D.updateObject = function () {
console.log("in update");
OBJECT3D.obj.scale.x = 2.5; // SCALE
OBJECT3D.obj.geometry.needsUpdate = true;
//render();
}
});
Sorry, if the Code is not the best one, but i'm really new in this stuff! :) hope you can help me!
Thanks!
I think it is because you are using $.getScript method. This will load your script each time again and create a new instance of OBJECT3D.
I propose that you make sure that your code in 3D-animation.js.erb will be included and called only once upon the load of a page (include it just like any other regular javascript file) and then call your update directly like this:
$(document).on('change',".configurator > form select", function(event) {
OBJECT3D.updateObject();
});
also, I believe you can drop following lines of code:
OBJECT3D.obj.geometry.needsUpdate = true;
and
OBJECT3D.obj.geometry.dynamic = true;

Categories