WebGL tutorials for the front end of the web, the tutorials on the Internet are based on the assumption of computer graphics, which is not very friendly to web developers, so this pit is opened.
final effect
https://codepen.io/chendonming/pen/xxPvqxP
Mouse click to draw a point.
How webGL displays a point
First of all, you have to know how webGL displays a point?
To draw any object in webGL, you need a 顶点着色器
and 片元着色器
,
Vertex Shader : A program that describes the properties of a vertex (position, color, etc.).
Fragment Shader : A program that performs fragment processing.
Maybe you will be confused, and a lot of official theories will be discouraged, so I will directly show the simplest code to show a point, I believe you will understand immediately.
<canvas id="glcanvas" width="640" height="480">
你的浏览器似乎不支持或者禁用了HTML5 <code><canvas></code> 元素.
</canvas>
First, I need some simple wrapper functions:
function initShaders(gl, vshader, fshader) {
var program = createProgram(gl, vshader, fshader);
if (!program) {
console.log('Failed to create program');
return false;
}
gl.useProgram(program);
gl.program = program;
return true;
}
/**
* Create the linked program object
* @param gl GL context
* @param vshader a vertex shader program (string)
* @param fshader a fragment shader program (string)
* @return created program object, or null if the creation has failed
*/
function createProgram(gl, vshader, fshader) {
// Create shader object
var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
if (!vertexShader || !fragmentShader) {
return null;
}
// Create a program object
var program = gl.createProgram();
if (!program) {
return null;
}
// Attach the shader objects
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// Link the program object
gl.linkProgram(program);
// Check the result of linking
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
var error = gl.getProgramInfoLog(program);
console.log('Failed to link program: ' + error);
gl.deleteProgram(program);
gl.deleteShader(fragmentShader);
gl.deleteShader(vertexShader);
return null;
}
return program;
}
/**
* Create a shader object
* @param gl GL context
* @param type the type of the shader object to be created
* @param source shader program (string)
* @return created shader object, or null if the creation has failed.
*/
function loadShader(gl, type, source) {
// Create shader object
var shader = gl.createShader(type);
if (shader == null) {
console.log('unable to create shader');
return null;
}
// Set the shader program
gl.shaderSource(shader, source);
// Compile the shader
gl.compileShader(shader);
// Check the result of compilation
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
var error = gl.getShaderInfoLog(shader);
console.log('Failed to compile shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}
This is a function that initializes the shader
Initialize webgl:
const canvas = document.querySelector("#glcanvas");
// 初始化WebGL上下文
const gl = canvas.getContext("webgl");
// 确认WebGL支持性
if (!gl) {
alert("无法初始化WebGL,你的浏览器、操作系统或硬件等可能不支持WebGL。");
return;
}
// 使用完全不透明的黑色清除所有图像
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 用上面指定的颜色清除缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);
Call the initialize shader function.
const VSHADER_SOURCE = `
void main() {
gl_Position = vec4(0.0 ,0.0 ,0.0 , 1.0);
gl_PointSize = 10.0;
}
`;
const FSHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0 ,0.0 ,0.0 ,1.0);
}
`;
initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
gl.drawArrays(gl.POINTS, 0, 1);
OK, by now, you should see a red dot in the middle of the black canvas.
Parse
The most critical part is actually VSHADER_SOURCE
and FSHADER_SOURCE
two strings, which represent the coordinates of the point and the color of the point respectively.
VSHADER_SOURCE
and FSHADER_SOURCE
belong to the glsl
code,
VSHADER_SOURCE
in gl_Position
represents the position of the point, gl_Position
is a built-in variable of glsl
.
You will find that the value of gl_Position
is a vec4 type, and the coordinates have 4 values? In fact, this is a homogeneous coordinate.
For vec4(x, y, z, w), the real world coordinates are (x/w, y/w, z/w), so we generally set the fourth parameter of vec4 to 1 .
Why do you need homogeneous coordinates? Because in the three-dimensional world, the vector is also represented by three coordinates, so in order to distinguish the vector from the real position, a fourth parameter is introduced, and the fourth parameter of the vector is 0.
js and GLSL communication
The above code does draw a point, but it is written in a string, which is definitely inconvenient for us to operate, so it is necessary to operate the variables in glsl
.
const VSHADER_SOURCE = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 10.0;
}
`;
As shown above, we add an attribute to the vertex shader code, and then assign the attribute a_Position to the glsl built-in variable gl_Position. Does this mean that if I change the value of a_Position, gl_Position will also change?
js gets and modifies attribute
Required API:
gl.getAttribLocation(gl.program, attribute);
gl.vertexAttrib3f(index, x, y, z);
The getAttribLocation method returns the subscripted location of an attribute in the given WebGLProgram object
vertexAttrib3f can assign values to vertex attibute variables
Now just modify the attribute before gl.drawArrays(gl.POINTS, 0, 1);
var a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
So far, the complete code is as follows:
const canvas = document.querySelector("#glcanvas");
// 初始化WebGL上下文
const gl = canvas.getContext("webgl");
// 确认WebGL支持性
if (!gl) {
alert("无法初始化WebGL,你的浏览器、操作系统或硬件等可能不支持WebGL。");
return;
}
// 使用完全不透明的黑色清除所有图像
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 用上面指定的颜色清除缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);
const VSHADER_SOURCE = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 10.0;
}
`;
const FSHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0 ,0.0 ,0.0 ,1.0);
}
`;
//初始化着色器
initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
var a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
//画点
gl.drawArrays(gl.POINTS, 0, 1);
draw multiple points
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
gl.drawArrays(gl.POINTS, 0, 1);
gl.vertexAttrib3f(a_Position, 0.5, 0.5, 0.0);
gl.drawArrays(gl.POINTS, 0, 1);
You will notice that there are two red dots on the screen.
drawArrays
method is used to draw primitives from the vector array, and each time it is executed, the GPU is notified to render the primitives.
Two points are fine now, what if there are thousands of points? We need to draw multiple points at once in order to maintain performance.
Typed Array TypedArray
For multiple points, we need to store the position of the point in a variable, we choose TypedArray,
It has several advantages over ordinary Array: performance performance is also performance.
For typedArray
Introduction see the following code:
// 下面代码是语法格式,不能直接运行,
// TypedArray 关键字需要替换为底部列出的构造函数。
new TypedArray(); // ES2017中新增
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);
// TypedArray 指的是以下的其中之一:
Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();
Then how to choose, see the following list:
type | range of individual element values | size (bytes) | describe | Web IDL type | Equivalent types in C |
---|---|---|---|---|---|
Int8Array | -128 to 127 | 1 | 8-bit binary signed integer | byte | int8_t |
Uint8Array | 0 to 255 | 1 | 8-bit unsigned integer (loop from another boundary after going out of range) | octet | uint8_t |
Uint8ClampedArray | 0 to 255 | 1 | 8-bit unsigned integer (out of range bounds) | octet | uint8_t |
Int16Array | -32768 to 32767 | 2 | 16-bit binary signed integer | short | int16_t |
Uint16Array | 0 to 65535 | 2 | 16-bit unsigned integer | unsigned short | uint16_t |
Int32Array | -2147483648 to 2147483647 | 4 | 32-bit binary signed integer | long | int32_t |
Uint32Array | 0 to 4294967295 | 4 | 32-bit unsigned integer | unsigned long | uint32_t |
Float32Array | 1.2 × 10^-38 to 3.4 × 10^38 | 4 | 32-bit IEEE floating point number (7 significant digits, eg 1.1234567 ) | unrestricted float | float |
Float64Array | 5.0 × 10^-324 to 1.8 × 10^308 | 8 | 64-bit IEEE floating point number (16 significant digits, eg 1.123...15 ) | unrestricted double | double |
BigInt64Array | -2^63 to 2^63-1 | 8 | 64-bit binary signed integer | bigint | int64_t (signed long long) |
BigUint64Array | 0 to 2^64 - 1 | 8 | 64-bit unsigned integer | bigint | uint64_t (unsigned long long) |
For this tutorial, because we will use floating point numbers later, and because the data is not large, we choose Float32Array
.
try to draw two points
Store the coordinates of the two points in a variable
const verties = new Float32Array([0.0, 0.5, -0.5, -0.5]);
Hang the data to a certain memory location in the buffer and write the data
const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // verties就是我们自己创建的Float32Array数据 gl.bufferData(gl.ARRAY_BUFFER, verties, gl.STATIC_DRAW);
Read data and modify attribute
const a_Position = gl.getAttribLocation(gl.program, "a_Position"); gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(a_Position);
To understand the vertexAttribPointer function, you can see this note of mine
https://note.youdao.com/s/c5EG9usHModify the attribute, the next step is to call the drawing command
// 因为是绘制两个点,第三个参数输入2 gl.drawArrays(gl.POINTS, 0, 2);
full code
Remove the initialization webGL and tool functions initShaders
, because there is no change every time you write...
initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
const verties = new Float32Array([0.0, 0.5, -0.5, -0.5]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verties, gl.STATIC_DRAW);
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
gl.drawArrays(gl.POINTS, 0, 2);
Mouse monitor coordinates and write
If the above is understood, the third step is the easiest (for web developers).
The specific functions of the above code are all implemented, only need:
- Convert screen coordinates to webGL coordinates when clicking
- Store coordinates in Float32Array data
- Modify attribute, render.
Convert to webGl coordinates
const x = (e.offsetX - 320) / 320;
const y = -(e.offsetY - 240) / 240;
where 320 = 640/2
240 = 480/2
320 represents the width of the canvas element, 240 represents the height of the canvas element
Store Float32Array data
First of all, Float32Array is of fixed length and cannot be modified dynamically, so you need to create a new one Float32Array
const newArr = new Float32Array(length + 2)
for (let i = 0; i < arrayBuffer.length; i++) {
newArr[i] = arrayBuffer[i]
}
newArr[arrayBuffer.length] = x;
newArr[arrayBuffer.length + 1] = y;
final code
The code can be viewed in codePen. If it cannot be opened, I will show the code:
<!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>test</title>
</head>
<body onload="main()">
<canvas id="glcanvas" width="640" height="480">
你的浏览器似乎不支持或者禁用了HTML5 <code><canvas></code> 元素.
</canvas>
</body>
<script src="utils/cuon-utils.js"></script>
<script>
let arrayBuffer = new Float32Array()
function main() {
const canvas = document.querySelector("#glcanvas");
// 初始化WebGL上下文
const gl = canvas.getContext("webgl");
// 确认WebGL支持性
if (!gl) {
alert("无法初始化WebGL,你的浏览器、操作系统或硬件等可能不支持WebGL。");
return;
}
// 使用完全不透明的黑色清除所有图像
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 用上面指定的颜色清除缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);
const VSHADER_SOURCE = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 10.0;
}
`;
const FSHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0 ,0.0 ,0.0 ,1.0);
}
`;
initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
// 监听点击事件
document.getElementById('glcanvas').addEventListener('mousedown', e => {
clear(gl);
// 左上角原点坐标
const x = (e.offsetX - 320) / 320;
const y = -(e.offsetY - 240) / 240;
let length = arrayBuffer.length;
const newArr = new Float32Array(length + 2)
for (let i = 0; i < arrayBuffer.length; i++) {
newArr[i] = arrayBuffer[i]
}
newArr[arrayBuffer.length] = x;
newArr[arrayBuffer.length + 1] = y;
const len = initVertexBuffer(gl, newArr);
gl.drawArrays(gl.POINTS, 0, len);
arrayBuffer = newArr;
})
}
function initVertexBuffer(gl, verties) {
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verties, gl.STATIC_DRAW);
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
const FSIZE = verties.BYTES_PER_ELEMENT
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0);
gl.enableVertexAttribArray(a_Position);
return verties.length / 2;
}
function clear(gl) {
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
</script>
</html>
Among them cuon-utils.js
is a small tool function encapsulated
// cuon-utils.js (c) 2012 kanda and matsuda
/**
* Create a program object and make current
* @param gl GL context
* @param vshader a vertex shader program (string)
* @param fshader a fragment shader program (string)
* @return true, if the program object was created and successfully made current
*/
function initShaders(gl, vshader, fshader) {
var program = createProgram(gl, vshader, fshader);
if (!program) {
console.log('Failed to create program');
return false;
}
gl.useProgram(program);
gl.program = program;
return true;
}
/**
* Create the linked program object
* @param gl GL context
* @param vshader a vertex shader program (string)
* @param fshader a fragment shader program (string)
* @return created program object, or null if the creation has failed
*/
function createProgram(gl, vshader, fshader) {
// Create shader object
var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
if (!vertexShader || !fragmentShader) {
return null;
}
// Create a program object
var program = gl.createProgram();
if (!program) {
return null;
}
// Attach the shader objects
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// Link the program object
gl.linkProgram(program);
// Check the result of linking
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
var error = gl.getProgramInfoLog(program);
console.log('Failed to link program: ' + error);
gl.deleteProgram(program);
gl.deleteShader(fragmentShader);
gl.deleteShader(vertexShader);
return null;
}
return program;
}
/**
* Create a shader object
* @param gl GL context
* @param type the type of the shader object to be created
* @param source shader program (string)
* @return created shader object, or null if the creation has failed.
*/
function loadShader(gl, type, source) {
// Create shader object
var shader = gl.createShader(type);
if (shader == null) {
console.log('unable to create shader');
return null;
}
// Set the shader program
gl.shaderSource(shader, source);
// Compile the shader
gl.compileShader(shader);
// Check the result of compilation
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
var error = gl.getShaderInfoLog(shader);
console.log('Failed to compile shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}
/**
* Initialize and get the rendering for WebGL
* @param canvas <cavnas> element
* @param opt_debug flag to initialize the context for debugging
* @return the rendering context for WebGL
*/
function getWebGLContext(canvas, opt_debug) {
// Get the rendering context for WebGL
var gl = WebGLUtils.setupWebGL(canvas);
if (!gl) return null;
// if opt_debug is explicitly false, create the context for debugging
if (arguments.length < 2 || opt_debug) {
gl = WebGLDebugUtils.makeDebugContext(gl);
}
return gl;
}
happy
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。