Created by @yangwt
WebGL是一项用来在网页上绘制和渲染复杂三维图形(3D图形),并允许用户与之进行交互的技术。
WebGL 本质上是基于光栅化的 API ,而不是基于 3D 的 API。
把物体的数学描述以及与物体相关的颜色信息转换为屏幕上用于对应位置的像素及用于填充像素的颜色,这个过程称为光栅化。
WebGL只关注两件事——坐标和颜色。
使用WebGL程序的任务就是实现具有投影矩阵坐标和颜色的WebGL对象即可。
定义两个着色器———顶点着色器和片元着色器。顶点着色器提供坐标,片元着色器提供颜色
tips: 无论画板尺寸多大,投影矩阵坐标的范围只会在-1到1之间
<script id="2d-vertex-shader" type="x-shader/x-vertex"> attribute vec2 a_position; void main() { gl_Position = vec4(a_position, 0, 1); } </script> <script id="2d-fragment-shader" type="x-shader/x-fragment"> void main() { gl_FragColor = vec4(0, 1, 0, 1); // green } </script>
着色器是用GLSL写的方法。处理每个顶点时都要引用一次,将定义的3D坐标点转化为屏幕可以显示出来的2D顶点,即投影矩阵的坐标。
用户可以将投影矩阵的值存储在特定的变量gl_Position中。GPU会处理这些值,并将它们存储在内部。
片元着色器中gl_fragColor设置颜色,描绘图形时,每个px都会通过片元着色器得到该px的颜色。
WebGL context
var canvas = document.getElementById("canvas"); var gl = getWebGLContext(canvas);
const 常量
attribute 顶点着色器定义的全局变量,存储可变的顶点坐标值
uniform 着色器定义的只读变量
varing 定义在顶点和片元着色器之间传递的变量,在顶点着色器定义,片元着色器中只读
webgl-utils.js
createProgramFromScript(gl, ["2d-vertex-shader", "2d-fragment-shader"]); 创建一个GLSL Program
gl.getAttribLocation(program, "a_position");
gl.getUniformLocation(program, "a_position");
gl.uniform2f(location, canvas.width, canvas.height); 设置分辨率
gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1); 设置随机颜色
gl.createBuffer(); 创建缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 绑定缓冲区为当前操作对象
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([]), gl.STATIC_DRAW); 将内存中的顶点数组复制到当前操作的数据缓冲区中
gl.enableVertexAttribArray(anyVertexAttributeLocation); 开启数组模式
gl.vertexAttribPointer(index, size, type, normalized, stride, offset); 把当前工作的数据缓冲区指定给数组
gl.clear(gl.COLOR_BUFFER_BIT); 清空canvas
gl.uniformMatrix(matrixLocation, false, matrix); 创建矩阵
gl.drawArray(gl.TRIANGLE, 0, 3); 意味着有3个顶点要处理
new Float32Array([ // left column 0, 0, 30, 0, 0, 150, 0, 150, 30, 0, 30, 150, // top rung 30, 0, 100, 0, 30, 30, 30, 30, 100, 0, 100, 30, // middle rung 30, 60, 67, 60, 30, 90, 30, 90, 67, 60, 67, 90])
// Set the matrix. gl.uniformMatrix3fv(matrixLocation, false, matrix); // Draw the geometry. gl.drawArrays(gl.TRIANGLES, 0, 18);
<script id="2d-vertex-shader" type="x-shader/x-vertex"> attribute vec2 a_position; uniform mat3 u_matrix; void main() { gl_Position = u_matrix * a_position; } </script>
计算机的3D世界由点组成,两个不重合的点组成一条直线,三个不在一条直线上的点就能组成一个三角形面,无数三角形面就能够组成各种形状的物体。
3D与2D最显著区别就是,3D具有深度,也就是Z轴(垂直屏幕方向)
new Float32Array([ // left column front 0, 0, 0, 30, 0, 0, 0, 150, 0, 0, 150, 0, 30, 0, 0, 30, 150, 0, ...(此处省略13个顶点坐标) // bottom 0, 150, 0, 0, 150, 30, 30, 150, 30, 0, 150, 0, 30, 150, 30, 30, 150, 0, // left side 0, 0, 0, 0, 0, 30, 0, 150, 30, 0, 0, 0, 0, 150, 30, 0, 150, 0]),
// Set the matrix. gl.uniformMatrix4fv(matrixLocation, false, matrix); // Draw the geometry. gl.drawArrays(gl.TRIANGLES, 0, 16*6);
<script id="3d-vertex-shader" type="x-shader/x-vertex"> attribute vec4 a_position; uniform mat4 u_matrix; void main() { // Multiply the position by the matrix. gl_Position = u_matrix * a_position; } </script>
WebGL有前面和后面的概念 (看得见的为前面)。
前面的顶点坐标以顺时针的顺序定义
后面的顶点坐标以逆时针的顺序定义
Not
1, 2, 3, 40, 50, 60, 700, 800, 900,
But
1, 2, 3, 700, 800, 900, 40, 50, 60,
scale of 2, 1, rotation of 30 degrees, and translation of 100, 0
translation of 100,0, rotation of 30 degrees and scale of 2, 1
2D 3x3 Matrix
s = Math.sin(angleToRotateInRadians)
c = Math.cos(angleToRotateInRadians)
将一张图像映射(贴)到一个集合图形的表面,这张图片称为纹理图像或纹理。
纹理映射的作用,就是根据纹理图像,为之前光栅化后的每个片元涂上合适的颜色。组成纹理图像的像素又被称为纹素。
准备好映射到几何图形上的纹理图像(不能跨域)
为几何图形配置纹理映射方式
加载纹理图像,对其进行一些配置,以在WebGL中使用它。
在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元。
顶点着色器定义纹理坐标
attribute vec4 a_position; attribute vec2 a_texcoord; uniform mat4 u_matrix; varying vec2 v_texcoord; void main() { // Multiply the position by the matrix. gl_Position = u_matrix * a_position; // Pass the texcoord to the fragment shader. v_texcoord = a_texcoord; }
sampler2D引用纹理,texture2D得到颜色
precision mediump float; // Passed in from the vertex shader. varying vec2 v_texcoord; // The texture. uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, v_texcoord); }
设置纹理坐标
var positionLocation = gl.getAttribLocation(program, "a_position"); var texcoordLocation = gl.getAttribLocation(program, "a_texcoords"); ... // Create a buffer for texcoords. var buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.enableVertexAttribArray(texcoordLocation); // We'll supply texcoords as floats. gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0); // Set Texcoords. setTexcoords(gl);
function setTexcoords(gl) { gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([ // left column front 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, // top rung front 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, ...
引入图片
var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // Fill the texture with a 1x1 blue pixel. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); // Asynchronously load an image var image = new Image(); image.src = "resources/f-texture.png"; image.addEventLisenter('load', function() { // Now that the image has loaded make copy it to the texture. gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, image); gl.generateMips(gl.TEXTURE_2D); });
即近大远小的特征
实现:将坐标X和Y除以Z
<script id="2d-vertex-shader" type="x-shader/x-vertex"> ... uniform float u_fudgeFactor; ... void main() { // Multiply the position by the matrix. vec4 position = u_matrix * a_position; // Adjust the z to divide by float zToDivideBy = 1.0 + position.z * u_fudgeFactor; // Divide x and y by z. gl_Position = vec4(position.xy / zToDivideBy, position.zw); } </script>
clipspace 中 Z 从 -1 到 +1,加 1 之后 zToDivideBy 从 0 到 +2 * fudgeFactor
移动镜头
移动物体
镜头移到原点
画一圈F
<script id="2d-vertex-shader" type="x-shader/x-vertex"> var numFs = 5; var radius = 200; // Compute the projection matrix var aspect = canvas.clientWidth / canvas.clientHeight; var projectionMatrix = makePerspective(fieldOfViewRadians, aspect, 1, 2000); // Draw 'F's in a circle for (var ii = 0; ii < numFs; ++ii) { var angle = ii * Math.PI * 2 / numFs; var x = Math.cos(angle) * radius; var z = Math.sin(angle) * radius; var translationMatrix = makeTranslation(x, 0, z); // Multiply the matrices. var matrix = translationMatrix; matrix = matrixMultiply(matrix, projectionMatrix); // Set the matrix. gl.uniformMatrix4fv(matrixLocation, false, matrix); // Draw the geometry. gl.drawArrays(gl.TRIANGLES, 0, 16 * 6); } </script>
画镜头
var cameraMatrix = makeTranslation(0, 0, radius * 1.5); cameraMatrix = matrixMultiply(cameraMatrix, makeYRotation(cameraAngleRadians);
根据镜头矩阵计算视图矩阵
var viewMatrix = makeInverse(cameraMatrix);
应用视图矩阵来计算每个F的矩阵
var matrix = translationMatrix; matrix = matrixMultiply(matrix, viewMatrix); // <=-- added matrix = matrixMultiply(matrix, projectionMatrix);
Thank you.