法向量修复

其实到目前为止,我们做的 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 不太一样。为什么?

在顶点着色器里,法向量的变换要注意以下几点:

  1. 法向量主要是用于表示表面的方向
  2. 表面的方向不受平移的影响。例如,如果一个平面整体移动了,它的法向量也不会改变,只不过是位置变了。
  3. 因而,在法向量变换中,我们经常会忽略平移,只需要考虑缩放和旋转就行了。
  4. 法向量经常表示为(x,y,z,0);
  5. 当应用变换矩阵的时候,因为第四维度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>

可以看到,顺滑了很多:

最后修改:2024 年 08 月 14 日
收款不要了,给孩子补充点点赞数吧