Three.js 中的硬光材质混合模式?
Posted
技术标签:
【中文标题】Three.js 中的硬光材质混合模式?【英文标题】:Hard Light material blending mode in Three.js? 【发布时间】:2016-06-02 22:59:26 【问题描述】:我目前正在使用 Three.js 提供的MeshPhongMaterial
用基本的水创建一个简单的场景。我希望水材质具有Hard Light
混合模式,可以在 Photoshop 等应用程序中找到。如何实现右侧下方的Hard Light
混合模式?
上面图片的右半部分在 Photoshop 中设置为Hard Light
。我正在尝试在 Three.js 中重新创建 Hard Light
混合模式。
我遇到的一条线索是完全重新实现MeshPhongMaterial
的片段和顶点着色器,但这需要我一些时间,因为我对此很陌生。
方法在 Three.js 中为材质实现Hard Light
混合模式是什么?
/*
* 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>
【问题讨论】:
将灯光移近表面。 @gaitat,我的问题是如何为 MeshPhongMaterial 重新创建在 Photoshop 等应用程序中看到的强光混合模式。据我所知,这是一个乘法+屏幕操作。灯光的位置无关紧要。 你试过什么?如果你提高聚光灯(0.9)和镜面反射分量(60),它似乎更接近我。 @holtavoltMeshPhongMaterial
的基本外观是正确的,但我正在寻找的是 Photoshop 中的混合模式 Hard Light
。 Hard Light
的效果,如我帖子的右半部分所示,提供了一些透明度(乘法+屏幕操作),但保留了大部分蓝色。公式可以在这里查看en.wikipedia.org/wiki/Blend_modes#Hard_Light我对着色器很陌生,所以如果在 Three.js 中制作自定义着色器是方法,我很想得到一些指点。谢谢!
好的 - 您是否尝试设置不透明度?当我添加不透明度:0.90(并如前所述调整照明)时,我再次得到更接近硬光混合的东西。
【参考方案1】:
我不认为你会得到你想要的效果。
如何生成第一张图片?我假设您只是在 Photoshop 中制作了模糊的椭圆形并选择了“强光”?
如果你想在 three.js 中做同样的事情,你需要生成一个模糊的椭圆并在 2d 中使用 three.js 中的后期处理效果
您可以通过在 three.js 中制作第二个场景来生成这样的椭圆形,添加灯光并将它们照射在没有波浪的黑色平面上,该平面与原始场景中的水位置相同。将其渲染到渲染目标。您可能只想要那个场景中的聚光灯和点光源。在您当前的场景中,一定要移除聚光灯。将其渲染到另一个渲染目标。
完成后,使用实现强光的后期处理效果组合场景
// 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);
【讨论】:
优秀的答案!我创建了一个WebGLRenderTarget
,稍微修改了代码并使用ShaderChunk
结合ShaderMaterial
添加我修改后的MeshPhongMaterial
。我会尽快跟进我的工作版本的代码——以防它帮助其他人。非常感谢!【参考方案2】:
感谢gman's excellent answer,我最终以以下方式完成了它。查看下面的代码 sn-p 以查看它的运行情况。
正如gman 所述:
-
我创建了一个
WebGLRenderTarget
,场景被渲染到。
然后WebGLRenderTarget
与window.innerWidth
、window.innerHeight
和color
一起作为纹理传递给ShaderMaterial
的制服。
与当前片段相关的各个纹理坐标是通过将gl_FragCoord
除以窗口的宽度和高度来计算的。
片段现在可以从WebGLRenderTarget
纹理中对屏幕上的内容进行采样,并将其与对象的color
结合以输出正确的gl_FragColor
。
到目前为止效果很好。我目前唯一正在研究的是创建一个单独的场景,其中仅包含混合所需的对象,也许是克隆的。我认为这会更高效。目前我正在切换要在渲染循环中混合的对象的可见性,在它被发送到WebGLRenderTarget
之前和之后。对于具有更多对象的更大场景,这可能没有多大意义并且会使事情复杂化。
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>
【讨论】:
以上是关于Three.js 中的硬光材质混合模式?的主要内容,如果未能解决你的问题,请参考以下文章