Blinn-Phong反射模型实践(web实现)

Blinn-Phong反射模型实践(web实现)

games101 第四次作业

最终完成带贴图的 Blinn-Phong 模型,产生光照效果

展示图

完成了

  1. 不带贴图的 Blinn-Phone 反射模型
  2. 带贴图的模型,但是纹理映射应用在顶点着色器上
  3. 带贴图的模型,纹理映射在片元着色器上

Blinn-Phone 光照模型

光照分为三种,分别为环境光,漫反射光和类镜面反射的高光。这分别对应三种反射,当光照射在物体表面,物体表面会发生相应的反射,将光反射到人眼当中,这样,人眼才能看见物体。

环境光简单理解为任何地方都有的一种光,光的颜色和强度相同,当然,现实可不是这样;漫反射在初中学过,在物体表面某个点发生漫反射后,从任何地方都能看到这个点且颜色强度都一样;高光反射类似光照到镜子上产生的高亮的效果。设置进入人眼的光为 L,漫反射为 Ld,高光反射为 Ls,环境反射为 La,可以有公式

\[\begin{equation}
L = L_a+L_d+Ls
\end{equation}
\]

环境反射

环境光由于任何地方的光照强度相同,因此有公式

\[L_a = k_a \times I_a
\]

ka 代表了当前影响因子,可以令其为颜色,Ia 是光照强度,所有光照强度一致

环境光

该图是仅仅使用环境光和贴图产生的效果,Ia 设置为了 0.39,ka 设置为 vec3(1.0, 1.0, 1.0)。

漫反射

初中物理可以了解到,漫反射光强度一样,这里定义的漫反射光,从任何地方看向反射的点,强度都一样

由下图可以看到,I 表示原始光照强度,在三维空间中,光照强度以球面的形式向外扩散,因此强度衰减与球面积有关,假如光照与物体表面的点距离为 r,则在点处的光强为I/r^2 ;光从正面照向一个物体和从侧面照向一个物体强度是不同的,从正面照强度最大,可以得到当前点应角度产生的影响为n*l;如果 n*l为负值,说明光在背面,无法被看到,因此有了下述图片展示的公式

漫反射

Ld 表示漫反射强度,kd 表示影响因子,可以为颜色,I 为初始光照强度,r 为光源距离物体点的距离,n 为当前点的法向量,l 为点到光源的方向向量。将环境反射和漫反射加入程序中可得

漫反射1

单独和环境反射进行对比可以发现,牛牛变得更有轮廓了

高光反射

高光反射最为复杂,最重要的一点,当反射角与入射角大小一致时,光照强度最大。首先依下图所示定义多个变量,I 为点到光的方向向量,n 为点的法向量,v 为点到视角的方向向量。现在,我们知道,反射角与入射角一致时,强度最大,若此时反射角设为入射角大小,那么 v 与反射线的夹角越大,损失的光照越多,而 v 与反射线夹角可以转换为 n 与 h 的夹角。

h 叫半城向量,实际上就是 I 向量与 v 向量的角平分线的方向向量。此时因为 n 和 h 的夹角产生的光照损失值为 n * h,其他部分与漫反射一致。I / r^2表示光照随距离的缩减,ks 表示影响因子

高光反射

将上述三种反射集合起来形成了 Blinn-Phone 反射模型,和前面两种对比,有了明显的高光

高光反射2

顶点着色器和片元着色器

在顶点着色器中,会遍历每个顶点的属性进行处理,此时的运算均是三角顶点的运算。WebGL 提供了 Shader 的加载,从顶点着色器中可以定义 vary 变量类型,并在片元着色器中接收

  • 如果传输的是顶点坐标,那么在片元着色器中接收的是三角形内部像素点的顶点坐标
  • 如果传输的是法向量,那么在片元着色器中接收到的是三角形内部经过插值后的法向量
  • 如果传输的是顶点颜色,那么在片元着色器中接收到的是颜色插值
  • 其他均如此,在片元着色器中会经过插值计算后产生逐像素的插值

逐顶点计算的反射代码

