Three.js material texture and color - three.js

I trying to make a material for a car model with three.js where the base color of the car can be changed dynamically. This was easy enough by changing the color attribute of the MeshPhongMaterial that I am using.
I then needed to apply a texture over the top and thought this would be easy enough by adding an image to the map attribute of the material.
The outcome was not what I expected though, the map/texture image was also shaded the color set by the color attribute. I want it to basically overlay the base color.
Can anyone point me in the right direction?

Here is a complete script chunk. Insert immediately after reading three.js
<script>
THREE.ShaderChunk.map_fragment = [
"#ifdef USE_MAP",
"vec4 texelColor = texture2D( map, vUv ); /* NEWWW */",
"#ifdef GAMMA_INPUT",
"texelColor.xyz *= texelColor.xyz;",
"#endif",
"gl_FragColor.rgb = mix(gl_FragColor.rgb,texelColor.rgb,texelColor.a);",
"vec3 surfDiffuse = mix(diffuse,vec3(1,1,1),texelColor.a);",
"#else",
"vec3 surfDiffuse = diffuse;",
"#endif"].join('\n');
// now replace references to 'diffuse' with 'surfDiffuse'
THREE.ShaderChunk.lights_phong_fragment =
THREE.ShaderChunk.lights_phong_fragment.replace(/\bdiffuse\b/gm,'surfDiffuse')
THREE.ShaderLib.phong.fragmentShader = [
"uniform vec3 diffuse;",
"uniform float opacity;",
"uniform vec3 ambient;",
"uniform vec3 emissive;",
"uniform vec3 specular;",
"uniform float shininess;",
THREE.ShaderChunk[ "color_pars_fragment" ],
THREE.ShaderChunk[ "map_pars_fragment" ],
THREE.ShaderChunk[ "lightmap_pars_fragment" ],
THREE.ShaderChunk[ "envmap_pars_fragment" ],
THREE.ShaderChunk[ "fog_pars_fragment" ],
THREE.ShaderChunk[ "lights_phong_pars_fragment" ],
THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
THREE.ShaderChunk[ "bumpmap_pars_fragment" ],
THREE.ShaderChunk[ "normalmap_pars_fragment" ],
THREE.ShaderChunk[ "specularmap_pars_fragment" ],
"void main() {",
"gl_FragColor = vec4( vec3 ( 1.0 ), opacity );",
THREE.ShaderChunk[ "map_fragment" ],
THREE.ShaderChunk[ "alphatest_fragment" ],
THREE.ShaderChunk[ "specularmap_fragment" ],
THREE.ShaderChunk[ "lights_phong_fragment" ],
THREE.ShaderChunk[ "lightmap_fragment" ],
THREE.ShaderChunk[ "color_fragment" ],
THREE.ShaderChunk[ "envmap_fragment" ],
THREE.ShaderChunk[ "shadowmap_fragment" ],
THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
THREE.ShaderChunk[ "fog_fragment" ],
"}"
].join('\n');
</script>
learned a little bit about the three.js phong material -- it doesn't handle highlights the way I'd like, but... whatever. If you need more sophistication I'd suggest using ShaderMaterial instead.

You will need a custom shader for that -- and a texture with an alpha channel (essentially, a DDS format). If you look in the three.js source, you'll see a bit of fragment-shader code called 'map_fragment' that looks like so:
"#ifdef USE_MAP",
"#ifdef GAMMA_INPUT",
"vec4 texelColor = texture2D( map, vUv );",
"texelColor.xyz *= texelColor.xyz;",
"gl_FragColor = gl_FragColor * texelColor;",
"#else",
"gl_FragColor = gl_FragColor * texture2D( map, vUv );",
"#endif",
"#endif"
As you can see it multiplies your texture map by the color (which at that point is in "gl_FragColor"). Instead, it sound like you want your map to overlay on top of the base color, only were you've painted it (say, chrome bumpers and headlights).
A simple way to do this might be to just alter the way 'phong' works before you instantiate any phong materials, by changing the value of that "map_fragment" string.
Instead of "gl_FragColor * (something)" -- try this as a re-assignment of THREE.ShaderChunk.map_fragment:
[
"#ifdef USE_MAP",
"vec4 texelColor = texture2D( map, vUv );",
"#ifdef GAMMA_INPUT",
"texelColor.xyz *= texelColor.xyz;",
"#endif",
"gl_FragColor = vec4(mix(gl_FragColor.rgb,texelColor.rgb,texelColor.a),(gl_FragColor.a*texelColor.a));",
"#endif"
].join('\n');
The hazard with overriding the stock method this way is that it will apply to ALL models in the scene -- the painted texture will overlay the material color. If that's okay, you're done. Otherwise you should create a new THREE.ShaderMaterial that's like "phong" except for the difference cited here.

Related

How to make Three.js ShaderMaterial transparent

