javascript - Hard Light material blending mode in Three.js? - Stack Overflow

I am currently using the MeshPhongMaterial provided by Three.js to create a simple scene with basic wat

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 e across is to pletely 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=".js/r73/three.min.js"></script>

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 e across is to pletely 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./ajax/libs/three.js/r73/three.min.js"></script>

Share Improve this question edited Feb 25, 2016 at 17:52 Jason asked Feb 21, 2016 at 15:33 JasonJason 5,5852 gold badges36 silver badges43 bronze badges 6
  • move the light closer to the surface. – gaitat Commented Feb 21, 2016 at 23:56
  • @gaitat, my question is how I would recreate the hard light blend mode seen in applications such as Photoshop for a MeshPhongMaterial. That is a multiply + screen operation to my knowledge. The light's position is not relevant. – Jason Commented Feb 22, 2016 at 7:26
  • What have you tried? If you boost the spotlight (0.9) and specular ponents (60), it seems closer to me. – holtavolt Commented Feb 23, 2016 at 17:38
  • @holtavolt the basic look of the MeshPhongMaterial is right, but it's the blending mode, Hard Light in Photoshop, that I am looking for. The effect of Hard Light, as is shown in the right half of the image in my post, provides some transparency (multiply + screen operation), but retains most of the blue colors. The formula can be viewed here en.wikipedia/wiki/Blend_modes#Hard_Light I am quite new to shaders, so if making a custom shader in Three.js is the way to do this, I would love to have some pointers. Thank you! – Jason Commented Feb 23, 2016 at 18:06
  • OK - did you try setting your opacity? When I add an opacity: .90 (and tweak the lighting as mentioned before), I again get something closer to your Hard Light blending. – holtavolt Commented Feb 24, 2016 at 2:04
 |  Show 1 more ment

2 Answers 2

Reset to default 3 +100

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 bine 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:

  1. I created a WebGLRenderTarget to which the scene is rendered to.
  2. The WebGLRenderTarget is then passed to the ShaderMaterial's uniforms as a texture, together with the window.innerWidth, window.innerHeight and color.
  3. The respective texture coordinates, in relation to the current fragment, are calculated by dividing gl_FragCoord by the window's width and height.
  4. The fragment can now sample what is on screen from the WebGLRenderTarget texture and bine 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 plicate 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./ajax/libs/dat-gui/0.5.1/dat.gui.js"></script>
<script src="https://cdnjs.cloudflare./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>

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744220446a4563754.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信