I am working with shaders in Three.js for the first time and I can't tweak some of the code that is essential to it, other than just change the RGBA. For example I can't place it inside a .html() method. What I'm going for is applying 10 different shader colors.
varying vec3 vNormal;
void main() {
float intensity = pow( 0.4 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) ), 4.0 );
gl_FragColor = vec4( 0, 0, 255, 1.0 ) * intensity; }
//gl_FragColor = vec4( 0, 0, 255, 1.0 ) is the RGBA value
}
As of right now, I can't do anything with that line that I found but just to let it untouched so it'll work. I can only copy the ID on its script tag, tweak the RGBA code and refer it to different <script> elements. But I don't want to do that ten times. My code needs to be efficient.
The entire code necessary is inside this fiddle.
How can and would you change that code so you can easily call a shader color?
You need to add a uniform for the colour.
HERE you can see the first one I did when starting with Three.js shaders.
but basically it's something like...
HTML/shaders:
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform vec3 diffuse;
void main() {
gl_FragColor = vec4(diffuse.x, diffuse.y, diffuse.z, 1.0);
}
</script>
Create the shader in JS...
var uniforms = {
diffuse: { type: "c", value: new THREE.Color(0xeeeeee) }
};
var vertexShader = document.getElementById('vertexShader').text;
var fragmentShader = document.getElementById('fragmentShader').text;
material = new THREE.ShaderMaterial({
uniforms : uniforms,
vertexShader : vertexShader,
fragmentShader : fragmentShader,
});
To change the color, change uniforms.diffuse.value.
My example does not include your vNormal and intensity, but you get the idea.
Also, I have my shaders in HTML, but they obviously could just be javascript variables/Strings.
Check this version of your fiddle
You may find THIS page of other experiments of mine interesting.
Related
I have a simple model consisting of 1000 instances of sphere. I am trying to use instancing to reduce the number of draw calls. However, I am not able to change the transparency/opacity of the individual child geometries.
I already tried the following things:
I am able to change transparency of every sphere using
material.fragmentShader = "varying vec3 vColor;void main() { gl_FragColor = vec4( vColor, 0.2 );}";
However, that changes the opacity to 0.2 for every sphere.
The html file is like:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A-Frame Instancing component</title>
<meta name="description" content="A-Frame Instancing component">
<script>
/*
var WebVRConfig = {
FORCE_ENABLE_VR: true,
BUFFER_SCALE: 1.0
};
*/
</script>
<script src="https://cdn.rawgit.com/aframevr/aframe/v0.4.0/dist/aframe-master.min.js"></script>
<script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v3.2.0/dist/aframe-extras.min.js"></script>
<script type="text/javascript" src="build/aframe-instancing.js"></script>
</head>
<body>
<a-scene stats>
<a-assets>
<img id="sky" src="https://cdn.rawgit.com/aframevr/aframe/master/examples/primitives/models/peach-gradient.jpg">
</a-assets>
<a-entity instancing="count:100"></a-entity>
<a-sky src="#sky"></a-sky>
<a-entity light="type:directional;color:#FFFFFF" position="-1 1 1"></a-entity>
</a-scene>
</body>
</html>
The function to acheive instancing:
AFRAME.registerComponent('instancing', {
schema: {
count: {type: 'int', default: 10000}
},
var geometry = new THREE.InstancedBufferGeometry();
geometry.copy(new THREE.SphereBufferGeometry(5.0));
var translateArray = new Float32Array(count*3);
var vectorArray = new Float32Array(count*3);
var colorArray = new Float32Array(count*3);
geometry.addAttribute('translate', new THREE.InstancedBufferAttribute(translateArray, 3, 1));
geometry.addAttribute('vector', new THREE.InstancedBufferAttribute(vectorArray, 3, 1));
geometry.addAttribute('color', new THREE.InstancedBufferAttribute(colorArray, 3, 1));
var material = new THREE.ShaderMaterial({
uniforms: {
time: {value: 0}
},
vertexShader: [
'attribute vec3 translate;',
'attribute vec3 vector;',
'attribute vec3 color;',
'uniform float time;',
'varying vec3 vColor;',
'const float g = 9.8 * 1.5;',
'void main() {',
' vec3 offset;',
' offset.xz = vector.xz * time;',
' offset.y = vector.y * time - 0.5 * g * time * time;',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position + translate + offset, 1.0 );',
' vColor = color;',
'}'
].join('\n'),
fragmentShader: [
'varying vec3 vColor;',
'void main() {',
' gl_FragColor = vec4( vColor, 1 );',
'}'
].join('\n')
});
var mesh = new THREE.Mesh(geometry, material);
this.model = mesh;
el.setObject3D('mesh', mesh);
el.emit('model-loaded', {format:'mesh', model: mesh});
//try to change opacity here
material.fragmentShader = "varying vec3 vColor;void main() { gl_FragColor = vec4( vColor, 0.2 );}";
material.transparent = true;
//use the new opacity
var mesh1 = new THREE.Mesh(geometry1, material);
this.mesh = mesh1;
el.setObject3D('mesh', mesh1);
el.emit('model-loaded', {format:'mesh', model: mesh1});
}
});
}
]);
Can anyone please tell me, how to change opacity of only one sphere? Thank you in advance!
Also, suppose I am trying to replicate multiple boxes.
One of which is as following:
<a-box position="19.0 1.5 23.0"
width="32.0"
height="1.0"
depth="40.0"
color="#969696"
shader="flat"
flat-shading="true">
</a-box>
What would be the values I would fill in translateArray & vectorArray ?
Thanks a lot in advance!
Your colors are only RGB values, not RGBA. Update your color attribute to support 4 values, and change associated vec3 references to use vec4. The last value in the vector will be your alpha (transparency) value.
It looks like you already know how to send the value from your vertex shader into your fragment shader, so I won't go into detail there. But in your fragment shader, you can use the color directly, because gl_FragColor expects to be set to a vec4.
Further clarification:
When you create a InstancedBufferAttribute, you create one attribute per instance. So your color attribute currently only carries RGB values for each instance.
Hard-coding 1 or 0.2 in the w place (i.e. gl_FragColor = vec4( vColor, 1 );), you apply it universally to all instances. So, you need to define the alpha value on a per-instance basis, and the easiest way to do this is through your already-created and instanced color attribute.
//geometry.addAttribute('color', new THREE.InstancedBufferAttribute(colorArray, 3, 1)
geometry.addAttribute('color', new THREE.InstancedBufferAttribute(colorArray, 4, 1)
The code above makes room for the alpha values, which you should supply for each sphere. Your colorArray will contain data like [ R, G, B, A, R, G, B, A, ... ].
Then, in your shaders...
// vertex shader
// ...
attribute vec4 color;
varying vec4 vColor;
// ...
void main(){
// ...
vColor = color;
// ...
}
// fragment shader
// ...
varying vec4 vColor;
// ...
void main(){
// ...
gl_FragColor = vColor;
}
Now, the alpha value that you supplied for each sphere instance will be used only for that instance. For example, if you want the sphere at index 1 to be the only transparent sphere, your colorArray buffer should look like this:
colorArray = [
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 0.5,
1.0, 1.0, 1.0, 1.0,
// ...
];`
Important caveat
This implementation does not depth-sort the instances, and so blending will be dependent on render order. You can read more about this in the following question:
Transparency within instanced shapes
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 making my first steps playing with Three.js and coding with JavaScript. And since some months ago I am experimenting with Shaders and Shader Materials.
The thing is that I loaded a mesh with a Fresnel material. I can see perfectly the material in the surface, but I can't see at the inside of the mesh when I turn it : http://codepen.io/gnazoa/pen/WrLJay
I tried to make a second material and put it under the shader material. But is not the best solution, sometimes, when I load the browser, it doesn't work correctly: http://codepen.io/gnazoa/pen/rxovKb
Do you have a suggestion? Is there a way to see all the sides of the mesh using the Fresnel Shader?
Here I let the code of the shaders that I am using:
<script id="vertexShader" type="x-shader/x-vertex">
uniform float fresnelBias;
uniform float fresnelScale;
uniform float fresnelPower;
varying float vReflectionFactor;
varying vec3 vReflect;
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
vec3 worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );
vec3 I = worldPosition.xyz - cameraPosition;
vReflect = reflect( I, worldNormal );
vReflectionFactor = fresnelBias + fresnelScale * pow( 1.0 + dot( normalize( I ), worldNormal ), fresnelPower );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform vec3 color;
uniform samplerCube envMap;
varying vec3 vReflect;
varying float vReflectionFactor;
void main() {
vec4 envColor = textureCube( envMap, vec3( -vReflect.x, vReflect.yz ) );
gl_FragColor = vec4(mix(color, envColor.xyz, vec3(clamp( vReflectionFactor, 0.0, 1.0 ))), 1.0);
}
</script>
EDIT:
I already found the answer in the manual, it was so simple like write side: THREE.DoubleSide, in the material to see all the sides of the mesh.
But now I am trying to search why my mesh is not black in the inside if my color uniform is black.
Do you have a suggestion?
Your inside material is so highly glossy that it reflects the white over and over again. You don't see black because of this infinite reflection of white.
The easiest solution to this is setting a different inside and outside material. Your highly glossy black on the outside and a non reflective black on the inside. I tested it and it works quite well:
outsideMaterial = new THREE.ShaderMaterial({
side: THREE.FrontSide,
uniforms : uniforms,
vertexShader : vertexShader,
fragmentShader : fragmentShader,
wireframe: false
});
insideMaterial = new THREE.MeshBasicMaterial({
side: THREE.BackSide,
color: 0x000000
});
var loader = new THREE.BinaryLoader();
loader.load( "http://threejs.org/examples/obj/walt/WaltHead_bin.js", function ( geometry ) {
geometry.scale( 7, 7, 7 );
blackObject = new THREE.Object3D();
inside = new THREE.Mesh( geometry, insideMaterial );
outside = new THREE.Mesh( geometry, outsideMaterial );
blackObject.add( inside );
blackObject.add( outside );
scene.add( blackObject );
});
I am running into an issue where I am trying to apply a custom shader material to a series of nested objects to simulate a glow effect around each node. The effect works well on my home laptop (windows 8.1 latest chrome), but my work computer does not render the frontface of the glowing object, only the backface. I have checked it against a few systems and it seems to be mostly a chrome rendering issue on windows devices.
http://i.imgur.com/uYLtoxm.gif
I have included a codepen example where I shifted the glow off to the side and you can see that in some versions it is not rendering the front group of normals. The red dots should have a glow applied to each of them that shows up on the front and back(left and right in example). Any help would be appreciated, I am stumped as to what is going on.
Here is the shader material settings
local.glowNodeMat = new THREE.ShaderMaterial(
{
uniforms:
{
"c": { type: "f", value: 0 },
"p": { type: "f", value: 5.5 },
glowColor: { type: "c", value: new THREE.Color(0xaaccff) },
viewVector: { type: "v3", value: local.camera.position }
},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
side: THREE.FrontSide,
blending: THREE.AdditiveBlending,
transparent: true
});
http://codepen.io/sniejadlik/pen/oDarE
///////////////////
FIXED thanks to Volune. Thanks for the help!
Fixed Vertex shader
<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 );
// incorrect intensity = pow( c - dot(vNormal, vNormel), p );
intensity = pow( abs(c - dot(vNormal, vNormel) ), p );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<!-- fragment shader a.k.a. pixel shader -->
<script id="fragmentShader" type="x-shader/x-vertex">
uniform vec3 glowColor;
varying float intensity;
void main()
{
vec3 glow = glowColor * intensity;
gl_FragColor = vec4( glow, 1.0 );
}
</script>
The error is in your vertex shader:
intensity = pow( c - dot(vNormal, vNormel), p );
You have c = 0 and p = 0.5. c - dot(vNormal, vNormel) may be negative (when the dot product returns a positive value), so you're trying to get the square of a negative value.
For some unknown reason, the fallback in Firefox looks like pow( abs(...), 0.5 ), while the fallback in Chrome seems to be 0.0.
Try to fix your shader like this:
intensity = pow( abs( c - dot(vNormal, vNormel) ), p );
I am trying to pass an attribute variable with three.js to the vertex shader, then the vertex shader should pass it to the fragment shader through a varying variable.
Vertex shader:
attribute vec4 color;
varying vec4 outColor;
void main()
{
outColor= color;
gl_Position= projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
Fragment shader:
varying vec4 outColor;
void main() {
gl_FragColor = outColor;
}
This way let's say that I have a cube with 8 vertices: if there is a different color for each vertex, then the cube should be drawn by interpolating the color of each vertex, and in the middle of a face it should have a mixed color. This is the javascript code snippet where I initialize the attributes:
var colors= [];
for(var i=0; i<geometry.vertices.length; i++) {
colors.push(new THREE.Vector4(0.0,1.0,0.0,1.0));
}
var attributes = {
color: {type:"v4", value: colors}
};
var material= new THREE.ShaderMaterial({
uniforms: uniforms,
attributes: {},
vertexShader: document.getElementById("vertexShader").textContent,
fragmentShader: document.getElementById("fragmentShader").textContent
});
For now this should draw a completely green cube. The problem is that the instruction in the vertex shader outColor=color; messes up everything: I just see a black screen. If I replace this instruction with outColor=vec4(0.0,1.0,0.0,1.0);, I see a correctly drawn green cube on the screen.
Here is the full source code.
Try passing attributes instead of {} to the THREE.ShaderMaterial constructor.