I'm very new to shaders. I'm trying to achieve this color and transparent effect:
Example #1 Example#2
This is my result:
my result
This what I have so far:
this.material = new THREE.ShaderMaterial({
extensions: {
derivatives: "#extension GL_OES_standard_derivatives : enable"
},
side: THREE.DoubleSide,
uniforms: {
time: { value: 0 },
resolution: { value: new THREE.Vector4() }
},
transparent: true,
vertexShader: vertex,
fragmentShader: fragment
});
fragmentShader
varying vec2 vUv;
varying float vNoise;
uniform vec2 u_resolution;
void main() {
vec3 color1 = vec3(0.,0.,0.);
vec3 color2 = vec3(1.,1.,1.);
vec3 finalcolor = mix(color1,color2,0.9*(vNoise+1.));
gl_FragColor = vec4( vec3(finalcolor),0.2);
}
How would you go about this?
Thanks!
I guess that it is a bit late for answer, but maybe someone will have same problem. I think you are looking for Fresnel Shader:
Three.js example can be found here:
https://jsfiddle.net/8n36c47p/4/
Here is main shader part:
vertexShader: [
"varying vec3 vPositionW;",
"varying vec3 vNormalW;",
"void main() {",
" vPositionW = vec3( vec4( position, 1.0 ) * modelMatrix);",
" vNormalW = normalize( vec3( vec4( normal, 0.0 ) * modelMatrix ) );",
" gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join( "\n" ),
fragmentShader: [
"varying vec3 vPositionW;",
"varying vec3 vNormalW;",
"void main() {",
" vec3 color = vec3(1., 1., 1.);",
" vec3 viewDirectionW = normalize(cameraPosition - vPositionW);",
" float fresnelTerm = dot(viewDirectionW, vNormalW);",
" fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.);",
" gl_FragColor = vec4( color * fresnelTerm, 1.);",
"}"
].join( "\n" )

Three.js ShaderMaterial with lights on mesh imported from Blender

I'm trying to get a ShaderMaterial with lights working in three.js r77. It works correctly when applied to a mesh with a simple BoxGeometry but it behaves incorrectly when applied to a mesh imported from Blender.
A simple jsfiddle illustrating the problem is here. The mesh on the left is created from a Blender export. The mesh on the right is created from a simple BoxGeometry. Both are using the same ShaderMaterial. The position of the light is indicated by the DirectionalLightHelper.
The mesh on the right is being lit correctly, while the mesh on the left is not. Clearly the problem is in my shader code. I initially assumed that the problem is in the UV map on the imported mesh, but that does not appear to be true. In the jsfiddle example the UV map is copied directly to the BoxGeometry mesh from the imported geometry---they're rotated relative to each other because of the coordinate differences between Blender and three.js, but the lighting is still working correctly on the mesh on the right with the imported UVs.
The shader code is:
THREE.TestShader = {
uniforms: {
"uDirLightPos": {
type: "v3",
value: new THREE.Vector3(20, 20, 20)
},
"uDirLightColor": {
type: "c",
value: new THREE.Color(0xffffff)
},
"uTexture": {
type: "t",
value: null
},
},
vertexShader: [
"varying vec3 vNormal;",
"varying vec3 vViewPosition;",
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"vNormal = normalize(normalMatrix * normal);",
"vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);",
"vViewPosition = -mvPosition.xyz;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
"}"
].join("\n"),
fragmentShader: [
"uniform vec3 uDirLightPos;",
"uniform vec3 uDirLightColor;",
"varying vec2 vUv;",
"varying vec3 vNormal;",
"varying vec3 vViewPosition;",
"varying vec4 mvPosition;",
"uniform sampler2D uTexture;",
"void main() {",
"vec4 lDirection = viewMatrix * vec4(uDirLightPos, 0.0);",
"vec3 lVector = normalize(lDirection.xyz);",
"vec3 normal = normalize(vNormal);",
"float diffuse = dot(normal, lVector);",
"vec4 texel = texture2D( uTexture, vUv );",
"gl_FragColor = vec4(uDirLightColor * diffuse, 1.0) * texel;",
"}"
].join("\n")
};
In addition to a solution to this particular problem, a pointer to better documentation on the three.js shader stuff would be appreciated. The official documentation for ShaderChunk, ShaderLib, and UniformsLib is not what you'd call exhaustive.
Your Blender-exported model has incorrect normals.
vnh1 = new THREE.VertexNormalsHelper( mesh1, 1, 0xff0000, 1 );
scene.add( vnh1 );
fiddle: https://jsfiddle.net/5j0axcgz/1/
three.js r.77

Custom ShaderMaterial map issue

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
});
}

converting script shader to js shader

