How to move shader scripts in external files? - opengl-es

I would like to know how could I move shaders which are actually included in my html to external files. In this way, I'll be able to include them in my gulp tasks. I took a look at how JavaScript shaders files are written but I don't understand very well.
For example with the Glow shader code below, how could I move it to an external file ?
<script type="x-shader/x-vertex" id="vertexShaderGlow">
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 type="x-shader/x-vertex" id="fragmentShaderGlow">
uniform vec3 glowColor;
varying float intensity;
void main()
{
vec3 glow = glowColor * intensity;
gl_FragColor = vec4( glow, 1.0 );
}
</script>

The other answer provided is simply taking GLSL code and turning each line into a string. Each string is a value in an array. The join call is concatenating all of the strings with a \n character to make the code easier to read when debugging. I've done it this way many times before, and is a legitimate solution to what you're trying to do.
But if you'd rather have external files with raw GLSL code, you can do that, too. Consider the two files:
glow_vertex.glsl
glow_fragment.glsl
These files contain the shader code which you would normally have in the script tags. You can use an XMLHTTPRequest to fetch the files, and use the returned text as your shader code.
var vertexShader = null;
var fragmentShader = null;
function shadersDone(){
var material = new THREE.ShaderMaterial({
uniforms: { /* define your uniforms */},
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
}
function vertexDone(code){
vertexShader = code;
if(fragmentShader !== null){
shadersDone();
}
}
function fragmentDone(code){
fragmentShader = code;
if(vertexShader !== null){
shadersDone();
}
}
var xhr1 = new XMLHttpRequest();
var xhr2 = new XMLHttpRequest();
xhr1.open("GET", "/server/glow_vertex.glsl", true);
xhr2.open("GET", "/server/glow_fragment.glsl", true);
xhr1.responseType = "text";
xhr2.responseType = "text";
xhr1.onload = function(){
if(xhr1.readyState === xhr1.DONE && xhr1.status === 200){
vertexDone(xhr1.resultText);
}
};
xhr2.onload = function(){
if(xhr2.readyState === xhr2.DONE && xhr2.status === 200){
fragmentDone(xhr2.resultText);
}
};
xhr1.send(null);
xhr2.send(null);
Note that that's all asynchronous. Also, your server is going to need to be configured to send GLSL files as plain text.
As long as we're talking about the modern web...
There is also the option to import your shader code. VERY BIG BUT it's currently only supported in Chrome and Opera (although polyfills do exist). Microsoft Edge lists the functionality as "under consideration," and Firefox does not indent to implement it in its current state. So take what follows with a large grain of salt, and keep an eye on: http://caniuse.com/#search=import
In your HTML, and before the JavaScript which would use it...
<link id="vertexImport" rel="import" href="/server/glow_vertex.glsl" />
<link id="fragmentImport" rel="import" href="/server/glow_fragment.glsl" />
Then later in your JavaScript:
var material = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertexImport").import.body.childNodes[0].data,
fragmentShader: document.getElementById("fragmentImport").import.body.childNodes[0].data,
});
Again, this is asynchronous. You may need to add an onload handler for each link, so you don't attempt to access the code before it's loaded.

