Blinn-Phong反射模型实践(web实现)
games101 第四次作业
最终完成带贴图的 Blinn-Phong 模型,产生光照效果
完成了
- 不带贴图的 Blinn-Phone 反射模型
- 带贴图的模型,但是纹理映射应用在顶点着色器上
- 带贴图的模型,纹理映射在片元着色器上
Blinn-Phone 光照模型
光照分为三种,分别为环境光,漫反射光和类镜面反射的高光。这分别对应三种反射,当光照射在物体表面,物体表面会发生相应的反射,将光反射到人眼当中,这样,人眼才能看见物体。
环境光简单理解为任何地方都有的一种光,光的颜色和强度相同,当然,现实可不是这样;漫反射在初中学过,在物体表面某个点发生漫反射后,从任何地方都能看到这个点且颜色强度都一样;高光反射类似光照到镜子上产生的高亮的效果。设置进入人眼的光为 L,漫反射为 Ld,高光反射为 Ls,环境反射为 La,可以有公式
环境反射
环境光由于任何地方的光照强度相同,因此有公式
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 为点到光源的方向向量。将环境反射和漫反射加入程序中可得
单独和环境反射进行对比可以发现,牛牛变得更有轮廓了
高光反射
高光反射最为复杂,最重要的一点,当反射角与入射角大小一致时,光照强度最大。首先依下图所示定义多个变量,I 为点到光的方向向量,n 为点的法向量,v 为点到视角的方向向量。现在,我们知道,反射角与入射角一致时,强度最大,若此时反射角设为入射角大小,那么 v 与反射线的夹角越大,损失的光照越多,而 v 与反射线夹角可以转换为 n 与 h 的夹角。
h 叫半城向量,实际上就是 I 向量与 v 向量的角平分线的方向向量。此时因为 n 和 h 的夹角产生的光照损失值为 n * h
,其他部分与漫反射一致。I / r^2
表示光照随距离的缩减,ks 表示影响因子
将上述三种反射集合起来形成了 Blinn-Phone 反射模型,和前面两种对比,有了明显的高光
顶点着色器和片元着色器
在顶点着色器中,会遍历每个顶点的属性进行处理,此时的运算均是三角顶点的运算。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 框架加快实践效率