got the uniforms in e.g
uniforms: {
"time": { type: "f", value: 0.0 }
},
where does e.g.
attribute float customFrequency;
attribute vec3 customColor; go? tia (just added code I am trying to convert)
<script type="x-shader/x-vertex" id="vertexshader">
uniform float time;
attribute float customFrequency;
attribute vec3 customColor;
varying vec3 vColor;
void main()
{
vColor = customColor;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
varying vec3 vColor;
void main()
{
gl_FragColor = vec4( vColor, 1.0 );
gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord );
}
</script>
apologies for not formulating the question very well - want to create threejs shader from above script in the form of
THREE.BasicShader = {
uniforms: {},
vertexShader: [
"void main() {",
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join("\n"),
fragmentShader: [
"void main() {",
"gl_FragColor = vec4( 1.0, 0.0, 0.0, 0.5 );",
"}"
].join("\n")
};
and cannot find an example using vertex attributes. tia
The question is not very clear, but I believe you are a little bit confused on the basic concepts.
Shaders are not supposed to be converted to javascript. They are written in GLSL language which the browser also understands and passes over to the display driver.
Uniforms are the way you pass variables between Javascript code and GLSL shaders. So you only need to care about uniforms on the Javascript side. The other code in the shader scripts are part of the shader GLSL code, can't be shared with or converted to javascript, and if you want to make changes to them, you need to modify the shader itself.
Lee Stemkoski kindly supplied the answer to this:
THREE.BasicShader = {
uniforms: {},
vertexShader: [
"uniform float time;",
"attribute float customFrequency;",
"attribute vec3 customColor;",
"varying vec3 vColor;",
"void main()",
"{",
"vColor = customColor;",
"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
"gl_PointSize = (1.0 + sin( customFrequency * time )) * 8.0 * ( 300.0 / length( mvPosition.xyz ) );",
"gl_Position = projectionMatrix * mvPosition;",
"}"
].join("\n"),
fragmentShader:
[
((similar to above))
].join("\n")
};

Normalize function in webGL not working (THREE.js)

I am currently working on creating a shader in THREE.JS which will act like the normal shader, but using a color as the input to define how it is shaded.
Below is the Code that is causing the problem, and after that is a longer explanation of why I am doing it, and what I think is causing the problem:
fragmentShader: [
"uniform float opacity;",
"varying vec3 vNormal;",
"uniform vec3 color;",
"void main() {",
//THIS IS WHAT I THINK IS SCREWING UP EVERYTHING
//Can I not call normalize with that complex
//of equations within a normalize function?
"gl_FragColor = vec4( normalize(color.r + (vNormal.x*color.r)*.5, color.g + (vNormal.y*color.g)*.5, color.b + (vNormal.z*color.b)*.5), opacity );",
"}"
].join("\n")
A longer description:
I am basically using the same code as the normal shader in the THREE.shaderLib, which is as follows:
'normal': {
uniforms: {
"opacity" : { type: "f", value: 1.0 }
},
vertexShader: [
"varying vec3 vNormal;",
"void main() {",
"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
"vNormal = normalize( normalMatrix * normal );",
"gl_Position = projectionMatrix * mvPosition;",
"}"
].join("\n"),
fragmentShader: [
"uniform float opacity;",
"varying vec3 vNormal;",
"void main() {",
"gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );",
"}"
].join("\n")
},
What I am using is basically this, but with a color aspect added, and it is called within a function that defines a new shader, like so:
function assignNewShader(color){
var colorAssign = new THREE.Color(color)
THREE.ShaderLib[color] = {
uniforms: {
"opacity" : { type: "f", value: 1.0 },
"color" : { type: "c", value: colorAssign },
},
vertexShader: [
"varying vec3 vNormal;",
"void main() {",
"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
"vNormal = normalMatrix * normal;",
"gl_Position = projectionMatrix * mvPosition;",
"}"
].join("\n"),
fragmentShader: [
"uniform float opacity;",
"varying vec3 vNormal;",
"uniform vec3 color;",
"void main() {",
"gl_FragColor = vec4(color.r + (vNormal.x*color.r)*.5, color.g + (vNormal.y*color.g)*.5, color.b + (vNormal.z*color.b)*.5, opacity );",
"}"
].join("\n")
}
}
You can see that the biggest difference lies within the 'fragmentShader' section, where the vNormal is used to make the gl_FragColor similar (but not exactly the same) as the color that is given in the function.
My problem is this: As a object is 'scaled' this color difference gets more and more drastic, to the point where all of the colors are only as bright as possible. Because of that, I tried to do the following to the 'fragementShader' section of the code:
fragmentShader: [
"uniform float opacity;",
"varying vec3 vNormal;",
"uniform vec3 color;",
"void main() {",
"gl_FragColor = vec4( normalize(color.r + (vNormal.x*color.r)*.5, color.g + (vNormal.y*color.g)*.5, color.b + (vNormal.z*color.b)*.5), opacity );",
"}"
].join("\n")
When I do this, I am greeted with a PLETHORA of errors including:
ERROR: 0:37: 'normalize' : no matching overloaded function found
ERROR: 0:37: 'constructor' : not enough data provided for construction
WebGL: INVALID_VALUE: attachShader: no object or object deleted
Could not initialise shader
VALIDATE_STATUS: false, gl error [1281]
WebGL: INVALID_OPERATION: getUniformLocation: program not linked
I am definitly in over my head getting into the webGL part of THREE, but it seems to me that this mode of altering the fragment shader should work. Does anybody have any ideas as to why it might not?
Normalize takes a vec3 not a vec4.

Resources