Standard Full Screen Quad Setup not Working in Three.js - javascript

I'm trying to establish a full screen quad using a pass thru vertex shader in THREE.js. The quad itself is a plane geometry with dimension (2, 2) located at the origin. It is assigned the ShaderMaterial. The camera is at z = 1 aiming at the quad.
The shaders are quite simple:
Vertex Shader:
void main() {
gl_Position = vec4( position, 1.0 );
}
Fragment Shader:
void main() {
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
But nothing shows up on screen. This setup is a standard way of render-to-texture, why is it not working in THREE.js?
I've tried plane.frustumCulled = false and changing the clip planes of the camera to no avail.
Any help is appreciated.
Upon further investigation, the reason for not seeing the rendering result is more involved and pointing to some odd behavior in three.js.
I am using a PlaneGeometry with a rotation matrix applied, which is then wrapped by an Object3D with a counter rotation.
var geometry = new THREE.PlaneGeometry(2, 2);
var m4 = new THREE.Matrix4().makeRotationX(Math.PI * 0.5);
geometry.applyMatrix(m4);
var mesh = new THREE.Mesh(geometry, material);
var obj = new THREE.Object3D();
obj.add(mesh);
obj.rotation.x = -Math.PI * 0.5;
scene.add(obj);
This setup seems to throw three.js off and no rendering is shown.
Ok, the rotation got thrown away because it is a part of model view matrix that I ignored in the vertex shader. I've to refact what I'm currently doing.

I'm not sure the exact problem you're having, but here is an example of a working fullscreen quad using the same technique.
var canvas = document.getElementById('canvas');
var scene = new THREE.Scene();
var renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true});
var camera = new THREE.PerspectiveCamera(45, canvas.clientWidth / canvas.clientWidth, 1, 1000);
var clock = new THREE.Clock();
var quad = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.ShaderMaterial({
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
depthWrite: false,
depthTest: false
})
);
scene.add(quad);
var box = new THREE.Mesh(
new THREE.BoxGeometry(50, 50, 50),
new THREE.MeshBasicMaterial({color: '#000', wireframe: true})
);
scene.add(box);
camera.position.z = 200;
render();
function render() {
requestAnimationFrame(render);
if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
var dt = clock.getDelta();
box.rotation.x += dt * 2 * Math.PI / 5;
box.rotation.y += dt * 2 * Math.PI / 7;
box.rotation.z += dt * 2 * Math.PI / 11;
renderer.render(scene, camera);
}
html, body, #canvas {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
<canvas id="canvas"></canvas>
<script id="vertex-shader" type="x-shader/x-vertex">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
varying vec2 vUv;
void main() {
gl_FragColor = vec4(vUv, 0.0, 1.0);
}
</script>

Related

how to hide intersection of two 3d object by three.js? [duplicate]

This question already has an answer here:
three.js - drawing two overlapping transparent spheres and hiding intersection
(1 answer)
Closed 3 years ago.
how to hide/disvisible intersection of two object3D by three.js?
for example:
there are two spheres are 'S-Red' and 'S-Blue'
because S-Red is transparent so looks like:
but I hope can display like this
You can set opacity of sphere pixels in fragment shader:
body, canvas {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/controls/TransformControls.js"></script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, innerWidth/innerHeight, 0.01, 1000);
camera.position.set(5,5,0);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth,innerHeight);
document.body.appendChild(renderer.domElement);
let orbit = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(500, 100, 0x666666, 0x444444));
let s1 = sphere(3, 2, 0)
let s2 = sphere(3, -2, 1)
let u1 = s1.material.uniforms, u2 = s2.material.uniforms;
requestAnimationFrame( render );
function sphere(radius, position, color){
color = color.toFixed(1)
var geometry = new THREE.SphereGeometry(radius, 50, 50);
var material = new THREE.ShaderMaterial({
transparent: true,
depthWrite: false,
side: THREE.DoubleSide,
uniforms: {c: {type: "3f"}, o: {type: "3f"}},
vertexShader: `
varying vec3 p;
void main() {
// transfer vertex position to fragment shader,
// this value is interpolated by gpu hardware between pixels of triangle,
// containing this vertex
p = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`,
fragmentShader: `
varying vec3 p; // position of current pixel relative to sphere center
uniform vec3 c; // center of current sphere
uniform vec3 o; // center of opposite sphere
void main() {
vec3 a = abs(p)*50.0;
float opacity = a.x<1. || a.y<1. || a.z<1. ? 0.8 : 0.3;
// here is test of shpere overlapping
opacity = distance(o, p + c) < 3.0 ? 0.0 : opacity;
gl_FragColor = vec4(vec3(${color}, 0.0, 1.0 - ${color}), opacity);
}`
});
let mesh = new THREE.Mesh(geometry, material);
mesh.translateX(position)
scene.add(mesh);
let control = new THREE.TransformControls(camera, renderer.domElement);
control.addEventListener('dragging-changed', e => orbit.enabled = !e.value);
scene.add(control);
control.attach(mesh)
return mesh;
}
function render() {
requestAnimationFrame( render );
let p1 = s1.position, p2 = s2.position;
u2.o.value = u1.c.value = [p1.x, p1.y, p1.z];
u1.o.value = u2.c.value = [p2.x, p2.y, p2.z];
u1.c.needUpdate = u1.o.needUpdate =
u2.c.needUpdate = u2.o.needUpdate = true;
renderer.render( scene, camera );
}
</script>

