法向量修复
其实到目前为止,我们做的 3D 模型都有一个一直存在的问题。我也是在完成了光照模型之后发现了这个问题,特此记录为一篇文章:那就是所使用的法向量normal
不是正确的,我也是在调整图案的时候发现的:
修复法向量朝向
比如我现在把上一章节的代码的geometry
换成另一种几何体:TorusKnotGeometry
。
const geometry = new THREE.TorusKnotGeometry(1.2, 0.4, 128, 32);
并且给它一个动画循环让它转起来,在update
里书写这段逻辑:
this.update(() => {
...,
const t = this.clock.elapsedTime;
mesh.rotation.y = 2 * t;
})
注意到了吗,边缘光越来越大了,因为光在随着物体进行旋转,但是事实并不是如此,按照正常的逻辑,光应该是静止不动的。
为什么会这样?因为法向量并没有考虑到物体的变换,既然如此,那我们就应该对法向量也应用变换。
我们在顶点着色器上,对normal
法向量也进行物体变换:
vUv=uv;
// vNormal=normal;
vNormal = (modelMatrix * vec4(normal, 0.)).xyz;
如果前面你仔细观察了代码,那么细心的你就会发现,这里与变换矩阵相乘向量的第四维度w
是 0,跟之前的position
的 1 不太一样。为什么?
在顶点着色器里,法向量的变换要注意以下几点:
- 法向量主要是用于表示表面的方向
- 表面的方向不受平移的影响。例如,如果一个平面整体移动了,它的法向量也不会改变,只不过是位置变了。
- 因而,在法向量变换中,我们经常会忽略平移,只需要考虑缩放和旋转就行了。
- 法向量经常表示为(x,y,z,0);
- 当应用变换矩阵的时候,因为第四维度
w
是 0,故而平移部分不会影响到法向量。
如果!我一定要把w
设置为 1 呢?
那就会发生法向量偏移的现象,但是法向量是始终垂直于物体表面的,不应该发生偏转
看一下上面这张图的说明大概就能懂了~
现在我们来看一下修改之后的内容:
边缘光的大小恢复正常了,问题顺利解决!
修复网格状图案
尽管可能非常不明显,但是实际是是真的存在的一个问题:
当我把 IBL 的光照强度扣到 0.2,这个扭曲形状的表面会有一些网格状的图案。
why?
因为,法向量的长度并不总是 1.
在每个顶点之间,varying
变量是受值影响的,vNormal
自然也不例外,当我们对两个单位向量进行插值的时候,得到的向量长度不一定就是 1。
比如看这张图:
我要怎么修复这个问题?很简单的,在片元着色器里对法向量再一次进行归一化就可以了。
先把之前在main
主函数写的vNormal
批量换成normal
,然后再在uv
的下方声明归一化之后的normal
就可以了。
我在这里贴出来全部的代码吧:
<style>
body {
margin: 0;
background: black;
}
</style>
<div id="sketch"></div>
<script src="https://unpkg.com/kokomi.js@1.9.78/build/kokomi.umd.js"></script>
<script src="https://unpkg.com/three@0.154.0/build/three.min.js"></script>
<script>
class Sketch extends kokomi.Base {
create() {
this.camera.position.set(0, 0, 5);
new kokomi.OrbitControls(this);
// const geometry = newTorusKnotGeometry THREE.SphereGeometry(2, 64, 64);
const geometry = new THREE.TorusKnotGeometry(1.2, 0.4, 128, 32);
const envmap = new THREE.CubeTextureLoader().load([
"https://cdn.jsdelivr.net/gh/ArisaTaki/MyBlogPic@image/img/px.png", // 正X
"https://cdn.jsdelivr.net/gh/ArisaTaki/MyBlogPic@image/img/nx.png", // 负X
"https://cdn.jsdelivr.net/gh/ArisaTaki/MyBlogPic@image/img/py.png", // 正Y
"https://cdn.jsdelivr.net/gh/ArisaTaki/MyBlogPic@image/img/ny.png", // 负Y
"https://cdn.jsdelivr.net/gh/ArisaTaki/MyBlogPic@image/img/pz.png", // 正Z
"https://cdn.jsdelivr.net/gh/ArisaTaki/MyBlogPic@image/img/nz.png", // 负Z
]);
const material = new THREE.ShaderMaterial({
vertexShader: /* glsl */ `
uniform float iTime;
uniform vec3 iResolution;
uniform vec4 iMouse;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vWorldPosition;
void main(){
vec3 p=position;
gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.);
vUv=uv;
// vNormal=normal;
vNormal = (modelMatrix * vec4(normal, 0.)).xyz;
vWorldPosition=(modelMatrix*vec4(p,1)).xyz;
}
`,
fragmentShader: /* glsl */ `
uniform float iTime;
uniform vec3 iResolution;
uniform vec4 iMouse;
uniform samplerCube iChannel0;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vWorldPosition;
float fresnel(float bias,float scale,float power,vec3 I,vec3 N)
{
return bias+scale*pow(1.-dot(I,N),power);
}
void main(){
vec2 uv=vUv;
vec3 normal = normalize(vNormal);
vec3 col=vec3(0.);
vec3 objectColor=vec3(1.);
vec3 lightColor=vec3(.875,.286,.333);
// 环境光
float ambIntensity=.2;
vec3 ambient=lightColor*ambIntensity;
col+=ambient*objectColor;
// 漫反射
vec3 lightPos=vec3(0., 0., 5.);
vec3 lightDir=normalize(lightPos-vWorldPosition);
float diff=dot(normal,lightDir);
diff=max(diff,0.);
vec3 diffuse=lightColor*diff;
col+=diffuse*objectColor;
// 镜面反射
// vec3 reflectDir=reflect(-lightDir,normal);
vec3 viewDir=normalize(cameraPosition-vWorldPosition);
// float spec=dot(viewDir,reflectDir);
vec3 halfVec = normalize(lightDir + viewDir);
float spec = dot(normal, halfVec);
spec=max(spec,0.);
float shininess=32.;
spec=pow(spec,shininess);
vec3 specular=lightColor*spec;
col+=specular*objectColor;
// IBL镜面反射
// 光照强度
float iblIntensity=.2;
// 反射光方向向量
vec3 iblCoord=normalize(reflect(-viewDir,normal));
vec3 ibl = textureCube(iChannel0, iblCoord).xyz;
vec3 iblLight = ibl * iblIntensity;
col += iblLight * objectColor;
// 菲涅尔反射
vec3 frenselColor=vec3(1.);
float frenselIntensity=.6;
float fres = fresnel(0.,1.,5.,viewDir,normal);
vec3 frenselLight = frenselColor * fres * frenselIntensity;
col += frenselLight * objectColor;
gl_FragColor=vec4(col,1.);
}
`,
uniforms: {
iChannel0: { value: envmap },
},
});
const mesh = new THREE.Mesh(geometry, material);
this.scene.add(mesh);
const uj = new kokomi.UniformInjector(this);
material.uniforms = {
...material.uniforms,
...uj.shadertoyUniforms,
};
this.update(() => {
const t = this.clock.elapsedTime;
mesh.rotation.y = 0.2 * t;
uj.injectShadertoyUniforms(material.uniforms);
});
}
}
const sketch = new Sketch("#sketch");
sketch.create();
</script>
可以看到,顺滑了很多: