我想在Three.js中使用ShaderMaterial,允许一些光线通过。正如我最近所了解到的,我需要的效果称为“子表面散射”。我找到了几个例子,但只有少数例子允许实时计算(没有额外的映射)。最接近的一个显示在此代码段中:
var container;
var camera, scene, renderer;
var sssMesh;
var lightSourceMesh;
var sssUniforms;
var clock = new THREE.Clock();
init();
animate();
function init() {
container = document.getElementById('container');
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 3000);
camera.position.z = 4;
camera.position.y = 2;
camera.rotation.x = -0.45;
scene = new THREE.Scene();
var boxGeometry = new THREE.CubeGeometry(0.75, 0.75, 0.75);
var lightSourceGeometry = new THREE.CubeGeometry(0.1, 0.1, 0.1);
sssUniforms = {
u_lightPos: {
type: "v3",
value: new THREE.Vector3()
}
};
var sssMaterial = new THREE.ShaderMaterial({
uniforms: sssUniforms,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragment_shader').textContent
});
var lightSourceMaterial = new THREE.MeshBasicMaterial();
sssMesh = new THREE.Mesh(boxGeometry, sssMaterial);
sssMesh.position.x = 0;
sssMesh.position.y = 0;
scene.add(sssMesh);
lightSourceMesh = new THREE.Mesh(lightSourceGeometry, lightSourceMaterial);
lightSourceMesh.position.x = 0;
lightSourceMesh.position.y = 0;
scene.add(lightSourceMesh);
renderer = new THREE.WebGLRenderer();
container.appendChild(renderer.domElement);
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
var delta = clock.getDelta();
var lightHeight = Math.sin(clock.elapsedTime * 1.0) * 0.5 + 0.7;
lightSourceMesh.position.y = lightHeight;
sssUniforms.u_lightPos.value.y = lightHeight;
sssMesh.rotation.y += delta * 0.5;
renderer.render(scene, camera);
}
body {
color: #ffffff;
background-color: #050505;
margin: 0px;
overflow: hidden;
}
<script src="http://threejs.org/build/three.min.js"></script>
<div id="container"></div>
<script id="fragment_shader" type="x-shader/x-fragment">
varying vec3 v_fragmentPos;
varying vec3 v_normal;
uniform vec3 u_lightPos;
void main(void)
{
vec3 _LightColor0 = vec3(1.0,0.5,0.5);
float _LightIntensity0 = 0.2;
vec3 translucencyColor = vec3(0.8,0.2,0.2);
vec3 toLightVector = u_lightPos - v_fragmentPos;
float lightDistanceSQ = dot(toLightVector, toLightVector);
vec3 lightDir = normalize(toLightVector);
float ndotl = max(0.0, dot(v_normal, lightDir));
float inversendotl = step(0.0, dot(v_normal, -lightDir));
vec3 lightColor = _LightColor0.rgb * ndotl / lightDistanceSQ * _LightIntensity0;
vec3 subsurfacecolor = translucencyColor.rgb * inversendotl / lightDistanceSQ * _LightIntensity0;
vec3 final = subsurfacecolor + lightColor;
gl_FragColor=vec4(final,1.0);
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec3 v_fragmentPos;
varying vec3 v_normal;
void main()
{
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
v_fragmentPos = (modelMatrix * vec4( position, 1.0 )).xyz;
v_normal = (modelMatrix * vec4( normal, 0.0 )).xyz;
gl_Position = projectionMatrix * mvPosition;
}
</script>
上面的代码适用于简单的几何体(在这种情况下是一个立方体)。但它在更复杂的网格上绘制了一些奇怪的黑色面孔。最后的目标是“iluminate”高度图地形,但应用相同的代码,我明白了:
正如你所看到的,“玻璃效应”在照亮区域非常好,但在较暗的区域(光线不能很好地到达)它会画出一些我不知道的全黑面孔如何避免。
我认为自己是一个中等级别的Three.js用户,但这是我第一次使用着色器,并且不那么容易,公平。每次我在着色器代码上更改某些内容时,结果都是视觉垃圾。我改变的唯一一件事是浅色(正面和背面)。我也尝试增加光线强度,但这不起作用,它只会“烧掉”更多的着色器,使更多的黑色面孔出现。
有人能指出我正确的方向吗?这个效果在第一个地方叫什么?我甚至不知道如何在WebGL资源上搜索它。任何帮助将不胜感激!
编辑:似乎让地形更厚(Z刻度更高)可以解决问题。也许这与光线对着脸的角度有关?当光进入立方体时,也会在原始片段中发生这种情况(您可以在帧中看到完整的黑色面部)。或许我只是胡说八道。这很容易成为我多年来遇到的最难的代码,而且只有10行!我只是想看到着色器在原始比例下看起来和在较厚的一样看起来一样好。但是这个公式所涉及的物理学的复杂性超出了我的范围。
答案 0 :(得分:1)
在像素着色器中有一些地方可能会将零除以零,这通常显示为黑色(第3行和第4行):
float ndotl = max(0.0, dot(v_normal, lightDir));
float inversendotl = step(0.0, dot(v_normal, -lightDir));
vec3 lightColor = _LightColor0.rgb * ndotl / lightDistanceSQ * _LightIntensity0;
vec3 subsurfacecolor = translucencyColor.rgb * inversendotl / lightDistanceSQ * _LightIntensity0;
vec3 final = subsurfacecolor + lightColor;
ndotl 和 inversendotl 的值都可以为0(通常,因为在两种情况下都会将它们钳制)。然后可以将后续项中的任何一项除以零,将它们渲染为黑色。尝试注释 subsurfacecolor 或 lightColor 以隔离有缺陷的(可能是两者)。
max()只需要一个小的增量即可为非零: max(0.0001,x)
step()可以通过将其替换为静态分支来解决,这也可以摆脱额外的点积,因为你只是否定了一个向量,结果就是相反的。