一、前言
自从入坑了CesiumJS,一直对一些特效感兴趣。奈何三维方面基础太薄弱,每次看别人写的glsl特效都有点力不从心。更不要说实现自己想要的特效。终于最近抽空入手了一本《WebGL编程指南》,强烈推荐像我一样的小白去看。这本书的原版是日文,后来被翻译为中文和英文。非常感谢本书的翻译者,用平实的语言让我读懂了WebGL的特性,并让我非常自然的接受了后面比较难以理解的矩阵变换。
我写WebGL编程指南系列,也是跟着这本书的脉络,整理一下我的学习心得,并分享出来。
二、WebGL概述
WebGL是一项用来在网页上绘制和渲染复杂三维图形,并允许用户与之进行交互的技术。传统的三维渲染,需要C或C++语言开发,并利用OpenGL(开源)或Direct3D(微软提供)这些三维图形库开开发独立应用程序。WebGL的出现,改变了桌面端应用程序垄断三维技术的情况。WebGL内嵌在浏览器中,不需要安装软件和3D环境,直接打开网页即可浏览。
WebGL技术规范继承自免费和开源的OpenGL标准,因此WebGL就是Web版的OpenGL(实际上,WebGL继承自OpenGL的一个特殊版本---OpenGL ES 2.0)。OpenGL 2.0版本开始,支持了可编程着色器方法(programmable shader functions),这个特性被OpenGL ES 2.0继承,并成为了WebGL 1.0标准的核心部分。
因此也可以说,OpenGL ES 着色器语言(GLSL ES),是WebGL的核心。WebGL需要两种着色器:
顶点着色器(Vertex shader):顶点着色器用来描述顶点特性(位置、大小、颜色等)。顶点是指二维或三维空间中的一个点,比如二维或三维图形的端点或交点。
片源着色器(Fragment shader):进行逐片源处理过程,如颜色、光照、阴影。片源也可以理解为像素。
这两个概念第一次结束会比较抽象,这里先有一个印象即可。后面随着案例的深入,会慢慢变得很形象。
三、WebGL入门
1、绘制一个点
熟悉web编程的朋友们应该都知道,在html中绘制二维元素,需要在<canvas>元素中实现。同理,利用WebGL绘制三维元素,也需要在<canvas>中绘制。
我写案例使用的是Vue3+typeScript。
<template>
<canvas id='webgl' width='400' height='400'></canvas>
</template>
<script lang='ts'>
import { defineComponent, onMounted } from 'vue'
export default defineComponent({
name: 'webgl1',
setup() {
// 着色器程序是以字符串形式嵌入进js代码中的
// WebGL因为继承自OpenGL,而OpenGL支持C、C++语言
// 所以着色器程序的编程风格与C相同,是强类型编程语言
// 关于C、C++的编程规范,这里不展开说,需自行了解。
// 顶点着色器程序
const v_shader_source = `
void main()
{
// 顶点着色器的内置变量
// gl_Position 表示顶点位置
// gl_pointSize 表示点的尺寸
gl_Position = vec4(0.0,0.0,0.0,1.0);
// gl_PointSize若不赋值,默认为1.0
gl_PointSize = 10.0;
}
`;
// 片元着色器程序
const f_shader_source = `
void main()
{
// gl_FragColor是片元着色器的唯一内置变量,
// 这里用来为这个点赋值颜色
// vec4()是一个四维向量,在颜色方面,分别代表 r g b a
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`;
/**
* 初始化着色器,将提前写好的glsl代码赋值给着色器
* */
const initShaders=(gl:WebGLRenderingContext,v_shader_source:string,f_shader_source:string)=>{
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vertexShader!, v_shader_source)
gl.compileShader(vertexShader!)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fragmentShader!, f_shader_source)
gl.compileShader(fragmentShader!)
const shaderProgram = gl.createProgram()
gl.attachShader(shaderProgram!, vertexShader!)
gl.attachShader(shaderProgram!, fragmentShader!)
gl.linkProgram(shaderProgram!)
gl.useProgram(shaderProgram!)
}
onMounted(()=>{
// 从<canvas>标签中获取webgl绘图上下文
const canvas = document.getElementById('webgl') as HTMLCanvasElement;
const gl = canvas.getContext('webgl') as WebGLRenderingContext;
//初始化着色器
initShaders(gl, v_shader_source, f_shader_source)
gl.clearColor(1.0, 1.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.POINTS, 0, 1)
})
}
})
</script>
<style scoped>
</style>
程序执行后的效果:
2、WebGL坐标系统
gl_Position = vec4(0.0,0.0,0.0,1.0);
在顶点着色器中,我们定义了一个四维变量作为点的位置,但实际上我们只需要x、y、z三个纬度的坐标即可。这是为什么呢?
这涉及到线性代数的知识。由4个分量组成的矢量被称为其次坐标,因为它能够提高处理三维数据的效率,所以在三维图形系统重被大量使用。事实上,齐次坐标(x,y,z,w)等价于(x/w,y/w.z/w)。所以(0.0,0.0,0.0,1.0)等价于(0.0,0.0,0.0)。
根据上面的执行结果,可知点绘制在了canvas的中间,但熟悉屏幕坐标的同学都知道,canvas的(0,0)点应该是左上角,为什么会将点绘制在中间呢?
那是因为gl_Position表示的是webgl的坐标系中的位置,canvas的坐标需转换为webgl中的坐标系统。二者的坐标系统关系,见下图:
以上就是我对WebGL的初步认识,总结一下:
WebGL继承自OpenGL ES 2.0实现,glsl着色器编程是它的核心。
着色器又分为:顶点着色器和片元着色器。顶点着色器用来定义图形的交点、顶点的位置、大小。片源着色器用来基于顶点的颜色渲染其余片元的颜色、光照和阴影效果。
着色器语言规范与C语言相同。
顶点着色器中内置两个变量:gl_Position,gl_PointSize。
片源着色器中内置一个变量:gl_FragColor。
WebGL坐标系统,是归一化的坐标系统,其x轴、y轴的坐标范围是[-1,1]。且(0,0)位置位于中心处。