// 实现了自定义的颜色的 blinn-phong reflection
const vertexShader = `
// vec3 normal, uv,
varying vec3 vColor;  
void main(){
    // Ld 漫反射
    vec3 lightPoint = vec3(3.0, 4.0, -3.0);
    vec3 l = normalize(lightPoint - position);
    float radius1 = distance(lightPoint, position);
    // Kd
    vec3 LdColor = vec3(1.0, 0.5, 0.7);
    float Id = 1.0;
    vec3 Ld = LdColor*(Id/radius1)*max(0.0, dot(normal,l));
    
    // La 环境光
    vec3 LaColor = vec3(1.0, 1.0, 1.0);
    float Ia = 0.2;
    // vec3 La = LaColor*Ia;
    // 用自己的颜色
    vec3 La = LdColor*Ia;
    
    // Ls 镜面反射光
    vec3 LsColor = vec3(1.0, 1.0, 1.0);
    float Is =1.0;
    vec3 vs = normalize(cameraPosition-position);
    vec3 hs = normalize(vs+l);
    float p = 30.0;
    vec3 Ls = LsColor*(Is/radius1)* pow(max(0.0, dot(normal, hs)), p);
    
    vColor = La + Ld + Ls;
    
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;

const fragmentShader = `
varying vec3 vColor; 
void main(){
    gl_FragColor = vec4(vColor, 1.0);
}
`;

逐片元计算的反射代码

//法线贴图
const vertexShader3 = `
// vec3 normal, uv,
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUv;
void main(){
    vPosition = position;
    vUv = uv;
    vNormal = normal;
    
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;

//法线贴图处理
const fragmentShader3 = `
uniform sampler2D cowTexture;
uniform sampler2D cowNormalTexture;
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUv;
void main(){

    vec4 map1 = texture2D(cowTexture, vUv); 
    vec3 selfColor = vec3(map1.x, map1.y, map1.z);
    
    // 法线贴图将改变 normal
    float uvAfterU = (vUv.x*1024.0+1.0)/1024.0;
    float uvAfterV = (vUv.y*1024.0+1.0)/1024.0;
    float c1 = 1.0;
    float c2 = 1.0;
    vec4 currentDepth = texture2D(cowNormalTexture, vUv);
    vec4 currentDepthU = texture2D(cowNormalTexture, vec2(uvAfterU, vUv.y));
    vec4 currentDepthV = texture2D(cowNormalTexture, vec2(vUv.x, uvAfterV));
    // float dp_u = c1 * 
    if(uvAfterU<=1.0 && uvAfterV<=1.0){
        
    }

    // Ld 漫反射
    vec3 lightPoint = vec3(3.0, 4.0, -3.0);
    vec3 l = normalize(lightPoint - vPosition);
    float radius1 = distance(lightPoint, vPosition);
    // Kd
    vec3 LdColor = vec3(1.0, 1.0, 1.0);
    float Id = 1.0;
    vec3 Ld = selfColor*(Id/radius1)*max(0.0, dot(vNormal,l));
    //
    // La 环境光
    vec3 LaColor = vec3(1.0, 1.0, 1.0);
    float Ia = 0.39;
    // vec3 La = LaColor*Ia;
    // 用自己的颜色
    vec3 La = selfColor*Ia;
    
    // Ls 镜面反射光
    vec3 LsColor = vec3(1.0, 1.0, 1.0);
    float Is =1.0;
    vec3 vs = normalize(cameraPosition-vPosition);
    vec3 hs = normalize(vs+l);
    float p = 30.0;
    vec3 Ls = selfColor*(Is/radius1)* pow(max(0.0, dot(vNormal, hs)), p);
    
    // 整体颜色
    vec3 vColor = La + Ld + Ls;
    // vec3 vColor = La + Ld ;
    // vec3 vColor = vec3(1.0, 0.6, 0.8);

    gl_FragColor = vec4(vColor, 1.0);
}
`;

在之前的实践中采用了纯 CPU 的计算方式,原生 canvas API 渲染,但是效率和处理太慢,因此,后续的实验采用 threejs 框架加快实践效率