I'm having troubles enabling scene Fog in THREE.ShaderMaterial. Currently the fog only affects other geometries, but the Skydome that is created using THREE.ShaderMaterial, is unaffected by the fog.
There appears to be a boolean fog parameter in ShaderMaterial, which apparently should be set to true to use scene fog. Using it results in uniforms.fogColor is undefined errors however. The error happens at WebGLRenderer function refreshUniformsFog.
Is it a bug or am I using the parameter wrong?
Test case based on webgl_materials_lightmap.html example here: http://jsfiddle.net/HXhb4/
If you set fog to true in line 62 and run the test, you get the errors. What I would like to happen is the skydome to be affected by the fog like a model or skydome created with normal MeshPhongMaterial.
If you want to use fog with your custom ShaderMaterial, you need to be sure to specify the required fog uniforms. For example,
var uniforms = {
topColor: { type: "c", value: new THREE.Color( 0x0077ff ) },
bottomColor: { type: "c", value: new THREE.Color( 0xffffff ) },
offset: { type: "f", value: 33 },
exponent: { type: "f", value: 0.6 },
fogColor: { type: "c", value: scene.fog.color },
fogNear: { type: "f", value: scene.fog.near },
fogFar: { type: "f", value: scene.fog.far }
}
var skyMat = new THREE.ShaderMaterial( {
vertexShader: vertexShader,
fragmentShader: fragmentShader,
uniforms: uniforms,
side: THREE.BackSide,
fog: true
} );
Also specify fogDensity if you decide to use it. You will also have to incorporate the fog logic into your shader.
three.js r.59
In the accepted answer WestLangley mentioned:
You will also have to incorporate the fog logic into your shader.
I figured out how to do that by reading the example NormalDisplacementShader.js. Here are the steps:
Write your shader in an external .js file
Add the UniformsLib
Add the ShaderChunks
In the fragment shader, declare vec3 outgoingLight
Use outgoingLight in your output gl_FragColor
Example below:
THREE.YourCustomShader = {
uniforms: THREE.UniformsUtils.merge( [
THREE.UniformsLib[ "fog" ],
{
"someCustomUniform" : { type: 'f', value: 1.0 }
}
] ),
fragmentShader: [
"varying float someCustomVarying;",
THREE.ShaderChunk[ "common" ],
THREE.ShaderChunk[ "fog_pars_fragment" ],
"void main() {",
"vec3 outgoingLight = vec3( 0.0 );",
THREE.ShaderChunk[ "fog_fragment" ],
"gl_FragColor = vec4(outgoingLight, 1.0);",
"}"
].join("\n"),
vertexShader: [
"uniform float someCustomUniform;",
"varying float someCustomVarying;",
"void main() {",
"someCustomVarying = 1.0 * someCustomUniform;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0 );",
"}"
].join("\n")
};
Related
I have one problem here)
I decided to use my shader for (scene.overrideMaterial) and I need to somehow get the map texture.
MaterialsShader = {
uniforms: {
time: { type: 'f', value: 0.0 },
},
vertexShader:
"varying vec2 vUv; \n\
void main(){\n\
vUv = uv;\n\
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0 );\n\
gl_Position = projectionMatrix * mvPosition;\n\
}",
fragmentShader: [
'varying vec2 vUv;',
'uniform float opacity;',
'#ifdef USE_MAP',
'uniform sampler2D map;',
'#endif',
'void main(){',
'vec3 color = vec3(1.0,0.0,0.0) * opacity;',
'#ifdef USE_MAP',
'vec4 mapTexel = texture2D( map, vUv.xy );',
'gl_FragColor = mapTexel;',
'#endif',
//'gl_FragColor.rgb = color;',
'}'].join("\n")
}
But for some reason the texture itself does not exist, I get black material.
var uniforms = THREE.UniformsUtils.clone( MaterialsShader.uniforms );
uniforms = THREE.UniformsUtils.merge( [uniforms, THREE.UniformsLib['common'],THREE.UniformsLib['lights']] );
var material = new THREE.ShaderMaterial({
uniforms : uniforms,
vertexShader : MaterialsShader.vertexShader,
fragmentShader : MaterialsShader.fragmentShader,
});
How can I get a (map) texture?
1) You need to actually define your map uniform...
MaterialsShader = {
uniforms: {
time: { type: 'f', value: 0.0 },
map: { type: 't', value: yourLoadedTexture },
},
2) You need to define USE_MAP in order to enter that code segment of your shader. Either add this to the top of your shader code:
#define USE_MAP true
Or you can set a defines property for your shader:
var material = new THREE.ShaderMaterial({
uniforms : uniforms,
vertexShader : MaterialsShader.vertexShader,
fragmentShader : MaterialsShader.fragmentShader,
map : yourLoadedTexture,
defines : {
USE_MAP: true
}
});
These have the same effect.
I add a json model with glow effect into the scene.
As follows:
I try to rotate the json model automatically.
However, it looks weird when it is rotating.
The glow effect of the model does not work.
I assume that the position of the json model does not be changed when this model is rotating. As the result, the viewVector.value of the ShaderMaterial is constant when this model is rotating(I do not change position of the camera).
if(jsonMesh){
jsonMesh.rotation.y += 0.1;
jsonMesh.material.uniforms.viewVector.value =
new THREE.Vector3().subVectors( camera.position, jsonMesh.position );
}
This is the Three.ShaderMaterial.
VertexShader and FragmentShader
<script id="vertexShader" type="x-shader/x-vertex">
uniform vec3 viewVector;
uniform float c;
uniform float p;
varying float intensity;
void main()
{
vec3 vNormal = normalize( normalMatrix * normal );
vec3 vNormel = normalize( normalMatrix * viewVector );
intensity = pow( c - dot(vNormal, vNormel), p );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform vec3 glowColor;
varying float intensity;
void main()
{
vec3 glow = glowColor * intensity;
gl_FragColor = vec4( glow, 1.0 );
}
</script>
Three.ShaderMaterial.
var customMaterial = new THREE.ShaderMaterial(
{
uniforms:
{
"c": { type: "f", value: 1.0 },
"p": { type: "f", value: 1.4 },
glowColor: { type: "c", value: new THREE.Color(0xffff00) },
viewVector: { type: "v3", value: camera.position }
},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
side: THREE.FrontSide,
blending: THREE.AdditiveBlending,
transparent: true
}
);
How should I modify the code in this case?
Here is the Demo and source code.
You can use built in three.js functions for this. Instead of using the camera position, I chose to show you how to set a light source position in the world. That way you can match the light source on your custom shader to any light sources you plan to add later to your 3d world. Feel free to change the worldLightPoint value to camera.position instead of new THREE.Vector3(100,100,100). and in that case the effect will remain constant with the camera position.
var v = new THREE.Vector3();
//var worldLightPoint = camera.position;
var worldLightPoint = new THREE.Vector3(100,100,100);
function update()
{
controls.update();
stats.update();
if(jsonMesh){
jsonMesh.rotation.y += 0.1;
jsonMesh.material.uniforms.viewVector.value = jsonMesh.worldToLocal(v.copy(worldLightPoint));
}
}
How might I make a gradient color for the sky without using images?
This is what I have so far (I know why it doesn't work - it is only reference for what I am trying to do):
<a-sky color="linear-gradient(red, yellow)"></a-sky>
ThreeJS solutions welcome as well, but I would need help integrating them into an A-Frame scene.
I ended up solving it with help from the given examples. Thank you Marko, Pablieros, ngokevin, and Mr Doob!
I ended up creating a custom shader and a primitive element to go along with it:
<a-gradient-sky material="topColor:0 1 0; bottomColor:1 0 0;"></a-gradient-sky>
AFRAME.registerShader('gradient', {
schema: {
topColor: {type: 'vec3', default: '1 0 0', is: 'uniform'},
bottomColor: {type: 'vec3', default: '0 0 1', is: 'uniform'},
offset: {type: 'float', default: '400', is: 'uniform'},
exponent: {type: 'float', default: '0.6', is: 'uniform'}
},
vertexShader: [
'varying vec3 vWorldPosition;',
'void main() {',
'vec4 worldPosition = modelMatrix * vec4( position, 1.0 );',
'vWorldPosition = worldPosition.xyz;',
'gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0 );',
'}'
].join('\n'),
fragmentShader: [
'uniform vec3 bottomColor;',
'uniform vec3 topColor;',
'uniform float offset;',
'uniform float exponent;',
'varying vec3 vWorldPosition;',
'void main() {',
' float h = normalize( vWorldPosition + offset ).y;',
' gl_FragColor = vec4( mix( bottomColor, topColor, max( pow( max(h, 0.0 ), exponent ), 0.0 ) ), 1.0 );',
'}'
].join('\n')
});
AFRAME.registerPrimitive('a-gradient-sky', {
defaultComponents: {
geometry: {
primitive: 'sphere',
radius: 5000,
segmentsWidth: 64,
segmentsHeight: 20
},
material: {
shader: 'gradient'
},
scale: '-1 1 1'
},
mappings: {
topColor: 'material.topColor',
bottomColor: 'material.bottomColor',
offset: 'material.offset',
exponent: 'material.exponent'
}
});
You may want to Register a custom material/shader similar to #ngokevin 's aframe-sun-sky at ngokevin/kframe or use registerShader
Which would look something like following example from docs.
AFRAME.registerShader('hello-world-shader', {
schema: {
color: { type: 'vec3', default: '0.5 0.5 0.5', is: 'uniform' }
},
vertexShader: [
'void main() {',
' gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;',
'}'
].join('\n'),
fragmentShader: [
'uniform vec3 color;'
'void main() {'
' gl_FragColor = vec4(color, 1.0);',
'}'
].join('\n')
});
And just for notice you tile says Skybox by aframe primitive <a-sky> is geometry: { primitive: 'sphere' }
I am trying to create a custom ShaderMaterial reusing the ShaderChunks but seem to have a problem when setting the map parameter.
In my custom shader, when I load a texture and set the materials map and uniforms.map.value value to the texture it does not get the values from the texture in my shader, though it does seem to set the USE_MAP definition.
HERE is a fiddle
In the shader, it turns green if USE_MAP is defined, but it should also get the map value.
Am I missing something simple?
Below are the important bits :)
Set up the shader
var shaderUniforms = THREE.UniformsUtils.merge([THREE.UniformsLib[ "common" ]]);
var shaderVertex = [
//THREE.ShaderChunk[ "common" ],
THREE.ShaderChunk[ "uv_pars_vertex" ],
"void main() {",
THREE.ShaderChunk[ "begin_vertex" ],
THREE.ShaderChunk[ "project_vertex" ],
"}",
].join("\n");
var shaderFragment = [
THREE.ShaderChunk[ "common" ],
THREE.ShaderChunk[ "uv_pars_fragment" ],
THREE.ShaderChunk[ "map_pars_fragment" ],
"void main() {",
"vec4 diffuseColor = vec4(1.0, 0.0, 0.0, 1.0);",
"#ifdef USE_MAP",
' diffuseColor = vec4(0.0, 1.0, 0.0, 1.0);',
'#endif',
THREE.ShaderChunk[ "map_fragment" ],
"gl_FragColor = diffuseColor;",
"}",
].join("\n");
Create the material
material = new THREE.ShaderMaterial({
uniforms: shaderUniforms,
vertexShader: shaderVertex,
fragmentShader: shaderFragment
});
Add the texture
function loadTexture() {
var loader = new THREE.TextureLoader();
loader.load(dataurl, function(texture){
console.log('texture loaded ok');
// What to do here to add texture (map) and update ShaderMaterial?
// Do I have to set needsUpdate on the texture?
texture.needsUpdate = true;
// Do I have to set needsUpdate on the material?
material.needsUpdate = true;
// Seems I have to set both the uniform map value and the material map value...
material.uniforms.map.value = texture;
material.map = texture;
// But still does not show the texture, though USE_MAP does seem to get defined in the shader (color changes to green).
// Hmmmm
});
}
I'm currently customizing the base phong material using THREE.ShaderMaterial and rebuilding the material with most of THREE js's base chunks and a customized fragment shader chunk. The problem I'm having is with the #define's in many part of three.js and trying to find the proper way to set them.
In the actual program it goes like this
// Clone the uniforms
var uniforms = THREE.UniformsUtils.clone(shader['uniforms']);
// Set uniform values
uniforms["map"].value = texture;
uniforms["diffuse"].value = new THREE.Color( 0xff0000 );
uniforms["envMap"].value = envMapt;
uniforms["reflectivity"].value = 0.7;
// Create material using shader
var material = new THREE.ShaderMaterial( {
vertexShader: shader['vertexShader'],
fragmentShader: shader['fragmentShader'],
uniforms: uniforms,
lights: true,
//map: true, // These don't seem to do anything
//envMap: true // These don't seem to do anything
} );
With a custom shader built like this
fragmentShader: [
"#define USE_MAP",
//"#define USE_ENVMAP",
"uniform vec3 diffuse;",
"uniform float opacity;",
.......
"void main() {",
THREE.ShaderChunk[ "alphatest_fragment" ],
THREE.ShaderChunk[ "specularmap_fragment" ],
......
// NDJ - Using custom frag shader
//THREE.ShaderChunk[ "lights_phong_fragment" ],
CustomShaderChunk[ "lights_phong_fragment" ],
......
THREE.ShaderChunk[ "fog_fragment" ],
"}"
].join("\n")
By manually adding the required #defines at the start of the shaders I can get it to do what I want. However this doesn't seem like the proper way to set it and it isn't very flexible.
Something similar to this, But I only need the base definitions. I've tried looking through the api and examples to find how to set these but can't seem to get it to work.
Just do it exactly as you described. Here is a sample:
ph = new THREE.MeshPhongMaterial( { ambient: 0x000000,
color: 0x0020ff,
specular: 0x2040ff,
shininess: 30,
map: theMap,
side: THREE.DoubleSide } );
ph.defines = {waldo_waldo_three: '(dx+3)', wonke: 7};
if you're being prudent, be careful about overwriting any exsiting 'defines' object