I'm trying to make skybox assigned to player camera.
When camera moves(also skybox moves with it), texture get stretched.
How to get rid of this?
Code:
var textureCube = THREE.ImageUtils.loadTextureCube( urls );
textureCube.format = THREE.RGBFormat;
var shader = THREE.ShaderUtils.lib[ "cube" ];
shader.uniforms[ "tCube" ].value = textureCube;
cubematerial = new THREE.ShaderMaterial({
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: shader.uniforms,
depthWrite: false,
side: THREE.BackSide
});
skyBox = new THREE.Mesh(new THREE.CubeGeometry(1000,1000,1000), cubematerial);
camera.add(skyBox);
So, after digging into Three.js examples, I found a way how to do this. http://learningthreejs.com/blog/2011/08/15/lets-do-a-sky/ is outdated. A way used in examples is to add skybox into second scene with fixed camera, and render both scenes. Look at webgl_materials_cars.html example.
Also because I use 3rd person camera assigned to character, I must get world rotation from character camera to skybox camera. This can be done on render with:
function render(){
<...>
skyboxCamera.rotation.setEulerFromRotationMatrix( new THREE.Matrix4().extractRotation( camera.matrixWorld ), skyboxCamera.eulerOrder );
renderer.render(skyboxScene, skyboxCamera);
renderer.render(scene, camera);
<...>
}
I know it's a closed question but I want to offer an alternative that does not require an additional scene, for future seekers:
start by reading and following this tutorial: http://learningthreejs.com/blog/2011/08/15/lets-do-a-sky/
now create the following shader (I added it to three.js ShaderLib, but if you don't want to temper with three' source code add it outside):
'skybox': {
uniforms: { "tCube": { type: "t", value: null },
"tFlip": { type: "f", value: -1 } },
vertexShader: [
"varying vec3 vWorldPosition;",
THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ],
"void main() {",
" vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
" vWorldPosition = worldPosition.xyz;",
" gl_Position = projectionMatrix * modelViewMatrix * vec4( position + cameraPosition, 1.0 );",
THREE.ShaderChunk[ "logdepthbuf_vertex" ],
"}"
].join("\n"),
fragmentShader: [
"uniform samplerCube tCube;",
"uniform float tFlip;",
"varying vec3 vWorldPosition;",
THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ],
"void main() {",
" gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );",
THREE.ShaderChunk[ "logdepthbuf_fragment" ],
"}"
].join("\n")
},
create your skybox like this:
// urls is a list of textures to use
var cubemap = THREE.ImageUtils.loadTextureCube(urls);
cubemap.format = THREE.RGBFormat;
var shader = THREE.ShaderLib['skybox']; // init the skybox shader we created above
shader.uniforms['tCube'].value = cubemap; // apply textures to shader
// create shader material
var skyBoxMaterial = new THREE.ShaderMaterial( {
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: shader.uniforms,
depthWrite: false,
side: THREE.BackSide
});
// create skybox mesh
var skybox = new THREE.Mesh(
new THREE.CubeGeometry(1000, 1000, 1000),
skyBoxMaterial
);
// THIS IS IMPORTANT! or the skybox will get culled after you move the camera too far..
skybox.frustumCulled = false;
Related
I'm working with THREE.js points and sometimes I need them to have different per point color. Sometimes, I'm also modifying their alpha value so I had to write my own shader programs.
In JavaScript I have the following code:
let materials;
if (pointCloudData.colors !== undefined) {
geometry.colors = pointCloudData.colors.map(hexColor => new THREE.Color(hexColor));
// If the point cloud has color for each point...
materials = new THREE.ShaderMaterial({
vertexColors: THREE.VertexColors,
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true,
});
} else {
// Set color for the whole cloud
materials = new THREE.ShaderMaterial({
uniforms: {
unicolor: { value: pointCloudData.color },
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true,
});
}
const pointCloud = new THREE.Points(geometry, materials);
I am basically setting the mesh color to a uniform value unless I have defined per point colors - then I set vertexColors to the geometry. I also checked the values being stored in the geometry.colors and they are correct RGB values in range [0,1].
My Vertex Shader code:
attribute float size;
attribute float alpha;
varying float vAlpha;
varying vec3 vColor;
void main() {
vAlpha = alpha;
#ifdef USE_COLOR
vColor = color;
#endif
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 300.0 / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
}
And my Fragment shader code:
uniform vec3 unicolor;
varying vec3 vColor;
varying float vAlpha;
void main() {
#ifdef USE_COLOR
gl_FragColor = vec4(vColor, vAlpha);
#else
gl_FragColor = vec4(unicolor, vAlpha);
#endif
}
Again, I am checking if the vertexColor is set and then passing it to the Fragment Shader which then sets the per point.
For some reason, the vertices are all white when setting the color per point (screenshot: The white pixels should be green/red). I'm far from advanced user in WebGL and any help would be appreciated. Am I doing something wrong that I'm not aware of?
You are creating a custom ShaderMaterial and using this pattern in your fragment shader:
#ifdef USE_COLOR
vColor = color;
#endif
Consequently, you need to specify the material.defines like so:
var defines = {};
defines[ "USE_COLOR" ] = "";
// points material
var shaderMaterial = new THREE.ShaderMaterial( {
defines: defines,
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
transparent: true
} );
You do not need to set vertexColors: THREE.VertexColors. That is just a flag used by built-in materials to alert the renderer to set the defines for you.
three.js r.85
OK, I think I figured it out since it's working now.
I had to set the colors as geometry attributes:
const colors = new Float32Array(n * 3);
for (let i = 0; i < n; i += 1) {
new THREE.Color(pointCloudData.colors[i]).toArray(colors, i * 3);
}
geometry.addAttribute('colors', new THREE.BufferAttribute(colors, 1));
I also used the suggestion provided by WestLangley and removed the vertexColors: THREE.VertexColors, part from the Material definition and set the define as well:
materials = new THREE.ShaderMaterial({
defines: {
USE_COLOR: '',
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true,
});
Then in my Vertex shader I added:
attributes vec3 colors;
to get the colors passed from the JavaScript. The rest is the same, I just passed the colors to the fragment shader using the same code as in the posted question above.
I am trying to create an earth with three js like this example which is an upgrade from this one. The problem i have is that even thought in the code i add to the scene first the sky, after the earth and at the end the atmosphere the render seams not to understand this order a respondes with this
Now when i zoom in and get near the earth object the render is working correct giving this responce.
The problem as you can see is also with the THREE.EdgesHelper when in long zoom level it gets rendered in some parts even thought it is behind the earth, but in short zoom level it works perfect. Any ideas how to overcome this? This is the all the code to create the spheres:
function earthView(){
if (!scene){
main();//here i create the controls,camera,scene,renderer etc
}
// create the geometry sphere stars
var geometry = new THREE.SphereGeometry(6371000000, 36, 36)
// create the material, using a texture of startfield
var material = new THREE.MeshBasicMaterial()
material.map = THREE.ImageUtils.loadTexture('images/earthView/ESO_-_Milky_Way.jpg')
material.side = THREE.BackSide
// create the mesh based on geometry and material
var mesh = new THREE.Mesh(geometry, material)
mesh.position.set(0,0,-6371000)
scene.add(mesh)
//earth
var geometry = new THREE.SphereGeometry(6371000, 36, 36)
var material = new THREE.MeshPhongMaterial()
var earthMesh = new THREE.Mesh(geometry, material)
//earthMesh.position.set(0,-6371000,0)
//earthMesh.rotation.set(0,-Math.PI/2,0)
helper = new THREE.EdgesHelper( earthMesh );
helper.material.color.set( 0xffffff );
material.map = THREE.ImageUtils.loadTexture('images/earthView/earthmap1k.jpg')
material.bumpMap = THREE.ImageUtils.loadTexture('images/earthView/earthbump1k.jpg')
material.bumpScale = 100
material.specularMap = THREE.ImageUtils.loadTexture('images/earthView/earthspec1k.jpg')
scene.add(earthMesh);
scene.add( helper );
//atmosphere
var geometry = new THREE.SphereGeometry(7365000, 36, 36)
var material = new createAtmosphereMaterial()
material.uniforms.glowColor.value.set(0x00b3ff)
material.uniforms.coeficient.value = 0.1
material.uniforms.power.value = 2.0
//material.side = THREE.BackSide
var earthAtmo = new THREE.Mesh(geometry, material)
//earthAtmo.position.set(0,0,-6371000)
scene.add(earthAtmo);
/**
* from http://stemkoski.blogspot.fr/2013/07/shaders-in-threejs-glow-and- halo.html
* #return {[type]} [description]
*/
function createAtmosphereMaterial(){
var vertexShader = [
'varying vec3 vNormal;',
'void main(){',
' // compute intensity',
' vNormal = normalize( normalMatrix * normal );',
' // set gl_Position',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}',
].join('\n')
var fragmentShader = [
'uniform float coeficient;',
'uniform float power;',
'uniform vec3 glowColor;',
'varying vec3 vNormal;',
'void main(){',
' float intensity = pow( coeficient - dot(vNormal, vec3(0.0, 0.0, 1.0)), power );',
' gl_FragColor = vec4( glowColor * intensity, 1.0 );',
'}',
].join('\n')
// create custom material from the shader code above
// that is within specially labeled script tags
var material = new THREE.ShaderMaterial({
uniforms: {
coeficient : {
type : "f",
value : 1.0
},
power : {
type : "f",
value : 2
},
glowColor : {
type : "c",
value : new THREE.Color('blue')
},
},
vertexShader : vertexShader,
fragmentShader : fragmentShader,
side : THREE.FrontSide,
blending : THREE.AdditiveBlending,
transparent : true,
depthWrite : false,
});
return material
}
}
I use renderer.sortObjects = false when i define the renderer so the objects get rendered by the order they are added in the scene.
Brief summary: get the helper and the atmosphere rendered like pic 2 in all zoom levels.
Update Hint : 1.might this be a problem of the graphic card?2.might the long distance i am using in pixels be the problem?
This question has already been answered at this post.So setting the logarithmicDepthBuffer: true did the job!
I could not load texture to my shader material, only what I see are just black dot. This is my shader.js
THREE.ShaderLib['cloud'] = {
uniforms: {
texture: { type: "t", value: THREE.ImageUtils.loadTexture("img/cloud.png") }
},
vertexShader: [
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"gl_PointSize = 8.0;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
"}",
].join("\n"),
fragmentShader: [
"varying vec2 vUv;",
"uniform sampler2D texture;",
"void main() {",
"gl_FragColor = texture2D(texture, vUv);",
"}",
].join("\n")
};
and this is how I'm trying to load and use it:
var cloudShader = THREE.ShaderLib["cloud"];
var uniforms = THREE.UniformsUtils.clone(cloudShader.uniforms);
var texture = THREE.ImageUtils.loadTexture("img/cloud.png", undefined, function () {
uniforms.texture.value = texture;
texture.needsUpdate = true;
var _material = new THREE.ShaderMaterial({
fragmentShader: cloudShader.fragmentShader,
vertexShader: cloudShader.vertexShader,
uniforms: uniforms
});
_material.uniforms.texture.value.needsUpdate = true;
var _geometry = new THREE.Geometry();
_geometry.vertices.push(new THREE.Vector3(0, 0, 0));
var _mesh = new THREE.Points(_geometry, _material);
scene.add(_mesh);
});
As You can see I'm trying to set update texture value two times, before and after material is created. It's really simpy example but I have no idea why it is not working in the way that I'm using it. There is no errors in debug console.
I'm using THREE.Points class, because I'm using it to generate clouds as a particle groups.
Thanks in advance for any help.
So, after some time I have found a solution for this specific problem. I need to move my uniforms from shader.js directly to place where i'm creating shader material. This is enough to fix problem with my texture, but still I don't know why previous code doesn't work. Working shader material looks like this:
var _material = new THREE.ShaderMaterial({
fragmentShader: cloudShader.fragmentShader,
vertexShader: cloudShader.vertexShader,
uniforms: uniforms: {
texture: { type: "t", value: THREE.ImageUtils.loadTexture("img/cloud.png") }
},
});
I had to re-write my question because I was asking the wrong thing before.
I meant to ask, how do you update a uniform in three.js?
This seems to work:
yourMesh.material.uniforms.yourUniform.value = whatever;
There are 2 Ways:
You can update uniform in Shader Material itself
OR
you can use mesh.material to access the ShaderMaterialand then update the uniform
Example of both cases:
var delta = 0
var customUniforms = {
delta: { value: 0 },
u_time: { value: Date.now() }
};
// shader material with custom Uniform
shaderMaterial = new THREE.ShaderMaterial({
uniforms: customUniforms,
vertexShader: document.getElementById("vertexShader2").textContent,
fragmentShader: document.getElementById("fragmentShader2").textContent
});
// test Mesh object for shader
var geometry = new THREE.BoxBufferGeometry(10, 10, 10, 10, 10, 10);
shaderMesh = new THREE.Mesh(geometry, shaderMaterial);
this.scene.add(shaderMesh);
In animate loop
animate = () => {
delta += 0.1;
// Update uniform in Shader Material
shaderMaterial.uniforms.delta.value = 0.5 + Math.sin(delta) * 0.0005;
// Update uniform from Mesh itself
shaderMesh.material.uniforms.u_time.value = delta;
}
https://codesandbox.io/s/autumn-http-e9wk5
Complete Example
<body>
<div id="container"></div>
<script src="js/three.min.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
gl_FragColor=vec4(st.x,st.y,0.0,1.0);
}
</script>
<script>
var container;
var camera, scene, renderer;
var uniforms;
init();
animate();
function init() {
container = document.getElementById( 'container' );
camera = new THREE.Camera();
camera.position.z = 1;
scene = new THREE.Scene();
var geometry = new THREE.PlaneBufferGeometry( 2, 2 );
uniforms = {
u_time: { type: "f", value: 1.0 },
u_resolution: { type: "v2", value: new THREE.Vector2() },
u_mouse: { type: "v2", value: new THREE.Vector2() }
};
var material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
container.appendChild( renderer.domElement );
onWindowResize();
window.addEventListener( 'resize', onWindowResize, false );
document.onmousemove = function(e){
uniforms.u_mouse.value.x = e.pageX
uniforms.u_mouse.value.y = e.pageY
}
}
function onWindowResize( event ) {
renderer.setSize( window.innerWidth, window.innerHeight );
uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
uniforms.u_time.value += 0.05;
renderer.render( scene, camera );
}
</script>
</body>
Little example updating the shader uniform.
/* Vertex Shader */
<script type="x-shader/x-fragment" id="myShader">
uniform float myuniform;
uniform sampler2D myTexture;
varying vec2 vUV;
varying vec2 ver;
ver = uv *vec2( myuniform, myuniform); // this will be updated on mouse move
gl_Position = projectionMatrix *modelViewMatrix * vec4(position,1.0);
</script>
/* Set uniform */
var myUniform;
var myTexture = new THREE.ImageUtils.loadTexture( './data/textures/theTexture.jpg' );
_uniforms = {
myUniform: { type: "f", value: myUniform },
myTexture: { type: "t", value: myTexture },
};
customMaterial = new THREE.ShaderMaterial(
{
uniforms: _uniforms,
vertexShader: document.getElementById( 'myShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
wireframe: false,
side: THREE.FrontSide
} );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
function onDocumentMouseMove(ev) {
uniforms.myUniform.value += 0.01; // Updates this new value
uniforms.myUniform.needsUpdate = true;
}
...
This seemed like a good way to do it for my use case:
const mat = new THREE.ShaderMaterial({
uniforms: {
customVec3Uniform: { value: new THREE.Vector3(1,1,1) }
},
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent,
});
As for updating during runtime (citation):
mat.uniforms.customVec3Uniform.value = new THREE.Vector3(2,2,2);
// mat.needsUpdate = true; // my example works without 'needsUpdate'
For those not using THREE.ShaderMaterial
If for example you are modifying a material beyond just THREE.ShaderMaterial, say THREE.MeshStandardMaterial, via edits to its fragment shader, you will need to do things somewhat differently.
In order to modify this shader's uniforms, WITHOUT requiring recompilation, you will need to store a reference to that shader within the onBeforeCompile callback, then access those uniforms via that stored reference's uniforms.
Typescript class:
First plug into THREE.Material's onBeforeCompile hook transferring the values stored somewhere (in my case a uniforms dictionary created in the constructor) into the shader's uniforms. This is important because shader compilation takes place prior to first usage. Store a reference to the shader in your class for access. Do any of your other work to the fragment shader using these uniforms as you see fit.
private _uniforms: { [uniform: string]: ShaderUniform } = {};
private _shader?:THREE.Shader;
this._material.onBeforeCompile = (shader) => {
let prepend = "";
//transfer any changes occurring prior to compilation,
//and also prepend these shaders to the fragment shader
Object.entries(this._uniforms).forEach(([key, info]) => {
prepend += `uniform ${info.type} ${key};\n`
shader.uniforms[key] = {value: info.value};
});
//prepend these shaders, along with any other work to the
//fragment shader via basic string substitutions
shader.fragmentShader = prepend + shader.fragmentShader;
//store a reference to the shader
this._shader = shader;
}
Get or set the property from the stored uniforms if not compiled yet, otherwise use the shader's own uniform value, which only exists after compilation.
public getUniform(name:string) : any {
return this._shader ? this._shader.uniforms[name].value : this._uniforms[name].value;
}
public setUniform(name:string, value:any) {
if (this._shader) {
return this._shader.uniforms[name].value = value
} else {
this._uniforms[name].value = value;
}
}
I am trying to use a custom shader with Three.js. I tried to do it like the many examples, but it doesn't work. My code is:
var vertex = "void main(){vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );gl_Position = projectionMatrix * mvPosition;}";
var fragment = "precision highp float;void main(void){gl_FragColor = vec4(0.0,1.0,0.0,1.0);}";
material = new THREE.ShaderMaterial({
vertexShader: vertex,
fragmentShader: fragment
});
var mesh = new THREE.Mesh(geometry,material);
…and everything is blank. But if I use this material :
material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
…everything works perfectly. What's wrong?
I found the problem: I had to use:
renderer = new THREE.WebGLRenderer();
instead of :
renderer = new THREE.CanvasRenderer();