The answer suggesting joining an array of glsl lines is a pattern that can be encountered with three, but should probably be avoided in this use case.
It may be useful in some kind of a module, where it's sort of a "compiled" snapshot of a shader, not intended to be modified.
Otherwise the main downsides of this approach is lack of syntax highlighting and being verbose.
Nowadays most js code is transformed one way or the other. Shader code should be inlined like this.
const myShader = new THREE.ShaderMaterial({
vertexShader: require('./myVert.vs'),
fragmentShader: require('./myFrag.vs'),
})
edit
myVert.vs:
//this is all nicely highlighted in sublime text for example
void main (){
gl_Position = vec4( position.xy, 0., 1.);
}
myVert.fs:
void main (){
gl_FragColor = vec4(1.,0.,0.,1.);
}
myClass.js:
class myMaterial extends THREE.ShaderMaterial{
constructor(){
super({
vertexShader: require('./myVert.vs'),
//^ becomes vertexShader: 'void main() {...}'
...

You can move the shader code into a separate JS file and include that file after three.js.
Here is one example from https://github.com/timoxley/threejs/blob/master/examples/js/shaders/ColorifyShader.js
/**
* #author alteredq / http://alteredqualia.com/
*
* Colorify shader
*/
THREE.ColorifyShader = {
uniforms: {
"tDiffuse": { type: "t", value: null },
"color": { type: "c", value: new THREE.Color( 0xffffff ) }
},
vertexShader: [
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join("\n"),
fragmentShader: [
"uniform vec3 color;",
"uniform sampler2D tDiffuse;",
"varying vec2 vUv;",
"void main() {",
"vec4 texel = texture2D( tDiffuse, vUv );",
"vec3 luma = vec3( 0.299, 0.587, 0.114 );",
"float v = dot( texel.xyz, luma );",
"gl_FragColor = vec4( v * color, texel.w );",
"}"
].join("\n")
};
With the above you would create our material like this:
material = new THREE.ShaderMaterial({
uniforms : THREE.ColorifyShader.uniforms,
vertexShader : THREE.ColorifyShader.vertexShader,
fragmentShader : THREE.ColorifyShader.fragmentShader
});
ofcourse you don't need to call the object THREE.ColorifyShader, you can call it whatever you want.

Related

Upgrading to Three js 0.130.1 version Points rendered with shadermaterial and buffergeometry not rendering

We were using Three 0.115 version and everything was working. Since we got vulnerability issues for < 0.125, we decided to upgrade to latest version. Then we are getting issues with shader material.
We have an application that uses Point cloud rendered with buffer geometry(positions, sizes and colors bufferattributes) and shadermaterial.
function vertexShader() {
return `attribute float size;
attribute vec3 customColor;
varying vec3 vColor;
attribute float visibility;
varying float vVisible;
void main() {
vColor = customColor;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 300.0 / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
vVisible = visibility;
}`
}
function fragmentShader() {
return `uniform vec3 color;
uniform sampler2D pointTexture;
varying vec3 vColor;
varying float vVisible;
void main() {
gl_FragColor = vec4( color * vColor, 1.0 );
gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord );
if ( gl_FragColor.a < ALPHATEST ) discard;
if (vVisible < 0.5) discard;
}`
}
and in our javascript init code.
const material = new THREE.ShaderMaterial({
uniforms: {
color: { value: new THREE.Color(0xffffff) },
texture: { value: new THREE.TextureLoader().load(circle) },
resolution: { value: new THREE.Vector2() },
},
vertexShader: vertexShader(),
fragmentShader: fragmentShader(),
alphaTest: 0.9,
blending: THREE.AdditiveBlending
});
there is no error in console. But points are not rendered.
we use raycast for detecting points and that works without any issue.
Any idea why after upgrading to latest version of three, rendering of points fails?
is this something to do with shadermaterial?
Thanks for the help :)

gradient multicolor metaballs with threejs and marchingcubes

I am looking on how to implement something similar than this work: https://vimeo.com/9121195 . But with explicitly attributing colors to each metaball from an array of given colors. As far as I can see, this is done entirely from the shader side in this example but I was wondering if this could not be implemented with Threejs and marchingcubes.
By creating a new THREE.ShaderMaterial I assume I can pass positions of each metaball to the shader and then evaluate the distance of each vertex with the given positions to finally assign a color to each pixel.
Just to simplify the problem I start with two moving metaballs.
from threejs:
function init() {
//...
var strength = 0.61;
var resolution = 70;
var substract = 25;
var scaleFactor = 1200;
var posData = []; // array storing metaballs positions
var myShaderMaterial = new THREE.ShaderMaterial({ uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader,
name:"myShaderMaterial" }); // shader is described below
effect = new THREE.MarchingCubes( resolution, myShaderMaterial,false,true);
effect.position.set( 0, 0, 0 );
effect.scale.set( scaleFactor, scaleFactor, scaleFactor );
effect.enableUvs = true;
effect.enableColors = true;
effect.hasColors = true;
effect.castShadow = true;
//...
scene.add( effect );
}
function render() {
effect.reset();
effect.init(resolution);
for (var i = 0; i <= posData.length - 1; i++) {
item = posData[i];
effect.addBall(item[0], item[1], item[2], strength, substract);
effect.material.uniforms.allPos.value[i] = new THREE.Vector3(item[0],item[1],item[2]).multiplyScalar(1./scaleFactor);
}
//...
}
here is my shader:
'myShaderMaterial' : {
uniforms: {
"colChoice":{type: "v3v", value:[new THREE.Color( 0xef5350 ),
new THREE.Color( 0xffffff )]},
"allPos":{type: "v3v",value:[new THREE.Vector3(0., 0., 0.),
new THREE.Vector3(0., 0., 0.)]},
},
vertexShader: [
"uniform vec3 colChoice[2];",
"varying vec3 vNormal;",
"varying vec3 vRefract;",
"varying vec3 blobColor;",
"varying vec3 otherColor;",
"uniform vec3 allPos[2];",
"varying float mixScale;",
"void main() {",
"vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
"vec3 worldNormal = normalize ( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );",
"vNormal = normalize( normalMatrix * normal );",
"gl_Position = projectionMatrix * mvPosition;",
"float distMin = 100000000000000000000000000000000000000.;",
"float distMax = 0.;",
"for (int i=0;i<2;i++){",
"float distV = distance(allPos[i], position );",
"if (distV<distMin){",
"distMin = distV;",
"}",
"if (distV>distMax){",
"distMax = distV;",
"}",
"mixScale = smoothstep(distMin,distMax,distV);",
"if (mod(float(i),2.0)==0.){",
"blobColor = colChoice[0];",
"otherColor = colChoice[1];",
"}",
"else{",
"blobColor = colChoice[1];",
"otherColor = colChoice[0];",
"}",
"}",
"}",
].join( "\n" ),
fragmentShader: [
"varying vec3 blobColor;",
"varying vec3 otherColor;",
"varying vec3 vNormal;",
"varying float mixScale;",
"void main() {",
"vec3 finalColor = (0.3*vNormal) + mix(otherColor,blobColor,mixScale);",
"gl_FragColor = vec4(finalColor,1.0);",
"}",
].join( "\n" )
here is a sample result:
The issue here is that obviously the distance is not calculated properly and the transition between two colors that I tried to do with mixScale also does not work. Any idea?
Yeah it's possible but I don't think it does it out of the box. I peeked at the marchingcubes code you linked, and it has an "enableColor" flag but color seems to be filled with the vertex coordinate instead of interpolating a separate color value from the density field.
I've had to do something very similar in the past to do multi material terrain generation from noise fields. I think I ended up extending the marching cubes to interpolate colors, then assigned each material to a color channel, r, g, b.

How to maintain the glow effect of a json model rotating in three.js scene?

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

custom vertex color with threejs LineSegments()

I am struggling with threejs to use a shaderMaterial on a THREE.LineSegments
my goal is to have per vertex color, on a bunch of line segments:
I have a vertex and a fragment shader :
<script type="x-shader/x-vertex" id="vertexshader">
varying vec3 vColor;
attribute vec3 customColor;
void main() {
vColor = customColor;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0 );
}
</script>
then in the threejs scene , I create all the relevant stuff for the shader, and apply it to the object:
var customColors = new Float32Array(_count*2*3); // 2 vertex by segment *length(RGB)
for (var i = 0; i < customColors.length/3; i++) {
var ratio = i / (customColors.length/3.0);
customColors[(i*3)] = ratio;
customColors[(i*3)+1] = ratio;
customColors[(i*3)+2] = ratio;
};
geo.addAttribute('customColor' , new THREE.BufferAttribute(customColors,3));
var drawCount = _count * 2;
geo.setDrawRange( 0, drawCount );
var uniforms = {
opacity : 0.5
};
var customMaterial = new THREE.ShaderMaterial( {
uniforms : uniforms,
vertexShader : document.getElementById( 'vertexshader' ).textContent,
fragmentShader : document.getElementById( 'fragmentshader' ).textContent,
}
);
var lines = new THREE.LineSegments(geo, customMaterial);
I can't find where the problem is.
The line segments don't appear at all,not even in black.
All I have managed to do is to 'hard code' the color in the fragment shader ( which defeats the purpose evidently).
No error message whatsoever in the console.
please help, what am i doing wrong here ?
After struggling again , I finally can see some line segments with a custom vertex color.
It looks I needed to give a value to the gl_Position variable ( I thought it would automatic, since I don't wan't to change vertex position).
I added this line into main() of the vertexShader:
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);

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

Resources