使用webGL绘制一个“Hello World” 并不是很轻松。它要涉及初始化WebGLRenderingContext初始化、WebGLProgram的初始化、模型顶点数据制作,以及最终渲染和绘制等:
- 我们要生成或找到画布对象;
- 初始化WebGLRenderingContext、WebGLProgram 即gl和program;
- 获取模型网格顶点数据
- 实现模型绘制
在这一节中我们来主要阐述如何初始化WebGLRenderingContext、WebGLProgram
**index.ts**
import RenderContext from './libs/renderContext';
import { Modal, Shape} from './libs';
const container = document.getElementById('root');
function main() {
//初始化canvas画布 以及 RenderContext中的gl和program
RenderContext.init({
container,
width: container.offsetWidth,
height: container.offsetHeight,
});
//获取gl和program
const gl = RenderContext.getGL()
const program = RenderContext.getProgram();
** 使用gl和program来绘制图形 **
}
main();
其中RenderContext是一个全局的单例,控制gl和program的初始化的过程,并提供导出获取gl和program的方法
**renderContext.ts**
import { initGL } from './boot/initGL'
import { initProgram } from './boot/initProgram'
interface Options {
container: HTMLElement;
canvas?: HTMLCanvasElement;
width: number;
height: number;
}
//创建一个单例 RenderContext
export default class RenderContext{
static gl: WebGLRenderingContext;//绑定的gl
static program: WebGLProgram; //绑定的program
static canvas: HTMLCanvasElement; //绑定的canvas
//初始化canvas
static init(options: Options) {
if (RenderContext.canvas) return;
if (options.canvas) {
RenderContext.canvas = options.canvas;
} else {
const canvasHtml = document.createElement('canvas');
canvasHtml.width = options.width;
canvasHtml.height = options.height;
canvasHtml.id = 'game-canvas';
options.container.appendChild(canvasHtml);
RenderContext.canvas = canvasHtml;
}
}
//初始化gl和program
static initContext(){
if (RenderContext.gl) return;
if (!RenderContext.canvas) return;
const gl = initGL(RenderContext.canvas);
if (gl) {
const program = initProgram(gl);
(gl as any).canvas.width = (gl as any).canvas.clientWidth;
(gl as any).canvas.height = (gl as any).canvas.clientHeight;
RenderContext.program = program;
RenderContext.gl = gl;
}
}
//获取单例gl
static getGL() {
RenderContext.initContext()
return RenderContext.gl;
}
//获取单例program
static getProgram() {
RenderContext.initContext()
return RenderContext.program;
}
}
initGL方法是通过canvas对象来获取WebGLRenderingContext
**boot/initGL.ts**
// 初始化gl
export function initGL(canvas: HTMLCanvasElement): WebGLRenderingContext | void {
if (canvas) {
const gl:WebGLRenderingContext = canvas.getContext("webgl");
if (!gl) {
throw "gl initialize fail"
}
return gl;
}
}
initProgram方法用于初始化WebGLProgram,并将program与gl关联;
**boot/initProgram.ts**
import { vertexShaderSource, fragmentShaderSource } from './shader'
//创建的是顶点着色器、片元着色器的方法
const loadShader = (gl: WebGLRenderingContext, type: GLenum, source: string) => {
const shader = gl.createShader(type); //创建的是顶点着色器、片元着色器类型
gl.shaderSource(shader, source);
gl.compileShader(shader); // 编译shader L代码
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const error = `An error occurred compiling this shaders: ${gl.getShaderInfoLog(shader)}`
gl.deleteShader(shader);
throw error;
}
return shader
}
//初始化program
export const initProgram = (gl: WebGLRenderingContext) => {
//创建顶点、片元着色器
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
//创建program
const program: WebGLProgram = gl.createProgram();
//将着色器绑定到program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 将该program链接到gl
gl.linkProgram(program);
return program;
}
每创建一个WebGLProgram都需要:顶点着色器vertexShader、片元着色器fragmentShader
**boot/shader.ts**
/**
* a_position 顶点[-1,1]数据 (0, 0)
* u_resolution 分辨率 (1920, 1680)
* zeroToOne 将顶点左边转化为[0,1]
* zeroToTwo 顶点坐标转换为[0,2]
* clipSpace 将顶点坐标转换[-1,1]
* gl_Position 设置点数据
*
*/
export const vertexShaderSource =`
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
vec2 zeroToOne = a_position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}`;
export const fragmentShaderSource =`
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}`;