WebGL热力图(一)
东北小麦客 2024-09-11 WebGLCanvas
# 参考资料
# WebGL案例
# index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shader</title>
</head>
<body>
<canvas id="canvasDom" width="1200" height="804"></canvas>
</body>
<script src="./main.ts" type="module"></script>
</html>
# util.ts
/**
* 齐次坐标(四维齐次坐标) 统一处理平移、旋转、缩放等变换
* ` (x,y,z,w) `
* - z 值代表深度测试, 来确定物体的前后关系
* - w 值通常用来表示 位置 还是 方向; `(x, y, z, 1.0) 表示一个位置` `(x, y, z, 0.0) 表示一个方向`
*/
/**
* 坐标系转换-Canvas转WebGL (Canvas TO WebGL)返回的数值的精度很关键!!!
* @param {number} 画布上x轴的点位坐标
* @param {number} 画布上y轴的点位坐标
* @param {number} 画布宽度
* @param {number} 画布高度
* @returns {number[]} ndc坐标(三维, 浮点数, 最多保留4位小数)
* - Canvas 二维坐标系
* (像素级别)默认的坐标系的原点 (0, 0) 位于画布的左上角。坐标系中的 x 值沿水平方向增加,y 值沿垂直方向增加,即 x 向右增大,y 向下增大
* - WebGL 三维坐标系(z轴默认填充0.0, z轴的分量代表深度测试, 来确定物体的前后关系)
* 原点是 (0, 0, 0),位于视口的中心。WebGL 使用的是一个标准化坐标系,范围通常是 [-1, 1],这意味着 x 和 y 的值在 NDC 中会被标准化到这个范围内
*/
export function coordCanvasToWebGL(x_canvas: number, y_canvas: number, canvasWidth: number, canvasHeight: number): number[] {
const x_ndc = +((x_canvas / (canvasWidth / 2)) - 1).toFixed(6);
const y_ndc = +(1 - (y_canvas / (canvasHeight / 2))).toFixed(6);
return [x_ndc, y_ndc, 0.0];
}
/**
* 坐标系转换-Canvas转WebGL (WebGL TO Canvas)
* @param {number} 画布上x轴的点位坐标
* @param {number} 画布上y轴的点位坐标
* @param {number} 画布宽度
* @param {number} 画布高度
* @returns {number[]} 画布坐标(二维, 整数)
* - Canvas 二维坐标系
* (像素级别)默认的坐标系的原点 (0, 0) 位于画布的左上角。坐标系中的 x 值沿水平方向增加,y 值沿垂直方向增加,即 x 向右增大,y 向下增大
* - WebGL 三维坐标系(z轴默认填充0.0, z轴的分量代表深度测试, 来确定物体的前后关系)
* 原点是 (0, 0),位于视口的中心。WebGL 使用的是一个标准化坐标系,范围通常是 [-1, 1],这意味着 x 和 y 的值在 NDC 中会被标准化到这个范围内
*/
export function coordWebGLToCanvas(x_ndc: number, y_ndc: number, canvasWidth: number, canvasHeight: number): number[] {
const x_canvas = Math.ceil((x_ndc + 1) * (canvasWidth / 2));
const y_canvas = Math.ceil((1 - y_ndc) * (canvasHeight / 2));
return [x_canvas, y_canvas];
}
# vertShader.vs
顶点着色器
#version 300 es
in vec4 a_position;
// out vec4 cur_position;
void main() {
// float curPointSize = 60.0;
// gl_PointSize = curPointSize;
// cur_position = a_position;
gl_Position = a_position;
}
# fragShader.fs
片元着色器
#version 300 es
precision mediump float;
// in vec4 cur_position;
out vec4 color;
uniform vec3 iResolution;
// test data
// uniform int PointCount;
const int PointCount = 30;
uniform vec3 Points[30];
uniform float HEAT_MAX;
const float PointRadius = .75;
vec4 gradient(float w, vec2 uv) {
w = pow(clamp(w, 0., 1.) * 3.14159 * .5, .9);
vec3 c = vec3(sin(w), sin(w * 2.), cos(w)) * 1.1;
float a = clamp(w, 0.5, 1.);
return mix(vec4(0.25, 1.0, 0.0, 0.5), vec4(c, a), w*0.2);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (2. * fragCoord - iResolution.xy) / min(iResolution.x, iResolution.y);
float d = 0.;
for (int i = 0; i < PointCount; i++) {
vec3 v = Points[i];
float intensity = v.z / HEAT_MAX;
float dis = length(uv - v.xy);
float pd = (1.0 - dis / PointRadius) * intensity;
d += pow(max(0., pd), 2.);
}
fragColor = gradient(d, uv);
}
void main() {
vec4 col = vec4(1.0, 0.0, 0.0, 1.0);
mainImage(col, gl_FragCoord.xy);
color = col;
}
# main.ts
import { coordCanvasToWebGL } from "./util";
/**
* 测试的温度测点
*/
let tempPointList = [
{
"x": 712,
"y": 662,
"value": 28.2
},
{
"x": 712,
"y": 486,
"value": 28.2
},
{
"x": 712,
"y": 310,
"value": 28.1
},
{
"x": 90,
"y": 299,
"value": 28
},
{
"x": 90,
"y": 470,
"value": 28.1
},
{
"x": 90,
"y": 689,
"value": 28.1
},
{
"x": 286,
"y": 678,
"value": 28
},
{
"x": 286,
"y": 474,
"value": 28
},
{
"x": 286,
"y": 282,
"value": 28
},
{
"x": 492,
"y": 694,
"value": 27.8
},
{
"x": 498,
"y": 480,
"value": 27.7
},
{
"x": 498,
"y": 291,
"value": 27.7
},
{
"x": 932,
"y": 300,
"value": 27.6
},
{
"x": 932,
"y": 489,
"value": 27.6
},
{
"x": 932,
"y": 650,
"value": 27.6
}
];
/**
* webgl2绘制上下文
*/
var gl:WebGL2RenderingContext;
/**
* 获取着色器源码-fn
* @param {string} 文件名称
* @returns {Promise<string>} 着色器源码
*/
function getShaderSource(filename: string) {
return fetch(filename).then((res) => res.text());
}
/**
* 创建着色器-fn
* @param {GLenum} 着色器类型
* @param {string} 着色器源码
* @returns {WebGLShader} shader着色器
*/
function createShaderFn(shaderType: GLenum, shaderSource:string) {
// 创建着色器
const shader = gl.createShader(shaderType) as WebGLShader;
// 着色器添加源码
gl.shaderSource(shader, shaderSource);
// 编译着色器源码
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const shaderTypeText = gl.VERTEX_SHADER === shaderType ? 'vertexShader' : 'fragmentShader';
console.error(`Error compiling ${shaderTypeText}:`, gl.getShaderInfoLog(shader)); // 输出编译错误信息
}
return shader;
}
/**
* 创建shader程序-fn
* @param {WebGLShader} 顶点着色器
* @param {WebGLShader} 片元着色器
* @returns {WebGLProgram} shader程序
*/
function createProgramFn(vertexShader: WebGLShader, fragmentShader: WebGLShader) {
// 创建shader程序
const shaderProgram = gl.createProgram() as WebGLProgram;
// shader程序添加着色器
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
// 链接shader程序
gl.linkProgram(shaderProgram);
// 使用shader程序
gl.useProgram(shaderProgram);
return shaderProgram;
}
async function main() {
// 获取顶点着色器和片元着色器的GLSL代码
const vertexShaderSource = await getShaderSource('./vertShader.vs');
const fragmentShaderSource = await getShaderSource('./fragShader.fs');
const canvasDom = document.getElementById('canvasDom') as HTMLCanvasElement; // 尝试获取canvas dom节点
gl = canvasDom.getContext('webgl2') as WebGL2RenderingContext;
// 创建着色器(顶点、片元着色器)
const vertexShader = createShaderFn(gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShaderFn(gl.FRAGMENT_SHADER, fragmentShaderSource);
// 创建shader程序
const shaderProgram = createProgramFn(vertexShader, fragmentShader);
const canvasNdcList = [
-1.0, 1.0,
1.0, 1.0,
-1.0, -1.0,
1.0, -1.0,
];
const triangleVertices = new Float32Array(canvasNdcList);
// 创建缓冲区
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);
// 获取a_position属性的位置
var a_Position = gl.getAttribLocation(shaderProgram, 'a_position');
// 将缓冲区的数据分配给a_Position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 开启a_Position变量
gl.enableVertexAttribArray(a_Position);
const iResolutionAddr = gl.getUniformLocation(shaderProgram, "iResolution");
//给iResolutionAddr 字段设置数据 ,是canvas的像素宽高
gl.uniform3f(iResolutionAddr, gl.canvas.width, gl.canvas.height, 1.0);
const points = [];
for(const item of tempPointList) {
const point = coordCanvasToWebGL(item.x, item.y, gl.canvas.width, gl.canvas.height);
point[2] = item.value;
points.push(point);
}
// points.push([1.0, -1.0, 35]);
// points.push([0, 0.0, 0]);
// console.log(points);
// 获取 uniform 位置
const uniformAddr = gl.getUniformLocation(shaderProgram, "Points");
gl.uniform3fv(uniformAddr, new Float32Array(points.flat()));
const HEATMAXAddr = gl.getUniformLocation(shaderProgram, "HEAT_MAX");
gl.uniform1f(HEATMAXAddr, 35.5);
// gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// gl.clearColor(0.0, 1.0, 0.0, 0.75); // 设置canvas的清除颜色
// gl.clear(gl.COLOR_BUFFER_BIT); // 清除canvas
// 执行绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// 绘制定位点
// const pointsVertices = new Float32Array(points.flat());
// // 创建缓冲区
// const pointsBuffer = gl.createBuffer();
// gl.bindBuffer(gl.ARRAY_BUFFER, pointsBuffer);
// gl.bufferData(gl.ARRAY_BUFFER, pointsVertices, gl.STATIC_DRAW);
// // 获取a_position属性的位置
// var a_Position = gl.getAttribLocation(shaderProgram, 'a_position');
// // 将缓冲区的数据分配给a_Position变量
// gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
// // 开启a_Position变量
// gl.enableVertexAttribArray(a_Position);
// gl.drawArrays(gl.POINTS, 0, points.length);
}
main();