How to achieve an x-ray-style effect in three.js / webgl? Some sort of this
UPD
I need real-time render with this stuff, not a still image. This can be done with shaders, that change density in non-linear way on overlaps based on distance. I briefly understand theory, but have no practice, that is why I need help with this
This is the as Владимир Корнилов's example except I changed the shader a little.
I'm not sure what he was going for with the dot(vNormal, vNormel). Doing abs(dot(vNormal, vec3(0, 0, 1)) will give you something that is brighter when facing toward or away from the view. Making it 1.0 - abs(dot(vNormal, vec3(0, 0, 1)) will flip that so perpendicular to the view is brighter. Then add the pow and it looks better to me but I guess that's subjective
var human;
var $ = document.querySelector.bind(document);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
var renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setClearColor(0x000000, 1.0);
lookAt = scene.position;
lookAt.y = 15;
camera.lookAt(lookAt);
document.body.appendChild(renderer.domElement);
var customMaterial = new THREE.ShaderMaterial(
{
uniforms: {
p: { type: "f", value: 2 },
glowColor: { type: "c", value: new THREE.Color(0x84ccff) },
},
vertexShader: $('#vertexShader').text,
fragmentShader: $('#fragmentShader').text,
side: THREE.DoubleSide,
blending: THREE.AdditiveBlending,
transparent: true,
depthWrite: false
});
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('http://greggman.github.io/doodles/assets/woman.dae', function (collada) {
dae = collada.scene;
dae.traverse( function ( child ) {
if (child instanceof THREE.Mesh) {
console.log(child);
child.material = customMaterial;
}
} );
dae.scale.x = 0.2;
dae.scale.y = 0.2;
dae.scale.z = 0.2;
human = dae;
scene.add(human);
});
function resize() {
var canvas = renderer.domElement;
var width = canvas.clientWidth;
var height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
}
// call the render function
function render(time) {
time *= 0.001;
resize();
camera.position.x = -20 * (Math.cos(time));
camera.position.z = (20 * (Math.sin(time)));
camera.position.y = 20;
camera.lookAt(lookAt);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r123/three.min.js"></script>
<script src="//greggman.github.io/doodles/js/three/js/loaders/ColladaLoader.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
uniform float p;
varying float intensity;
void main()
{
vec3 vNormal = normalize( normalMatrix * normal );
intensity = pow(1.0 - abs(dot(vNormal, vec3(0, 0, 1))), 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>
<style>
html, body {
margin: 0;
overflow: hidden;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
}
</style>
Ok, got acceptable result with this:
<!DOCTYPE html>
<html>
<head>
<title>X-ray</title>
<script type="text/javascript" src="js/three.js/build/three.js"></script>
<script type="text/javascript" src="js/three.js/examples/js/loaders/OBJLoader.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="js/stats.min.js"></script>
<script type="text/javascript" src="js/three.js/examples/js/renderers/SVGRenderer.js"></script>
<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>
<!-- 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>
<style>
body {
/* set margin to 0 and overflow to hidden, to go fullscreen */
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="Stats-output">
</div>
<!-- Div which will hold the Output -->
<div id="WebGL-output">
</div>
<!-- Javascript code that runs our Three.js examples -->
<script type="text/javascript">
// once everything is loaded, we run our Three.js stuff.
$(function () {
var mouseX = 0, mouseY = 0;
var human;
camstep = 0;
var stats = initStats();
// create a scene, that will hold all our elements such as objects, cameras and lights.
var scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render and set the size
var renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setClearColor(0x000000, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;
renderer.shadowMapType = THREE.PCFShadowMap;
materialCameraPosition = camera.position.clone();
materialCameraPosition.z += 10;
// position and point the camera to the center of the scene
camera.position.x = -10;
camera.position.y = 0;
camera.position.z = 15;
lookAt = scene.position;
lookAt.y = 15;
camera.lookAt(lookAt);
// add subtle ambient lighting
var ambientLight = new THREE.AmbientLight(0x0c0c0c);
//scene.add(ambientLight);
// add the output of the renderer to the html element
$("#WebGL-output").append(renderer.domElement);
var customMaterial = new THREE.ShaderMaterial(
{
uniforms: {
"c": { type: "f", value: 1.0 },
"p": { type: "f", value: 3 },
glowColor: { type: "c", value: new THREE.Color(0x84ccff) },
viewVector: { type: "v3", value: materialCameraPosition }
},
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent,
side: THREE.FrontSide,
blending: THREE.AdditiveBlending,
transparent: true,
//opacity: 0.5,
depthWrite: false
});
var manager = new THREE.LoadingManager();
manager.onProgress = function (item, loaded, total) {
console.log(item, loaded, total);
};
var loader = new THREE.OBJLoader(manager);
loader.load('body_anatomy3.obj', function (object) {
console.log(object);
object.traverse(function (child) {
if (child instanceof THREE.Mesh) {
console.log(child);
child.material = customMaterial;
}
});
object.position.y = 4;
object.scale.x = 0.01;
object.scale.y = 0.01;
object.scale.z = 0.01;
human = object;
scene.add(human);
});
// call the render function
var step = 0;
render();
function render() {
stats.update();
camstep += 0.02;
camera.position.x = -20 * (Math.cos(camstep));
camera.position.z = (20 * (Math.sin(camstep)));
camera.position.y = 20;
camera.lookAt(lookAt);
if (human) {
//human.rotation.y += 0.02;
materialCameraPosition = camera.position.clone();
materialCameraPosition.z += 10;
human.traverse(function (child) {
if (child instanceof THREE.Mesh) {
//console.log(child.material.uniforms.viewVector);
child.material.uniforms.viewVector.value =
new THREE.Vector3().subVectors(camera.position, human.position);
}
});
}
//sphere.material.uniforms.viewVector.value = new THREE.Vector3().subVectors(camera.position, sphere.position);
// render using requestAnimationFrame
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
// Align top-left
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
$("#Stats-output").append(stats.domElement);
return stats;
}
});
</script>
</body>
</html>
At the moment got close result with glow shader based on this demo http://stemkoski.github.io/Three.js/Shader-Glow.html
Related
I'm trying to implement the Voronoi shader in the Book of Shaders in three.js and cannot seem to wrap my head around why the mouse position is not having any effect on my visible output. (This is for the initial demo where the mouse moves and the Voronoi updates)
I'm logging the mouse position, checking that the uniform value is updating, yet the shader doesn't appear to be changing whatsoever on my end.
This is what I see (static image - no change at all) while animate is being called.
I'm seeing u_time update when I log it in animate so the callback is happening. The uniforms just don't appear to be updated and I thought I was updating them correctly.
Note - I followed this SO post regarding tracking mouse position as a basis, though I tried both that mouse position and the tweaked mouse I have below (to map from [-1,1]).
Full code:
<!--
* Based on Book of Shaders 12:
https://thebookofshaders.com/12/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>WebGL Demo - Voronoi</title>
<meta charset="utf-8">
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<script src="./libraries/threejs/three.min.js"></script>
<!-- shaders -->
<script type="x-shader/x-vertex" id="vertexShader">
void main() {
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
st.x *= u_resolution.x/u_resolution.y;
vec3 color = vec3(.0);
// Cell positions
vec2 point[5];
point[0] = vec2(0.83,0.75);
point[1] = vec2(0.60,0.07);
point[2] = vec2(0.28,0.64);
point[3] = vec2(0.31,0.26);
point[4] = u_mouse/u_resolution;
float m_dist = 1.; // minimum distance
// Iterate through the points positions
for (int i = 0; i < 5; i++) {
float dist = distance(st, point[i]);
// Keep the closer distance
m_dist = min(m_dist, dist);
}
// Draw the min distance (distance field)
color += m_dist;
// Show isolines
// color -= step(.7,abs(sin(50.0*m_dist)))*.3;
gl_FragColor = vec4(color,1.0);
}
</script>
</head>
<body></body>
<script>
let camera, scene, renderer;
let uniforms, mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.Camera();
camera.position.z = 1;
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor(0x000000, 1);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
uniforms = {
u_resolution: { type: 'vec2', value: new THREE.Vector2() },
u_mouse: { type: 'vec2', value: new THREE.Vector2() },
u_time: { type: 'float', value: 1.0 }
};
let vShader = document.getElementById("vertexShader").textContent;
let fShader = document.getElementById("fragmentShader").textContent;
let geometry = new THREE.PlaneGeometry(2, 2);
// give it a material
let material = new THREE.ShaderMaterial({
uniforms: uniforms,
fragmentShader: fShader,
vertexShader: vShader,
});
// and now create the mesh (geom+mat)
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
uniforms.u_time.value += 0.05;
renderer.render(scene, camera);
}
function onWindowResize(e) {
renderer.setSize(window.innerWidth, window.innerHeight);
uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;
}
document.onmousemove = function (e) {
uniforms.u_mouse.value.x = (e.clientX / window.innerWidth) * 2 - 1;//e.pageX / window.innerWidth;
uniforms.u_mouse.value.y = -(e.clientY / window.innerHeight) * 2 + 1;//e.pageY / window.innerHeight;
}
</script>
</html>
The line point[4] = u_mouse/u_resolution; probably doesn’t need to be divided by u_resolution because the Vector2 is already in the [-1, 1] range. You might just be getting very small values so the mouse movement is indistinguishable.
So I am trying to make a cloud generation script that when going forward, left, or right it generates clouds infinitely.
Here is an example of how it currently is, I would like it to generate clouds when moving left or right infinitely but currently it only generates clouds moving forwards infinitely. Arrows are provided in the example for better understanding. I apologize for not adding a image directly but I am unable to as I am a new user:
https://i.stack.imgur.com/4XzpZ.jpg
I would like to modify my script to make the clouds generate forward, left, and right.
Here is my script:
https://pastebin.com/raw/vkTVrybQ
// HTML:
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8" />
<title>cloud generation</title>
<link href="css/main.css" rel="stylesheet" type="text/css" />
<script src="js/ThreeWebGL.js"></script>
<script src="js/ThreeExtras.js"></script>
</head>
<body>
<script id="vs" type="x-shader/x-vertex">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script id="fs" type="x-shader/x-fragment">
uniform sampler2D map;
uniform vec3 fogColor;
uniform float fogNear;
uniform float fogFar;
varying vec2 vUv;
void main() {
float depth = gl_FragCoord.z / gl_FragCoord.w;
float fogFactor = smoothstep( fogNear, fogFar, depth );
gl_FragColor = texture2D( map, vUv );
gl_FragColor.w *= pow( gl_FragCoord.z, 20.0 );
gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );
}
</script>
<div class="container">
<canvas id="panel" width="10" height="1"></canvas>
</div>
<script type="text/javascript" src="js/script.js"></script>
</body>
</html>
// CSS:
*{
margin:0;
padding:0;
}
body {
color:#fff;
font:14px/1.3 Arial,sans-serif;
background-image: url(../images/sky.jpg);
}
.container {
height:1px;
}
// Javascript:
// inner variables
var canvas, ctx;
var camera, scene, renderer, meshMaterial, mesh, geometry, i, f;
var mouseX = 0, mouseY = 0;
var startTime = new Date().getTime();
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
if (window.attachEvent) {
window.attachEvent('onload', main_init);
} else {
if(window.onload) {
var curronload = window.onload;
var newonload = function() {
curronload();
main_init();
};
window.onload = newonload;
} else {
window.onload = main_init;
}
}
function main_init() {
// creating canvas and context objects
canvas = document.getElementById('panel');
var ctx = canvas.getContext('2d');
// preparing camera
camera = new THREE.Camera(30, window.innerWidth / window.innerHeight, 1, 5000);
camera.position.z = 6000;
// preparing scene
scene = new THREE.Scene();
// preparing geometry
geometry = new THREE.Geometry();
// loading texture
var texture = THREE.ImageUtils.loadTexture('../images/clouds.png');
texture.magFilter = THREE.LinearMipMapLinearFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
// preparing fog
var fog = new THREE.Fog(0x251d32, - 100, 5000);
// preparing material
meshMaterial = new THREE.MeshShaderMaterial({
uniforms: {
'map': {type: 't', value:2, texture: texture},
'fogColor' : {type: 'c', value: fog.color},
'fogNear' : {type: 'f', value: fog.near},
'fogFar' : {type: 'f', value: fog.far},
},
vertexShader: document.getElementById('vs').textContent,
fragmentShader: document.getElementById('fs').textContent,
depthTest: false
});
// preparing planeMesh
var planeMesh = new THREE.Mesh(new THREE.PlaneGeometry(64, 64));
for (i = 0; i < 10000; i++) {
planeMesh.position.x = Math.random() * 1000 - 500;
planeMesh.position.y = - Math.random() * Math.random() * 200 - 15;
planeMesh.position.z = i;
planeMesh.rotation.z = Math.random() * Math.PI;
planeMesh.scale.x = planeMesh.scale.y = Math.random() * Math.random() * 1.5 + 0.5;
THREE.GeometryUtils.merge(geometry, planeMesh);
}
mesh = new THREE.Mesh(geometry, meshMaterial);
scene.addObject(mesh);
mesh = new THREE.Mesh(geometry, meshMaterial);
mesh.position.z = - 10000;
scene.addObject(mesh);
// preparing new renderer and drawing it
renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// change positions by mouse
document.addEventListener('mousemove', onMousemove, false);
// change canvas size on resize
window.addEventListener('resize', onResize, false);
setInterval(drawScene, 30); // loop drawScene
}
function onMousemove(event) {
mouseX = (event.clientX - windowHalfX) * 0.3;
mouseY = (event.clientY - windowHalfY) * 0.2;
}
function onResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function drawScene() {
position = ((new Date().getTime() - startTime) * 0.1) % 10000;
camera.position.x += mouseX * 0.01;
camera.position.y += - mouseY * 0.01;
camera.position.z = - position + 10000;
renderer.render(scene, camera);
}
// ^ Please note I also use ThreeWebGL.js and ThreeExtras.js but these scripts are too large to paste here as they are APIS
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.
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>
I've created a custom shader in three.js to allow me to set individual vertices color, size and position.
Color and size work fine but when I update the vertex x,y or z it's not updated on screen.
JSFiddle
What am I missing?
Vertex shaders:
<script type="x-shader/x-vertex" id="vertexshader">
attribute float size;
attribute vec3 color;
varying vec3 vColor;
varying vec2 vUv;
void main()
{
vColor = color;
gl_PointSize = size;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
Fragment shader
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D texture;
varying vec2 vUv;
varying vec3 vColor;
void main()
{
vec4 color = vec4(vColor, 1);
gl_FragColor = color;
}
</script>
JS:
var container = document.getElementById('container');
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 100;
var scene = new THREE.Scene();
var vShader = document.getElementById('vertexshader').textContent;
var fShader = document.getElementById('fragmentshader').textContent;
var uniforms = {};
var attributes = {
color: { type: 'c', value: [] },
size: { type: 'f', value: [] }
};
var geometry = new THREE.Geometry();
for ( var i = 0; i < 100; i ++ )
{
var angle = Math.random() * Math.PI * 2;
var radius = 40 + (Math.random() * 5);
var vertex = new THREE.Vector3();
vertex.x = Math.cos(angle) * radius;
vertex.y = Math.sin(angle) * radius;
vertex.z = 0;
attributes.size.value[i] = Math.random() * 10;
attributes.color.value[i] = new THREE.Color( 0xff0000 );
geometry.vertices.push( vertex );
}
var material = new THREE.ShaderMaterial(
{
uniforms: uniforms,
attributes: attributes,
vertexShader: vShader,
fragmentShader: fShader,
transparent: true
});
var particleSystem = new THREE.PointCloud(geometry, material);
scene.add( particleSystem );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x292B30, 1 );
container.appendChild( renderer.domElement );
animate();
function animate()
{
requestAnimationFrame( animate );
render();
}
function render()
{
for (var i = geometry.vertices.length - 1; i >= 0; i--)
{
geometry.vertices[i].z = geometry.vertices[i].z - 0.5 ;
}
camera.lookAt( scene.position );
//particleSystem.geometry.__dirtyVertices = true;
renderer.render( scene, camera );
}
When modifying the vertex position, set the verticesNeedUpdate flag:
geometry.verticesNeedUpdate = true;
Updated fiddle: https://jsfiddle.net/1cmg0z2f/2/
Three.js r71, due to changes in ShaderMaterial the fiddle doesnt work with the new version.
I added a comment to the previous accepted answer but just in case here's an updated answer
geometry.attributes[attributeName].needsUpdate = true
eg
geometry.attributes.position.needsUpdate = true
keep in mind that this works for custom / shader attributes that you've added using setAttribute, which is what I was searching for before I found this QA.