webgl 入门
webgl 是什么?
wegbl 工作原理
3D 模型数据从诞生到最终显示在屏幕上,这期间经历了什么样的过程呢? 大家可以想象一下流水线的生产过程,流水线按照既定的步骤对原料进行加工,当前步骤只对前一步骤的结果进行处理,然后将处理后的结果传递给下一步骤,最终将原材料生产成完整的产品。WebGL 的工作方式和流水线类似,也是按照流水线的方式将 3D 模型数据渲染到 2D 屏幕上的,业界把这种渲染方式称为图形管线或者渲染管线,大家以后碰到这两个名词,应该能明白什么意思了,我们知道,WebGL 只能够绘制点、线段、三角形这三种基本图元,但是我们经常看到 WebGL 程序中含有立方体、球体、圆柱体等规则形体,甚至很多更复杂更逼真的不规则模型, 那么 WebGL 是如何绘制它们的呢? 其实这些模型本质上是由一个一个的点组成,GPU 将这些点用三角形图元绘制成一个个的微小平面,这些平面之间互相连接,从而组成各种各样的立体模型。因此,我们的首要任务是创建组成这些模型的顶点数据。 一般情况下,最初的顶点坐标是相对于模型中心的,不能直接传递到着色器中,我们需要对顶点坐标按照一系列步骤执行模型转换,视图转换,投影转换,转换之后的坐标才是 WebGL 可接受的坐标,即裁剪空间坐标。我们把最终的变换矩阵和原始顶点坐标传递给 GPU,GPU 的渲染管线对它们执行流水线作业。
- GPU 渲染管线的主要处理过程如下:
- 首先进入顶点着色器阶段,利用 GPU 的并行计算优势对顶点逐个进行坐标变换。
- 然后进入图元装配阶段,将顶点按照图元类型组装成图形。
- 接下来来到光栅化阶段,光栅化阶段将图形用不包含颜色信息的像素填充。
- 在之后进入片元着色器阶段,该阶段为像素着色,并最终显示在屏幕上。
此外我们需要知道 GLSL 是什么? GLSL 是运行在GPU里面的程序语言,又称着色器语言。而运行在 GPU 里面的程序被称为着色器(Shader)。
顶点着色器
attribute vec4 position;
void main() {
gl_Position = position;
}
片元着色器
void main(void) {
gl_FragColor = vec4(1.0, 0.5, 1.0, 1.0);
}
GLSL 是连接浏览器和 GPU 的桥梁,工程师通过编写 着色器(Shader) 来渲染出自己想要的效果。而复杂的图形都是有点,线,面这三种基本图元组成的。如下图茶壶所示:
demo 源码分析
先看图,彩色三角形是有顶点着色器确定绘制位置,而片元着色器进行片元着色,最后显示在屏幕上。
* @Descripttion:
* @version:
* @Author: Wang Ming
* @Date: 2022-01-23 14:00:39
* @LastEditors: Wang Ming
* @LastEditTime: 2022-02-17 19:12:05
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script id="shader-vs" type="shader-source">
//设置浮点数精度为中等精度。
precision mediump float;
attribute vec3 a_position;
attribute vec3 a_color;
varying vec3 v_color;
void main(void){
gl_Position = vec4(a_position,1.0);
v_color = a_color;
}
</script>
<script id="shader-fs" type="shader-source">
//设置浮点数精度为中等精度。
precision mediump float;
varying vec3 v_color;
void main(void){
gl_FragColor = vec4(v_color/vec3(255,255,255),1.0);
}
</script>
<script>
var webgl = null;
var vertexshaderObj = null;
var fragmentShaderObj = null;
var programObject = null;
var triangleBuffer = null;
var a_positionInxex = 0;
var a_colorInxex = 1;
window.onload = () => {
function getShaderSource(scriptID) {
var shaderScript = document.getElementById(scriptID);
if (shaderScript == null) return "";
var sourceCode = "";
var child = shaderScript.firstChild;
while (child) {
if (child.nodeType == child.TEXT_NODE)
sourceCode += child.textContent;
child = child.nextSibling;
}
return sourceCode;
}
let canvas = document.getElementById("canvas");
webgl = canvas.getContext("webgl");
webgl.viewport(0, 0, canvas.clientWidth, canvas.clientHeight);
//创建着色器
vertexshaderObj = webgl.createShader(webgl.VERTEX_SHADER);
fragmentShaderObj = webgl.createShader(webgl.FRAGMENT_SHADER);
console.log(fragmentShaderObj);
//往着色器添加GLSL
webgl.shaderSource(vertexshaderObj, getShaderSource("shader-vs"));
webgl.shaderSource(fragmentShaderObj, getShaderSource("shader-fs"));
//编译着色器
webgl.compileShader(vertexshaderObj);
webgl.compileShader(fragmentShaderObj);
判断顶点着色器是否存在,否则返回。
if (!webgl.getShaderParameter(vertexshaderObj, webgl.COMPILE_STATUS)) {
console.log(webgl.getShaderInfoLog(vertexshaderObj));
return;
}
判断片元着色器是否存在,否则返回。
if (
!webgl.getShaderParameter(fragmentShaderObj, webgl.COMPILE_STATUS)
) {
alert("ERROR:fragmentShaderObj");
return;
}
//创建绘制程序
programObject = webgl.createProgram();
//添加顶点着色器
webgl.attachShader(programObject, vertexshaderObj);
//添加片元着色器
webgl.attachShader(programObject, fragmentShaderObj);
GPU连接绘制程序
webgl.linkProgram(programObject);
判断GPU是否连接
if (!webgl.getProgramParameter(programObject, webgl.LINK_STATUS)) {
alert("ERROR:PROGRAMOBJ");
return;
}
console.log(programObject);
GPU使用绘制程序
webgl.useProgram(programObject);
{
//创建顶点缓冲区
let a_position = webgl.getAttribLocation(programObject, "a_position");
var jsArrayData = [0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0];
triangleBuffer = webgl.createBuffer(webgl, a_position, {
size: 3,
});
绑定缓冲区
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
往缓冲区写入数据
webgl.bufferData(
webgl.ARRAY_BUFFER,
new Float32Array(jsArrayData),
webgl.STATIC_DRAW
);
开启属性数组列表中指定索引处的通用顶点属性数组。
webgl.enableVertexAttribArray(a_positionInxex);
告诉显卡从当前绑定的缓冲区(bindBuffer() 指定的缓冲区)中读取顶点数据。
webgl.vertexAttribPointer(
a_positionInxex,
3,
webgl.FLOAT,
false,
0,
0
);
}
{
let a_Color = webgl.getAttribLocation(programObject, "a_color");
let colorData = [255, 99, 71, 77, 0, 71, 77, 254, 71];
var colorBuffer = webgl.createBuffer(webgl, a_Color, {
size: 3,
});
webgl.bindBuffer(webgl.ARRAY_BUFFER, colorBuffer);
webgl.bufferData(
webgl.ARRAY_BUFFER,
new Float32Array(colorData),
webgl.STATIC_DRAW
);
webgl.enableVertexAttribArray(a_colorInxex);
webgl.vertexAttribPointer(a_colorInxex, 3, webgl.FLOAT, false, 0, 0);
}
清屏
webgl.clearColor(0.0, 0.0, 0.1, 1.0);
webgl.clear(webgl.COLOR_BUFFER_BIT);
绘制
webgl.drawArrays(webgl.TRIANGLES, 0, jsArrayData.length);
console.log(webgl);
};
</script>
<canvas id="canvas" width="600" height="450"></canvas>
</body>
</html>