Three.js uniform dashed line relative to camera

I'm working to display geometric figures in 3D, using three.js.
When you draw (by hand) hidden lines as dashed lines, the 'dashes' are regular for all them. This means that a line parallel to the camera plane or a line (nearly) perpendicular to the camera plane should do have the same length and gap.
But this seems to not work with LineDashedMaterial.
For the attached example, I'm using this (very) basic code:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var geometry = new THREE.BoxGeometry( 2, 2, 2 );
var LINES_DASHED = new THREE.LineSegments(
new THREE.EdgesGeometry(geometry),
new THREE.LineDashedMaterial({
linewidth: 2,
color: 0x000000,
dashSize: 0.2,
gapSize: 0.1,
depthTest: false,
polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1
})
);
LINES_DASHED.computeLineDistances();
scene.add( LINES_DASHED );
scene.background = new THREE.Color( 0xffffff);
camera.position.z = 5;
var animate = function () {
requestAnimationFrame( animate );
LINES_DASHED.rotation.x += 0.01;
LINES_DASHED.rotation.y += 0.01;
renderer.render( scene, camera );
};
animate();
body { margin: 0; }
canvas { width: 100%; height: 100% }
<script src="https://threejs.org/build/three.min.js"></script>
Working example:
https://bs4.scolcours.ch/_dev/3js_ex.php
I thougth that using:
line.computeLineDistance();
will solve the problem. But it seems to calculate the line length in 3D space (which seems to be logical).
Is there something I missed ?
Thanks for your help!
That's abroad task. It seems that THREE.LineDashedMaterial does not support this.
But it is possible to write a shader and to use a THREE.ShaderMaterial.
The trick is to know the start of a line in the fragment shader. In general this easy by using a flat interpolation qualifier.
Sadly WebGL 1.0 / GLSL ES 1.00 doesn't support this. So we have to use WebGL 2.0 / GLSL ES 3.00.
In OpenGL ES there exists the extension GL_NV_shader_noperspective_interpolation. Unfortunately there doesn't seem to be a corresponding WebGL extension. (See WebGL Extension Registry)
So lets cerate a THREE.WebGLRenderer with a WebGL2 context. See How to use WebGL2:
var canvas = document.createElement( 'canvas' );
var context = canvas.getContext( 'webgl2' );
var renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );
The vertex shader has to pass the normalized device coordinate to the fragment shader. Once with default interpolation and once with no (flat) interpolation. This causes that in the fragment shade the first input parameter contains the NDC coordinate of the actual position on the line and th later the NDC coordinate of the start of the line.
flat out vec3 startPos;
out vec3 vertPos;
void main() {
vec4 pos = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_Position = pos;
vertPos = pos.xyz / pos.w;
startPos = vertPos;
}
Additionally the varying inputs, the fragment shader has uniform variables. u_resolution contains the width and the height of the viewport. u_dashSize contains the length of line and u_gapSize the length of a gap in pixel.
So the length of the line from the start to the actual fragment can be calculated:
vec2 dir = (vertPos.xy-startPos.xy) * u_resolution/2.0;
float dist = length(dir);
And fragment on the gab can be discarded, by the discard command.
if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
discard;
Fragment shader:
precision highp float;
flat in vec3 startPos;
in vec3 vertPos;
uniform vec3 u_color;
uniform vec2 u_resolution;
uniform float u_dashSize;
uniform float u_gapSize;
void main(){
vec2 dir = (vertPos.xy-startPos.xy) * u_resolution/2.0;
float dist = length(dir);
if ( fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize) )
discard;
gl_FragColor = vec4(u_color.rgb, 1.0);
}
Setup the THREE.ShaderMaterial and the uniforms:
var uniforms = {
u_resolution: {type: 'v2', value: {x: vpSize[0], y: vpSize[1]}},
u_dashSize : {type:'f', value: 10.0},
u_gapSize : {type:'f', value: 5.0},
u_color : {type: 'v3', value: {x:0.0, y:0.0, z:0.0} }
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var LINES_DASHED = new THREE.LineSegments(
new THREE.EdgesGeometry(geometry),
material);
Note, if the resolution of the canvas changes, the values of the u_resolution have to be set:
e.g.
LINES_DASHED.material.uniforms.u_resolution.value.x = window.innerWidth;
LINES_DASHED.material.uniforms.u_resolution.value.y = window.innerHeight;
I applied the suggestions to your original code. See the preview and the example:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 60, window.innerWidth/window.innerHeight, 0.1, 1000 );
var canvas = document.createElement( 'canvas' );
var context = canvas.getContext( 'webgl2' );
var renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );
var vpSize = [window.innerWidth, window.innerHeight];
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var geometry = new THREE.BoxGeometry( 2, 2, 2 );
var uniforms = {
u_resolution: {type: 'v2', value: {x: vpSize[0], y: vpSize[1]}},
u_dashSize : {type:'f', value: 10.0},
u_gapSize : {type:'f', value: 5.0},
u_color : {type: 'v3', value: {x:0.0, y:0.0, z:0.0} }
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var LINES_DASHED = new THREE.LineSegments(
new THREE.EdgesGeometry(geometry),
material);
LINES_DASHED.computeLineDistances();
scene.add( LINES_DASHED );
scene.background = new THREE.Color( 0xffffff);
camera.position.z = 5;
var animate = function () {
requestAnimationFrame( animate );
LINES_DASHED.rotation.x += 0.01;
LINES_DASHED.rotation.y += 0.01;
renderer.render( scene, camera );
};
window.onresize = function() {
vpSize = [window.innerWidth, window.innerHeight];
LINES_DASHED.material.uniforms.u_resolution.value.x = window.innerWidth;
LINES_DASHED.material.uniforms.u_resolution.value.y = window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
animate();
<script type='x-shader/x-vertex' id='vertex-shader'>
flat out vec3 startPos;
out vec3 vertPos;
void main() {
vec4 pos = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_Position = pos;
vertPos = pos.xyz / pos.w;
startPos = vertPos;
}
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
flat in vec3 startPos;
in vec3 vertPos;
uniform vec3 u_color;
uniform vec2 u_resolution;
uniform float u_dashSize;
uniform float u_gapSize;
void main(){
vec2 dir = (vertPos.xy-startPos.xy) * u_resolution.xy/2.0;
float dist = length(dir);
if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
discard;
gl_FragColor = vec4(u_color.rgb, 1.0);
}
</script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/r128/build/three.js"></script>

Apply color gradient to material on mesh - three.js

I have an STL file loaded into my scene with a single colour applied to a phong material
I'd like a way of applying two colours to this mesh's material with a gradient effect applied on the Z axis a like the example below.Gradient Vase]1
I have a feeling I may have to introduce shaders but I've not gotten this far with three.js.
Simple gradient shader, based on uvs:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
camera.position.set(13, 25, 38);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var canvas = renderer.domElement
document.body.appendChild(canvas);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geometry = new THREE.CylinderBufferGeometry(2, 5, 20, 32, 1, true);
var material = new THREE.ShaderMaterial({
uniforms: {
color1: {
value: new THREE.Color("red")
},
color2: {
value: new THREE.Color("purple")
}
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
}
`,
wireframe: true
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
render();
function resize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display;
block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/examples/js/controls/OrbitControls.js"></script>
Simple gradient shader, based on coordinates:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
camera.position.set(13, 25, 38);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var canvas = renderer.domElement
document.body.appendChild(canvas);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geometry = new THREE.CylinderBufferGeometry(2, 5, 20, 16, 4, true);
geometry.computeBoundingBox();
var material = new THREE.ShaderMaterial({
uniforms: {
color1: {
value: new THREE.Color("red")
},
color2: {
value: new THREE.Color("purple")
},
bboxMin: {
value: geometry.boundingBox.min
},
bboxMax: {
value: geometry.boundingBox.max
}
},
vertexShader: `
uniform vec3 bboxMin;
uniform vec3 bboxMax;
varying vec2 vUv;
void main() {
vUv.y = (position.y - bboxMin.y) / (bboxMax.y - bboxMin.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
}
`,
wireframe: true
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
render();
function resize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/examples/js/controls/OrbitControls.js"></script>
Gradient with vertex colours:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var canvas = renderer.domElement
document.body.appendChild(canvas);
var geom = new THREE.TorusKnotGeometry(2.5, .5, 100, 16);
var rev = true;
var cols = [{
stop: 0,
color: new THREE.Color(0xf7b000)
}, {
stop: .25,
color: new THREE.Color(0xdd0080)
}, {
stop: .5,
color: new THREE.Color(0x622b85)
}, {
stop: .75,
color: new THREE.Color(0x007dae)
}, {
stop: 1,
color: new THREE.Color(0x77c8db)
}];
setGradient(geom, cols, 'z', rev);
function setGradient(geometry, colors, axis, reverse) {
geometry.computeBoundingBox();
var bbox = geometry.boundingBox;
var size = new THREE.Vector3().subVectors(bbox.max, bbox.min);
var vertexIndices = ['a', 'b', 'c'];
var face, vertex, normalized = new THREE.Vector3(),
normalizedAxis = 0;
for (var c = 0; c < colors.length - 1; c++) {
var colorDiff = colors[c + 1].stop - colors[c].stop;
for (var i = 0; i < geometry.faces.length; i++) {
face = geometry.faces[i];
for (var v = 0; v < 3; v++) {
vertex = geometry.vertices[face[vertexIndices[v]]];
normalizedAxis = normalized.subVectors(vertex, bbox.min).divide(size)[axis];
if (reverse) {
normalizedAxis = 1 - normalizedAxis;
}
if (normalizedAxis >= colors[c].stop && normalizedAxis <= colors[c + 1].stop) {
var localNormalizedAxis = (normalizedAxis - colors[c].stop) / colorDiff;
face.vertexColors[v] = colors[c].color.clone().lerp(colors[c + 1].color, localNormalizedAxis);
}
}
}
}
}
var mat = new THREE.MeshBasicMaterial({
vertexColors: THREE.VertexColors,
wireframe: true
});
var obj = new THREE.Mesh(geom, mat);
scene.add(obj);
render();
function resize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
obj.rotation.y += .01;
requestAnimationFrame(render);
}
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display;
block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/build/three.min.js"></script>
Actually, it's up to you which approach to use: shaders, vertex colours, textures etc.
If you want to keep the functionality of the MeshPhongMaterial you can try extending the material.
This is a somewhat broad topic with several approaches, and you can read more about it in depth here.
There is a line in the phong materials shader that looks like this
vec4 diffuseColor = vec4( diffuse, opacity );
So after studying the book of shaders or some other tutorials, you will learn that you can mix two colors by using a normalized factor ( a number between 0,1).
That means that you could change this line to something like this
vec4 diffuseColor = vec4( mix(diffuse, myColor, vec3(myFactor)), opacity);
You can extend the shader as such
const myFactor = { value: 0 }
const myColor = {value: new THREE.Color}
myMaterial.onBeforeCompile = shader=>{
shader.uniforms.myFactor = myFactor
shader.uniforms.myColor = myColor
shader.fragmentShader = `
uniform vec3 myColor;
uniform float myFactor;
${shader.fragmentShader.replace(
vec4 diffuseColor = vec4( diffuse, opacity );
vec4 diffuseColor = vec4( mix(diffuse, myColor, vec3(myFactor)), opacity);
)}
`
Now when you change myFactor.value the color of your object should change from myMaterial.color to myColor.value.
Now to actually make it into a gradient you would replace myFactor with something dynamic. I like prisoners solution to use the uvs. It's entirely done in javascript, and very simple to hook up in this shader. Other approaches would probably require more shader work.
vec4 diffuseColor = vec4( mix(diffuse, myColor, vec3(vUv.y)), opacity);
Now the problem you may encounter - if you call new PhongMaterial({color}) ie. without any textures provided to it, the shader will compile without vUv.
There are many conditions that would cause it to compile and be useful to you, but i'm not sure if they break other stuff:
#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
So, adding something like
myMaterial.defines = {USE_MAP:''}
Might make vUv variable available for your shader. This way you get all the lights of the phong material to affect the material, you just change the base color.
If you want your gradient to be static, you could just add a texture to your material using the .map property. Or you could assign it to the .emissiveMap property if you want it to "glow" without the need of lights.
However, if you want your gradient to change, and always fade in the z-axis, even after rotating the model or camera, you'd have to write a custom shader, which would require you to take some tutorials. You could look at this example for how to implement custom shaders in Three.js, and visit https://thebookofshaders.com/ to get a good understanding on how to write a simple gradient shader.

Hard Light material blending mode in Three.js?

I am currently using the MeshPhongMaterial provided by Three.js to create a simple scene with basic water. I would like for the water material to have the Hard Light blending mode that can be found in applications such as Photoshop. How can I achieve the Hard Light blending modes below on the right?
The right halves of the images above are set to Hard Light in Photoshop. I am trying to recreate that Hard Light blend mode in Three.js.
One lead I have come across is to completely reimplement the MeshPhongMaterial's fragment and vertex shader, but this will take me some time as I am quite new to this.
What is the way to implement a Hard Light blending mode for a material in Three.js?
/*
* Scene config
**/
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0xffffff);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(0, 500, 1000);
camera.lookAt(scene.position);
/*
* Scene lights
**/
var spotlight = new THREE.SpotLight(0x999999, 0.1);
spotlight.castShadow = true;
spotlight.shadowDarkness = 0.75;
spotlight.position.set(0, 500, 0);
scene.add(spotlight);
var pointlight = new THREE.PointLight(0x999999, 0.5);
pointlight.position.set(75, 50, 0);
scene.add(pointlight);
var hemiLight = new THREE.HemisphereLight(0xffce7a, 0x000000, 1.25);
hemiLight.position.y = 75;
hemiLight.position.z = 500;
scene.add(hemiLight);
/*
* Scene objects
*/
/* Water */
var waterGeo = new THREE.PlaneGeometry(1000, 1000, 50, 50);
var waterMat = new THREE.MeshPhongMaterial({
color: 0x00aeff,
emissive: 0x0023b9,
shading: THREE.FlatShading,
shininess: 60,
specular: 30,
transparent: true
});
for (var j = 0; j < waterGeo.vertices.length; j++) {
waterGeo.vertices[j].x = waterGeo.vertices[j].x + ((Math.random() * Math.random()) * 30);
waterGeo.vertices[j].y = waterGeo.vertices[j].y + ((Math.random() * Math.random()) * 20);
}
var waterObj = new THREE.Mesh(waterGeo, waterMat);
waterObj.rotation.x = -Math.PI / 2;
scene.add(waterObj);
/* Floor */
var floorGeo = new THREE.PlaneGeometry(1000, 1000, 50, 50);
var floorMat = new THREE.MeshPhongMaterial({
color: 0xe9b379,
emissive: 0x442c10,
shading: THREE.FlatShading
});
for (var j = 0; j < floorGeo.vertices.length; j++) {
floorGeo.vertices[j].x = floorGeo.vertices[j].x + ((Math.random() * Math.random()) * 30);
floorGeo.vertices[j].y = floorGeo.vertices[j].y + ((Math.random() * Math.random()) * 20);
floorGeo.vertices[j].z = floorGeo.vertices[j].z + ((Math.random() * Math.random()) * 20);
}
var floorObj = new THREE.Mesh(floorGeo, floorMat);
floorObj.rotation.x = -Math.PI / 2;
floorObj.position.y = -75;
scene.add(floorObj);
/*
* Scene render
**/
var count = 0;
function render() {
requestAnimationFrame(render);
var particle, i = 0;
for (var ix = 0; ix < 50; ix++) {
for (var iy = 0; iy < 50; iy++) {
waterObj.geometry.vertices[i++].z = (Math.sin((ix + count) * 2) * 3) +
(Math.cos((iy + count) * 1.5) * 6);
waterObj.geometry.verticesNeedUpdate = true;
}
}
count += 0.05;
renderer.render(scene, camera);
}
render();
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
I don't think you're going to get the effect you want.
How do you generate the first image? I assume you just made fuzzy oval in photoshop and picked "hard light"?
If you want the same thing in three.js you'll need to generate a fuzzy oval and apply it in 2d using a post processing effect in three.js
You could generate such an oval by making a 2nd scene in three.js, adding the lights and shining them on a black plane that has no waves that's at the same position as the water is in the original scene. Render that to a rendertarget. You probably want only the spotlight and maybe point light in that scene. In your current scene remove the spotlight for sure. Render that to another render target.
When you're done combine the scenes using a post processing effect that implements hard light
// pseudo code
vec3 partA = texture2D(sceneTexture, texcoord);
vec3 partB = texture2D(lightTexture, texcoord);
vec3 line1 = 2.0 * partA * partB;
vec3 line2 = 1.0 - (1.0 - partA) * (1.0 - partB);
gl_FragCoord = vec4(mix(line2, line1, step(0.5, partA)), 1);
I ended up doing it in the following way thanks to gman's excellent answer. View the code snippet below to see it in action.
As gman described:
I created a WebGLRenderTarget to which the scene is rendered to.
The WebGLRenderTarget is then passed to the ShaderMaterial's uniforms as a texture, together with the window.innerWidth, window.innerHeight and color.
The respective texture coordinates, in relation to the current fragment, are calculated by dividing gl_FragCoord by the window's width and height.
The fragment can now sample what is on screen from the WebGLRenderTarget texture and combine that with the color of the object to output the correct gl_FragColor.
So far it works great. The only thing I am currently looking into is to create a separate scene containing only the objects that are necessary for blending, perhaps cloned. I assume that would be more performant. Currently I am toggling the visibility of the object to be blended in the render loop, before and after it is sent to the WebGLRenderTarget. For a larger scene with more objects, that probably doesn't make much sense and would complicate things.
var conf = {
'Color A': '#cc6633',
'Color B': '#0099ff'
};
var GUI = new dat.GUI();
var A_COLOR = GUI.addColor(conf, 'Color A');
A_COLOR.onChange(function(val) {
A_OBJ.material.uniforms.color = {
type: "c",
value: new THREE.Color(val)
};
A_OBJ.material.needsUpdate = true;
});
var B_COLOR = GUI.addColor(conf, 'Color B');
B_COLOR.onChange(function(val) {
B_OBJ.material.uniforms.color = {
type: "c",
value: new THREE.Color(val)
};
B_OBJ.material.needsUpdate = true;
});
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x888888);
renderer.setSize(window.innerWidth, window.innerHeight);
var target = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {format: THREE.RGBFormat});
document.body.appendChild(renderer.domElement);
camera.position.set(0, 0, 50);
camera.lookAt(scene.position);
var A_GEO = new THREE.PlaneGeometry(20, 20);
var B_GEO = new THREE.PlaneGeometry(20, 20);
var A_MAT = new THREE.ShaderMaterial({
uniforms: {
color: {
type: "c",
value: new THREE.Color(0xcc6633)
}
},
vertexShader: document.getElementById('vertexShaderA').innerHTML,
fragmentShader: document.getElementById('fragmentShaderA').innerHTML
});
var B_MAT = new THREE.ShaderMaterial({
uniforms: {
color: {
type: "c",
value: new THREE.Color(0x0099ff)
},
window: {
type: "v2",
value: new THREE.Vector2(window.innerWidth, window.innerHeight)
},
target: {
type: "t",
value: target
}
},
vertexShader: document.getElementById('vertexShaderB').innerHTML,
fragmentShader: document.getElementById('fragmentShaderB').innerHTML
});
var A_OBJ = new THREE.Mesh(A_GEO, A_MAT);
var B_OBJ = new THREE.Mesh(B_GEO, B_MAT);
A_OBJ.position.set(-5, -5, 0);
B_OBJ.position.set(5, 5, 0);
scene.add(A_OBJ);
scene.add(B_OBJ);
function render() {
requestAnimationFrame(render);
B_OBJ.visible = false;
renderer.render(scene, camera, target, true);
B_OBJ.visible = true;
renderer.render(scene, camera);
}
render();
body { margin: 0 }
canvas { display: block }
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.min.js"></script>
<script type="x-shader/x-vertex" id="vertexShaderA">
uniform vec3 color;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentShaderA">
uniform vec3 color;
void main() {
gl_FragColor = vec4(color, 1.0);
}
</script>
<script type="x-shader/x-vertex" id="vertexShaderB">
uniform vec3 color;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentShaderB">
uniform vec3 color;
uniform vec2 window;
uniform sampler2D target;
void main() {
vec2 targetCoords = gl_FragCoord.xy / window.xy;
vec4 a = texture2D(target, targetCoords);
vec4 b = vec4(color, 1.0);
vec4 multiply = 2.0 * a * b;
vec4 screen = 1.0 - 2.0 * (1.0 - a) * (1.0 - b);
gl_FragColor = vec4(mix(screen, multiply, step(0.5, a)));
}
</script>

WebGl scanner effect

Hey I have just seen this short video: https://vine.co/v/hxHz5H2Q07q and I am wondering how to achieve this scanning effect.
For start there are 2 groups needed: one holding cubes with material and a corresponding one with the same cubes but just wireframed, so it is possible to get these layers.
But how to make whe wired one appear in such "scanner" way? Is it via shaders or maybe there is some masking method in threejs for moving the mask across the rendered and displaying the given object linked with the mask?
Copied from the description of the original demo
This experiment was inspired by real-world projection mapping onto physical shapes. The field is created by random generation and merged into a single geometry. It is duplicated and the copy is rendered with a ShaderMaterial that looks at a uniform point and makes most pixels transparent other than those near the light point. Moving the point through the field creates the appearance of scanning.
So, make some geometry, draw it twice, once flat shaded, ones wireframe. For the wireframe version make a custom shader that takes a single point uniform. If the vertex (or pixel) is close to that point draw a color, if not draw transparent (or discard).
var camera = new THREE.PerspectiveCamera( 20, 1, 1, 10000 );
camera.position.z = 1800;
var scene = new THREE.Scene();
var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
var flatMaterial = new THREE.MeshLambertMaterial( { color: 0x606060, shading: THREE.FlatShading } );
var geometry = new THREE.IcosahedronGeometry( 200, 1 );
//var wireMaterial = new THREE.MeshBasicMaterial( { color: 0x00FF00, wireframe:true } );
var uniforms = {
color: { type: "c", value: new THREE.Color(0x00FF00) },
lightPos: { type: "v3", value: new THREE.Vector3(0, 0, 0) },
range: { type: "f", value: 150, },
};
var wireMaterial = new THREE.ShaderMaterial({
wireframe: true,
uniforms: uniforms,
attributes: {
},
vertexShader: document.getElementById('vertexshader').text,
fragmentShader: document.getElementById('fragmentshader').text,
depthTest: true,
transparent: true,
});
var mesh = new THREE.Mesh( geometry, flatMaterial );
scene.add( mesh );
var mesh = new THREE.Mesh( geometry, wireMaterial );
scene.add( mesh );
renderer = new THREE.WebGLRenderer( { antialias: true } );
document.body.appendChild( renderer.domElement );
function resize() {
var canvas = renderer.context.canvas
var width = canvas.clientWidth;
var height = canvas.clientHeight;
if (width != canvas.width || height != canvas.height) {
renderer.setSize( width, height, false );
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
}
function render() {
resize();
var time = Date.now() * 0.001;
uniforms.lightPos.value.x = Math.sin(time) * 200;
uniforms.lightPos.value.y = Math.cos(time) * 200;
camera.lookAt( scene.position );
renderer.render( scene, camera );
requestAnimationFrame( render );
}
render();
html, body {
margin: 0px;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<body>
</body>
<script type="not-js" id="vertexshader">
varying vec4 v_position;
void main() {
vec4 pos = vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewMatrix * pos;
v_position = modelMatrix * pos;
}
</script>
<script type="not-js" id="fragmentshader">
uniform vec3 color;
uniform vec3 lightPos;
uniform float range;
varying vec4 v_position;
void main() {
float distanceToLight = distance(lightPos, v_position.xyz);
gl_FragColor = mix(vec4(color, 1), vec4(0,0,0,0), step(range, distanceToLight));
}
</script>
You have a bunch of parallel rays maybe 100 or however many you want, that shoot towards your scene and intersect it and at the same time move up the y direction.

Categories