做微推的网站,2018网站建设高考成绩查询,网站地图html模板,网页设计与制作配套素材快速导航#xff08;持续更新中#xff09;
WebGL系列教程一#xff08;开篇#xff09; WebGL系列教程二#xff08;环境搭建及着色器初始化#xff09; WebGL系列教程三#xff08;使用缓冲区绘制三角形#xff09; WebGL系列教程四#xff08;绘制彩色三角形…快速导航持续更新中
WebGL系列教程一开篇 WebGL系列教程二环境搭建及着色器初始化 WebGL系列教程三使用缓冲区绘制三角形 WebGL系列教程四绘制彩色三角形 WebGL系列教程五使用索引绘制彩色立方体 WebGL系列教程六纹理映射与立方体贴图 WebGL系列教程七二维及三维旋转、平移、缩放 WebGL系列教程八GLSL着色器基础语法 WebGL系列教程九动画 WebGL系列教程十模型Model、视图View、投影Projection变换 WebGL系列教程十一光照原理及Blinn Phong着色模型 目录 快速导航持续更新中1 前言2 光源的分类3 光照的渲染3.1 高光3.2 漫反射3.3 环境光 4 代码实现4.1 计算逆转置矩阵4.2 对法向量进行变换4.3 计算法向量和光线方向的点积4.4 计算漫反射分量4.5 计算环境光分量4.6 计算视线方向单位向量4.7 计算反射向量4.8 计算视线方向和反射方向的点积4.9 计算高光4.10 组合漫反射、环境光和高光4.11 完整代码4.12 效果 5 总结 1 前言 什么是光照光照就是模拟出物体被光照射时的效果使得渲染场景看起来更真实。那么WebGL在干什么WebGL其实就是在计算继而还原每个像素的颜色和亮度。这就是我们这一节所要讲的内容对一个立方体进行光照的渲染。
2 光源的分类 我们常用的光源有点光源、面光源以及环境光。面光源是平行光因为太阳离地球非常非常远所以我们处理自然光时将太阳光也认为是面光源。常见的点光源有灯泡、火焰等点光源照射在物体表面的角度是不一样的因为会对着色有较大影响。环境光是指被墙壁等物体经过多次反射之后的光环境光会从各个角度去照射物体目前我们认为他们的强度都是一样的且强度很小因此通过一个微小的的值来代替想要精确还原的同学可以自行学习光线追踪来进行模拟。
3 光照的渲染 在进行光照的渲染时可以采用不同的方式比如对面进行着色Flat Shading对顶点进行着色Ground Shading对像素进行着色Phong Shading。 本文中采用的是逐像素进行着色即 Blinn Phong Shading是在Phong Shading的基础上对高光项进行了改进。Blinn Phong Shading 对光照进行渲染时分成了三个部分即高光、漫反射、环境光。
3.1 高光 如图所示v为观察方向R为镜面反射方向当R和v足够接近时就会产生高光。这两向量的夹角可以通过单位向量点乘得到。因为这个角度不好计算因此取入射方向加上出射方向结果的一半即半程向量h和法线n的夹角来计算这个角度。在实际应用中会对这个夹角的余弦值取m次方来满足需要。 可以看出次方m取值越大高光的范围越小。
3.2 漫反射 漫反射的反射光在各个方向上是均匀分布的现实中的很多材质比如纸张、岩石、塑料表面都是粗糙的在这种情况下反射光将会以不固定的角度反射出去漫反射正是在此基础上建立的反射模型。
3.3 环境光 环境光下的反射称为环境反射环境反射光的方向可以认为就是入射光的反方向由于环境光照射物体的方式是各方向均匀的、强度相等的所以反射光也是各项均匀的。 4 代码实现 好了讲完了理论我们来进行一下实操。现在我们要对每个像素进行操作因此先在片元着色器中进行声明
// u_LightColor: 光源的颜色
uniform vec3 u_LightColor;// u_LightPosition: 光源的位置世界坐标系中
uniform vec3 u_LightPosition;// u_AmbientLight: 环境光的颜色
uniform vec3 u_AmbientLight;// u_ViewPosition: 观察者的位置世界坐标系中
uniform vec3 u_ViewPosition;// u_Shininess: 高光反射的光泽度系数
uniform float u_Shininess;// v_Normal: 从顶点着色器传递过来的法向量世界坐标系中
varying vec3 v_Normal;// v_Position: 从顶点着色器传递过来的顶点位置世界坐标系中
varying vec3 v_Position;// v_Color: 从顶点着色器传递过来的顶点颜色
varying vec4 v_Color;4.1 计算逆转置矩阵 首先我们要搞明白什么是逆转置矩阵对一个矩阵先求逆矩阵然后再求转置就得到了逆转置矩阵。那么逆转置矩阵是干什么用的我们之前讲过模型矩阵是对顶点进行变换的逆转置矩阵是对法线进行变换的。当我们对模型进行了旋转、平移、缩放后模型的法线也要跟随着变化这时用逆转置矩阵就可以很方便的求出新的法线了。因此逆转置矩阵针对的是模型矩阵。
var modelMatrix new Matrix4(); // 模型矩阵
// 计算用于变换法向量的矩阵即逆转置矩阵
normalMatrix.setInverseOf(modelMatrix);
normalMatrix.transpose();4.2 对法向量进行变换
// u_NormalMatrix: 法向量变换矩阵用于正确变换法向量
//glsl
uniform mat4 u_NormalMatrix;
// 对法向量进行变换并归一化
v_Normal normalize(vec3(u_NormalMatrix * a_Normal));var u_NormalMatrix gl.getUniformLocation(gl.program, u_NormalMatrix);
// 将法向量变换矩阵传递给 u_NormalMatrix
gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);4.3 计算法向量和光线方向的点积
// 计算从光源到片段的方向向量并进行归一化
vec3 lightDirection normalize(u_LightPosition - v_Position);// 计算法向量和光线方向的点积用于漫反射计算
float nDotL max(dot(lightDirection, normal), 0.0);4.4 计算漫反射分量
// 计算漫反射分量
vec3 diffuse u_LightColor * nDotL;4.5 计算环境光分量
// 计算环境光分量
vec3 ambient u_AmbientLight * v_Color.rgb;4.6 计算视线方向单位向量
// 计算视线方向向量从观察者到片段的位置并进行归一化
vec3 viewDirection normalize(u_ViewPosition - v_Position);4.7 计算反射向量
// 计算反射方向向量vec3 reflectDirection reflect(-lightDirection, normal);4.8 计算视线方向和反射方向的点积
// 计算视线方向和反射方向的点积并根据光泽度系数计算高光反射分量
float spec pow(max(dot(viewDirection, reflectDirection), 0.0), u_Shininess);4.9 计算高光
// 计算高光分量使其接近白色
vec3 specular vec3(1.0, 1.0, 1.0) * spec;4.10 组合漫反射、环境光和高光
// 最终颜色由漫反射、环境光和高光三部分组成
gl_FragColor vec4(diffuse ambient specular, v_Color.a);4.11 完整代码
// 顶点着色器
script idvertex-shader typex-shader/x-vertex// a_Position: 顶点位置// a_Color: 顶点颜色// a_Normal: 顶点法向量attribute vec4 a_Position;attribute vec4 a_Color;attribute vec4 a_Normal;// u_MvpMatrix: 模型视图投影矩阵用于将顶点从模型坐标转换为裁剪坐标uniform mat4 u_MvpMatrix;// u_ModelMatrix: 模型矩阵用于将顶点从局部坐标变换到世界坐标uniform mat4 u_ModelMatrix;// u_NormalMatrix: 法向量变换矩阵用于正确变换法向量uniform mat4 u_NormalMatrix;// v_Color: 传递给片段着色器的顶点颜色varying vec4 v_Color;// v_Normal: 传递给片段着色器的法向量经过变换后的世界坐标系中的法向量varying vec3 v_Normal;// v_Position: 传递给片段着色器的顶点位置世界坐标系中的位置varying vec3 v_Position;void main() {// 计算顶点位置在裁剪坐标系中的位置gl_Position u_MvpMatrix * a_Position;// 计算顶点在世界坐标系中的位置v_Position vec3(u_ModelMatrix * a_Position);// 对法向量进行变换并归一化v_Normal normalize(vec3(u_NormalMatrix * a_Normal));// 将顶点颜色传递给片段着色器v_Color a_Color;}
/script// 片段着色器
script idvertex-shader typex-shader/x-vertex
#ifdef GL_ESprecision mediump float; // 设置浮点数精度#endif// u_LightColor: 光源的颜色uniform vec3 u_LightColor;// u_LightPosition: 光源的位置世界坐标系中uniform vec3 u_LightPosition;// u_AmbientLight: 环境光的颜色uniform vec3 u_AmbientLight;// u_ViewPosition: 观察者的位置世界坐标系中uniform vec3 u_ViewPosition;// u_Shininess: 高光反射的光泽度系数uniform float u_Shininess;// v_Normal: 从顶点着色器传递过来的法向量世界坐标系中varying vec3 v_Normal;// v_Position: 从顶点着色器传递过来的顶点位置世界坐标系中varying vec3 v_Position;// v_Color: 从顶点着色器传递过来的顶点颜色varying vec4 v_Color;void main() {// 对法向量进行归一化处理vec3 normal normalize(v_Normal);// 计算从光源到片段的方向向量并进行归一化vec3 lightDirection normalize(u_LightPosition - v_Position);// 计算法向量和光线方向的点积用于漫反射计算float nDotL max(dot(lightDirection, normal), 0.0);// 计算漫反射分量vec3 diffuse u_LightColor * nDotL;// 计算环境光分量vec3 ambient u_AmbientLight * v_Color.rgb;// 计算视线方向向量从观察者到片段的位置并进行归一化vec3 viewDirection normalize(u_ViewPosition - v_Position);// 计算反射方向向量vec3 reflectDirection reflect(-lightDirection, normal);// 计算视线方向和反射方向的点积并根据光泽度系数计算高光反射分量float spec pow(max(dot(viewDirection, reflectDirection), 0.0), u_Shininess);// 计算高光分量使其接近白色vec3 specular vec3(1.0, 1.0, 1.0) * spec;// 最终颜色由漫反射、环境光和高光三部分组成gl_FragColor vec4(diffuse ambient specular, v_Color.a);}
/scriptfunction main() {// 获取 canvas 元素var canvas document.getElementById(webgl);// 获取 WebGL 的渲染上下文var gl getWebGLContext(canvas);if (!gl) {console.log(无法获取 WebGL 的渲染上下文);return;}// 初始化着色器if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {console.log(无法初始化着色器);return;}// 初始化顶点缓冲区var n initVertexBuffers(gl);if (n 0) {console.log(无法设置顶点信息);return;}// 设置清除颜色并启用深度测试gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.enable(gl.DEPTH_TEST);// 获取 uniform 变量的存储位置var u_ModelMatrix gl.getUniformLocation(gl.program, u_ModelMatrix);var u_MvpMatrix gl.getUniformLocation(gl.program, u_MvpMatrix);var u_NormalMatrix gl.getUniformLocation(gl.program, u_NormalMatrix);var u_LightColor gl.getUniformLocation(gl.program, u_LightColor);var u_LightPosition gl.getUniformLocation(gl.program, u_LightPosition);var u_AmbientLight gl.getUniformLocation(gl.program, u_AmbientLight);var u_ViewPosition gl.getUniformLocation(gl.program, u_ViewPosition);var u_Shininess gl.getUniformLocation(gl.program, u_Shininess);if (!u_ModelMatrix || !u_MvpMatrix || !u_NormalMatrix || !u_LightColor || !u_LightPosition || !u_AmbientLight || !u_ViewPosition || !u_Shininess) { console.log(无法获取存储位置);return;}// 设置光的颜色白色gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);// 设置光源的位置世界坐标系中gl.uniform3f(u_LightPosition, 8.5, 4.0, 3.5);// 设置环境光gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);// 设置观察者的位置gl.uniform3f(u_ViewPosition, 6.0, 6.0, 14.0);// 设置光泽度系数gl.uniform1f(u_Shininess, 128.0);var modelMatrix new Matrix4(); // 模型矩阵var mvpMatrix new Matrix4(); // 模型视图投影矩阵var normalMatrix new Matrix4(); // 法向量变换矩阵var currentAngle 0.0; // Current rotation anglefunction tick(){currentAngle animate(currentAngle); // Update the rotation angle// 计算模型矩阵modelMatrix.setRotate(currentAngle, 0, 1, 0); // 绕 y 轴旋转// 计算视图投影矩阵mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);mvpMatrix.lookAt(6, 6, 14, 0, 0, 0, 0, 1, 0);mvpMatrix.multiply(modelMatrix);// 计算用于变换法向量的矩阵即逆转置矩阵normalMatrix.setInverseOf(modelMatrix);normalMatrix.transpose();// 将模型矩阵传递给 u_ModelMatrixgl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);// 将模型视图投影矩阵传递给 u_MvpMatrixgl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);// 将法向量变换矩阵传递给 u_NormalMatrixgl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);// 清除颜色和深度缓冲区gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);// 绘制立方体gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);requestAnimationFrame(tick, canvas); // 开启动画每一帧都去调用}tick();
}function initVertexBuffers(gl) {// 创建立方体// v6----- v5// /| /|// v1------v0|// | | | |// | |v7---|-|v4// |/ |/// v2------v3// 顶点坐标var vertices new Float32Array([2.0, 2.0, 2.0, -2.0, 2.0, 2.0, -2.0,-2.0, 2.0, 2.0,-2.0, 2.0, // v0-v1-v2-v3 前面2.0, 2.0, 2.0, 2.0,-2.0, 2.0, 2.0,-2.0,-2.0, 2.0, 2.0,-2.0, // v0-v3-v4-v5 右面2.0, 2.0, 2.0, 2.0, 2.0,-2.0, -2.0, 2.0,-2.0, -2.0, 2.0, 2.0, // v0-v5-v6-v1 上面-2.0, 2.0, 2.0, -2.0, 2.0,-2.0, -2.0,-2.0,-2.0, -2.0,-2.0, 2.0, // v1-v6-v7-v2 左面-2.0,-2.0,-2.0, 2.0,-2.0,-2.0, 2.0,-2.0, 2.0, -2.0,-2.0, 2.0, // v7-v4-v3-v2 下面2.0,-2.0,-2.0, -2.0,-2.0,-2.0, -2.0, 2.0,-2.0, 2.0, 2.0,-2.0 // v4-v7-v6-v5 后面]);// 颜色var colors new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 前面0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v3-v4-v5 右面0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v5-v6-v1 上面0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v1-v6-v7-v2 左面0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v7-v4-v3-v2 下面0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 // v4-v7-v6-v5 后面]);// 法向量var normals new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 前面1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 右面0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 上面-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 左面0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 下面0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 后面]);// 顶点的索引var indices new Uint8Array([0, 1, 2, 0, 2, 3, // 前面4, 5, 6, 4, 6, 7, // 右面8, 9,10, 8,10,11, // 上面12,13,14, 12,14,15, // 左面16,17,18, 16,18,19, // 下面20,21,22, 20,22,23 // 后面]);// 将顶点属性写入缓冲区坐标、颜色和法向量if (!initArrayBuffer(gl, a_Position, vertices, 3)) return -1;if (!initArrayBuffer(gl, a_Color, colors, 3)) return -1;if (!initArrayBuffer(gl, a_Normal, normals, 3)) return -1;// 解除绑定缓冲区对象gl.bindBuffer(gl.ARRAY_BUFFER, null);// 将顶点索引写入缓冲区对象var indexBuffer gl.createBuffer();if (!indexBuffer) {console.log(无法创建缓冲区对象);return false;}gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);return indices.length;
}function initArrayBuffer(gl, attribute, data, num) {// 创建缓冲区对象var buffer gl.createBuffer();if (!buffer) {console.log(无法创建缓冲区对象);return false;}// 将数据写入缓冲区对象gl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);// 将缓冲区对象分配给 attribute 变量var a_attribute gl.getAttribLocation(gl.program, attribute);if (a_attribute 0) {console.log(无法获取 attribute 的存储位置);return false;}gl.vertexAttribPointer(a_attribute, num, gl.FLOAT, false, 0, 0);// 启用缓冲区对象分配gl.enableVertexAttribArray(a_attribute);return true;
}
// Rotation angle (degrees/second)
var ANGLE_STEP 30.0;
// Last time that this function was called
var g_last Date.now();
function animate(angle) {// Calculate the elapsed timevar now Date.now();var elapsed now - g_last;g_last now;// Update the current rotation angle (adjusted by the elapsed time)var newAngle angle (ANGLE_STEP * elapsed) / 1000.0;return newAngle % 360;
}4.12 效果 看下动画效果
5 总结 本文介绍了光照原理的基础并在此基础上讲解了法线计算、入射光计算、反射光计算、逆转置矩阵等等最后我们使用Blinn Phong 着色模型通过组合高光、漫反射和环境光实现了对一个立方体的动态光照效果渲染。本文在理解上有一定的难度希望读者仔细体会回见~