WebGL基础(一)(|WebGL基础(一): 从一个鼠标画点开始了解原生webGL)

面向web前端的WebGL教程,网络上的教程均是假设有计算机图形学基础,对web开发者来说不是很友好, 故开辟此坑
最终效果 https://codepen.io/chendonmin...
鼠标点击 画一个点。
webGL如何展示一个点 首先得知道webGL如何展示出一个点?
webGL画任意物体 都需要一个顶点着色器片元着色器,
顶点着色器:描述顶点的特性(位置、颜色等)的程序.
片元着色器: 进行着片元处理过程的程序。
也许你会很懵,一大堆官方理论又要望而却步了,所以我直接展示下最简单的展示一个点的代码,相信你会马上明白。
你的浏览器似乎不支持或者禁用了HTML5 < canvas> 元素.

首先,我需要一些简单的封装函数:
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; }

这是一段初始化着色器的函数
初始化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);

【WebGL基础(一)(|WebGL基础(一): 从一个鼠标画点开始了解原生webGL)】调用初始化着色器函数。
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,目前为止,你应该能看到黑色canvas中间有个红色的点了。
解析 最关键的部分,其实就是VSHADER_SOURCEFSHADER_SOURCE两个字符串,分别表示了点的坐标和点的颜色。
VSHADER_SOURCEFSHADER_SOURCE是属于glsl代码,
VSHADER_SOURCE中的gl_Position代表的就是点的位置,gl_Positionglsl的内置变量。
你会发现gl_Position的值是个vec4类型,坐标居然有4个值?其实这个是齐次坐标.
对于vec4(x, y, z, w), 真实的世界坐标是 (x/w, y/w, z/w), 所以一般vec4第四个参数我们设置为1。
为什么需要齐次坐标呢,因为三维世界中,向量也是三个坐标表示的, 所以为了区分向量和真实位置引入了第四个参数,向量的第四个参数是0.
js和GLSL通信 上面代码确实画出一个点, 但是是写在一个字符串中的,这肯定不方便我们进行操作啊,所以操作glsl中的变量就很有必要了。
const VSHADER_SOURCE = ` attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 10.0; } `;

如上图,我们对顶点着色器代码 加入了一个attribute, 然后attribute a_Position赋值给glsl内置变量gl_Position,这是否意味着我改动a_Position的值,gl_Position也会改变呢?
js获取并修改attribute 需要的API:
gl.getAttribLocation(gl.program, attribute); gl.vertexAttrib3f(index, x, y, z);

getAttribLocation方法返回了给定WebGLProgram对象中某属性的下标指向位置
vertexAttrib3f可以为顶点attibute变量赋值
现在只需要在gl.drawArrays(gl.POINTS, 0, 1); 之前修改attribute即可
var a_Position = gl.getAttribLocation(gl.program, "a_Position"); gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

目前为止,完整代码如下:
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);

画多个点
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);

你会发现屏幕上存在了两个红点。
drawArrays方法用于从向量数组中绘制图元,每执行一次就要通知GPU渲染图元。
现在两个点还好,如果是成千上万个点呢?我们需要一次性画多个点,这样才能保持性能。
类型化数组TypedArray 对于多个点,我们需要把点的位置存在变量中,我们选择了TypedArray,
它相比普通Array有几个好处:性能 性能 还tm是性能。
对于typedArray介绍看如下代码:
// 下面代码是语法格式,不能直接运行, // 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();

那怎么选择呢, 看如下列表:
类型 单个元素值的范围 大小(bytes) 描述 Web IDL 类型 C 语言中的等价类型
Int8Array -128 to 127 1 8 位二进制有符号整数 byte int8_t
Uint8Array 0 to 255 1 8 位无符号整数(超出范围后从另一边界循环) octet uint8_t
Uint8ClampedArray 0 to 255 1 8 位无符号整数(超出范围后为边界值) octet uint8_t
Int16Array -32768 to 32767 2 16 位二进制有符号整数 short int16_t
Uint16Array 0 to 65535 2 16 位无符号整数 unsigned short uint16_t
Int32Array -2147483648 to 2147483647 4 32 位二进制有符号整数 long int32_t
Uint32Array 0 to 4294967295 4 32 位无符号整数 unsigned long uint32_t
Float32Array 1.2×10^-38 to 3.4×10^38 4 32 位 IEEE 浮点数(7 位有效数字,如 1.1234567 unrestricted float float
Float64Array 5.0×10^-324 to 1.8×10^308 8 64 位 IEEE 浮点数(16 有效数字,如 1.123...15) unrestricted double double
BigInt64Array -2^63 to 2^63-1 8 64 位二进制有符号整数 bigint int64_t (signed long long)
BigUint64Array 0 to 2^64 - 1 8 64 位无符号整数 bigint uint64_t (unsigned long long)
对于这个教程,因为等会我们会使用浮点数,又因为数据不大,所以选择Float32Array.
尝试绘制两个点
  • 将两个点的坐标储存到变量中
    const verties = new Float32Array([0.0, 0.5, -0.5, -0.5]);

  • 把数据挂到缓冲区某个内存位置,写入数据
    const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // verties就是我们自己创建的Float32Array数据 gl.bufferData(gl.ARRAY_BUFFER, verties, gl.STATIC_DRAW);

  • 读取数据并修改attribute
    const a_Position = gl.getAttribLocation(gl.program, "a_Position"); gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(a_Position);

    理解vertexAttribPointer函数可以看我的这篇笔记
    https://note.youdao.com/s/c5E...
  • 修改了attribute,下一步调用绘制命令
    // 因为是绘制两个点,第三个参数输入2 gl.drawArrays(gl.POINTS, 0, 2);

完整代码 除去初始化webGL和工具函数initShaders, 因为每次都写 没有变化...
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);

鼠标监听坐标并写入 如果上面的都理解了话,第三步反而是最简单的了(对于web开发人员来说).
具体功能上面代码都是实现了,只需要:
  • 点击的时候把屏幕坐标转成webGL坐标
  • 把坐标存入Float32Array数据
  • 修改attribute,渲染。
转成webGl坐标
const x = (e.offsetX - 320) / 320; const y = -(e.offsetY - 240) / 240;

其中 320 = 640/2
240 = 480/2
320代表canvas元素的宽, 240代表canvas元素高
存入Float32Array数据 首先Float32Array是固定长度的,无法动态修改,所以需要新建一个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;

最终代码 代码可以在codePen里查看, 如果无法打开的话, 我将代码展示出来:
test 你的浏览器似乎不支持或者禁用了HTML5 < canvas> 元素.

其中cuon-utils.js是封装的一个小工具函数
// 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 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

    推荐阅读