查看本文前请先查看 Django开发数据可视化大屏-项目启动配置
通过前面的文章,我们已经创建了一个Django简单项目,并且做了相关的配置,今天我们来制作视图模板,通过JS绘制3D动态背景效果。
我们先新建相关目录和文件:
下面我们来写index.html里面的代码:
DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>3D背景title>
head>
<body>
body>
html>
目前页面没有任何东西,下面加点东西:
DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>3D背景title>
<link rel="stylesheet" href="css/style.css">
head>
<body>
<canvas>canvas>
body>
html>
然后我们来加点样式:
html, body {
overflow: hidden;
touch-action: none;
position: absolute;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #000;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
user-select: none;
background: #000;
cursor: pointer;
}
把样式代码放入style.css文件中,打开网页后是黑色的。
下面我们新建js文件,并引入。
DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>3D背景title>
<link rel="stylesheet" href="css/style.css">
head>
<body>
<canvas>canvas>
<script src="js/script.js">script>
body>
html>
接下来就是写js代码(script.js):
const webGL = (transforms, setup) => {
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl", { alpha: false });
if (!gl) return false;
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
const resize = () => {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
camera.projection(fov);
};
window.addEventListener("resize", () => {
mustResize = true;
});
const clearScreen = () => {
if (mustResize) resize();
gl.clearColor(0.2, 0.225, 0.25, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
};
const u3 = (program, name) => {
const loc = gl.getUniformLocation(program, name);
return {
set (v) {
gl.uniform3f(loc, v[0], v[1], v[2]);
}
}
};
const computeNormals = (v) => {
const n = [];
for (let i = 0; i < v.length; i += 9) {
const p1x = v[i + 3] - v[i];
const p1y = v[i + 4] - v[i + 1];
const p1z = v[i + 5] - v[i + 2];
const p2x = v[i + 6] - v[i];
const p2y = v[i + 7] - v[i + 1];
const p2z = v[i + 8] - v[i + 2];
const p3x = p1y * p2z - p1z * p2y;
const p3y = -(p1x * p2z - p1z * p2x);
const p3z = p1x * p2y - p1y * p2x;
const mag = Math.sqrt(p3x * p3x + p3y * p3y + p3z * p3z);
if (mag === 0) {
n.push(0, 0, 0, 0, 0, 0, 0, 0, 0);
} else {
n.push(p3x / mag, p3y / mag, p3z / mag);
n.push(p3x / mag, p3y / mag, p3z / mag);
n.push(p3x / mag, p3y / mag, p3z / mag);
}
}
return n;
};
const Camera = () => {
const camProj = gl.getUniformLocation(program, "camProj");
const mView = new Float32Array(16);
const mProj = new Float32Array(16);
const camView = gl.getUniformLocation(program, "camView");
return {
dist: -setup.camDist,
move(rx, ry, z) {
mView.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, z, 1]);
if (rx !== 0) transforms.rx(mView, rx);
if (ry !== 0) transforms.ry(mView, ry);
gl.uniformMatrix4fv(camView, false, mView);
},
projection(fov) {
const near = 0.001;
const far = 100;
const top = near * Math.tan((fov * Math.PI) / 360);
const right = top * (gl.drawingBufferWidth / gl.drawingBufferHeight);
const left = -right;
const bottom = -top;
mProj[0] = (2 * near) / (right - left);
mProj[5] = (2 * near) / (top - bottom);
mProj[8] = (right + left) / (right - left);
mProj[9] = (top + bottom) / (top - bottom);
mProj[10] = -(far + near) / (far - near);
mProj[11] = -1;
mProj[14] = -(2 * far * near) / (far - near);
gl.uniformMatrix4fv(camProj, false, mProj);
}
};
};
const buffer = (program, attributeName, size, type) => {
const buffer = gl.createBuffer();
const loc = gl.getAttribLocation(program, attributeName);
gl.enableVertexAttribArray(loc);
return {
load (array) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(array), type);
gl.vertexAttribPointer(loc, size, gl.FLOAT, false, 0, 0);
}
}
};
const createShader = (source, type) => {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader));
}
return shader;
}
const compileShaders = (vertex, fragment) => {
const vertexShader = createShader(vertex, gl.VERTEX_SHADER);
const fragmentShader = createShader(fragment, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
return program;
};
const vertex =
precision highp float;
uniform mat4 camProj, camView;
attribute vec3 aPosition, aNormal;
attribute float aBrillance;
varying vec4 vPosition;
varying vec3 vNormal, vColor;
varying vec3 v_surfaceToLight, v_surfaceToView;
void main() {
vPosition = camView * vec4(aPosition, 1.0);
gl_Position = camProj * vPosition;
vNormal = normalize(vec3(camView * vec4(aNormal, 0.0)));
vColor = vec3(aBrillance, aBrillance, aBrillance);
v_surfaceToLight = vec3(0.0,0.0, 3.0) - gl_Position.xyz;
v_surfaceToView = vec3(0.0,0.0, 0.0) - gl_Position.xyz;
}
;
const fragment =
precision highp float;
varying vec4 vPosition;
varying vec3 vNormal, vColor;
varying vec3 v_surfaceToLight, v_surfaceToView;
uniform vec3 uLightPosition, uAmbientColor;
uniform vec3 uSpecularColor, uDiffuseColor;
const float density = 0.2; // fog density
void main() {
vec3 normal = normalize(vNormal);
vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
vec3 surfaceToViewDirection = normalize(v_surfaceToView);
float dotFromDirection = dot(surfaceToLightDirection, -vec3(0.0,-0.2,-1.0));
float inLight = smoothstep(0.988, 0.99, dotFromDirection);
float light = inLight * dot(normal, surfaceToLightDirection);
float z = gl_FragCoord.z / gl_FragCoord.w;
float fogFactor = clamp(exp2( - density * density * pow(z, 3.0)), 0.0, 1.0);
vec3 lightDirection = normalize(uLightPosition - vPosition.xyz);
vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, vNormal);
float specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), 6.0); // shininess
float diffuseLightWeighting = max(dot(vNormal, lightDirection), 0.0);
vec3 lightWeighting = uAmbientColor
+ uSpecularColor * specularLightWeighting
+ uDiffuseColor * diffuseLightWeighting
+ 5.0 * light * specularLightWeighting + light * uSpecularColor * 0.2;
vec3 color = vColor * lightWeighting;
gl_FragColor = mix(vec4(0.2, 0.225, 0.25, 1.0), vec4(color, 1.0), fogFactor);
}
;
const program = compileShaders(vertex, fragment);
const aPosition = buffer(program, "aPosition", 3, gl.STATIC_DRAW);
const aNormal = buffer(program, "aNormal", 3, gl.STATIC_DRAW);
const aBrillance = buffer(program, "aBrillance", 1, gl.STATIC_DRAW);
u3(program, "uLightPosition").set(setup.lightPosition);
u3(program, "uAmbientColor").set(setup.ambientColor);
u3(program, "uSpecularColor").set(setup.specularColor);
u3(program, "uDiffuseColor").set(setup.diffuseColor);
const fov = setup.fov;
let ry = 0;
let nVertices = 0;
let mustResize = true;
const camera = Camera();
const loadVertices = (vertices, brillance, rx, cz,) => {
const normals = computeNormals(vertices);
nVertices = vertices.length / 3;
aPosition.load(vertices);
aNormal.load(normals);
aBrillance.load(brillance);
camera.rx = rx;
camera.cz = cz;
};
const anim = () => {
requestAnimationFrame(anim);
clearScreen();
camera.move(camera.rx, ry, camera.dist + camera.cz);
ry -= 0.1;
gl.drawArrays(gl.TRIANGLES, 0, nVertices);
};
return {
gl: gl,
loadVertices: loadVertices,
resize: resize,
anim: anim
};
};
我们先定义一个webGL。
接下来写个structure,用来设置绘图结构
const structure = (setup, rules) => {
const shapes = [];
const stack = [];
const vertices = [];
const brillance = [];
let seed = 0;
const transforms = {
x(m, v) {
m[12] += m[0] * v;
m[13] += m[1] * v;
m[14] += m[2] * v;
m[15] += m[3] * v;
},
y(m, v) {
m[12] += m[4] * v;
m[13] += m[5] * v;
m[14] += m[6] * v;
m[15] += m[7] * v;
},
z(m, v) {
m[12] += m[8] * v;
m[13] += m[9] * v;
m[14] += m[10] * v;
m[15] += m[11] * v;
},
s(m, v) {
const a = Array.isArray(v);
const x = a ? v[0] : v;
const y = a ? v[1] : x;
const z = a ? v[2] : x;
m[0] *= x;
m[1] *= x;
m[2] *= x;
m[3] *= x;
m[4] *= y;
m[5] *= y;
m[6] *= y;
m[7] *= y;
m[8] *= z;
m[9] *= z;
m[10] *= z;
m[11] *= z;
},
rx(m, v) {
const rad = Math.PI * (v / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a10 = m[4];
const a11 = m[5];
const a12 = m[6];
const a13 = m[7];
const a20 = m[8];
const a21 = m[9];
const a22 = m[10];
const a23 = m[11];
m[4] = a10 * c + a20 * s;
m[5] = a11 * c + a21 * s;
m[6] = a12 * c + a22 * s;
m[7] = a13 * c + a23 * s;
m[8] = a10 * -s + a20 * c;
m[9] = a11 * -s + a21 * c;
m[10] = a12 * -s + a22 * c;
m[11] = a13 * -s + a23 * c;
},
ry(m, v) {
const rad = Math.PI * (v / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a00 = m[0];
const a01 = m[1];
const a02 = m[2];
const a03 = m[3];
const a20 = m[8];
const a21 = m[9];
const a22 = m[10];
const a23 = m[11];
m[0] = a00 * c + a20 * -s;
m[1] = a01 * c + a21 * -s;
m[2] = a02 * c + a22 * -s;
m[3] = a03 * c + a23 * -s;
m[8] = a00 * s + a20 * c;
m[9] = a01 * s + a21 * c;
m[10] = a02 * s + a22 * c;
m[11] = a03 * s + a23 * c;
},
rz(m, v) {
const rad = Math.PI * (v / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a00 = m[0];
const a01 = m[1];
const a02 = m[2];
const a03 = m[3];
const a10 = m[4];
const a11 = m[5];
const a12 = m[6];
const a13 = m[7];
m[0] = a00 * c + a10 * s;
m[1] = a01 * c + a11 * s;
m[2] = a02 * c + a12 * s;
m[3] = a03 * c + a13 * s;
m[4] = a00 * -s + a10 * c;
m[5] = a01 * -s + a11 * c;
m[6] = a02 * -s + a12 * c;
m[7] = a03 * -s + a13 * c;
},
b(m, v) {
if (v > 0) {
m[16] += v * (1 - m[16]);
} else {
m[16] += v * m[16];
}
}
};
const genCube = (x, y, z) => {
return [
[-x, -y, -z], [-x, y, -z], [ x, y, -z], [ x, -y, -z], [-x, -y, -z], [ x, y, -z],
[ x, y, z], [-x, y, z], [-x, -y, z], [ x, y, z], [-x, -y, z], [ x, -y, z],
[-x, y, -z], [-x, y, z], [ x, y, z], [ x, y, -z], [-x, y, -z], [ x, y, z],
[ x, -y, z], [-x, -y, z], [-x, -y, -z], [ x, -y, z], [-x, -y, -z], [ x, -y, -z],
[-x, -y, -z], [-x, -y, z], [-x, y, z], [-x, y, -z], [-x, -y, -z], [-x, y, z],
[ x, y, z], [ x, -y, z], [ x, -y, -z], [ x, y, z], [ x, -y, -z], [ x, y, -z]
];
};
const vCube = genCube(0.5, 0.5, 0.5);
const webgl = webGL(transforms, setup);
const pushVertices = (v, m) => {
const x = v[0];
const y = v[1];
const z = v[2];
const w = m[3] * x + m[7] * y + m[11] * z + m[15];
vertices.push(
(m[0] * x + m[4] * y + m[8] * z + m[12]) / w,
(m[1] * x + m[5] * y + m[9] * z + m[13]) / w,
(m[2] * x + m[6] * y + m[10] * z + m[14]) / w
);
brillance.push(m[16]);
};
const cube = (m, t) => {
const s = copy(m);
for (const c in t) {
if (transforms[c]) transforms[c](s, t[c]);
else console.log("error: " + c);
}
for (const v of vCube) {
pushVertices(v, s);
}
};
const size = (m) => {
return Math.min(
m[0] * m[0] + m[1] * m[1] + m[2] * m[2],
m[4] * m[4] + m[5] * m[5] + m[6] * m[6],
m[8] * m[8] + m[9] * m[9] + m[10] * m[10]
);
};
const transform = (s, p) => {
const m = copy(s);
m[18]++;
for (const c in p) {
if (transforms[c]) transforms[c](m, p[c]);
else console.log("error: " + c);
}
if (minSize === 0) return m;
else {
if (size(m) < minSize) m[17] = -1;
return m;
}
};
const random = (_) => {
seed = (seed * 16807) % 2147483647;
return (seed - 1) / 2147483646;
};
const copy = (s) => {
return [
s[0], s[1], s[2], s[3],
s[4], s[5], s[6], s[7],
s[8], s[9], s[10], s[11],
s[12], s[13], s[14], s[15],
s[16],
s[17],
s[18],
s[19]
];
};
const newStructure = () => {
console.log("seed:", seed);
vertices.length = 0;
brillance.length = 0;
runshapes(setup.start, setup.transform || {});
webgl.loadVertices(vertices, brillance, 20, 0);
};
const runshapes = (start, t) => {
let comp = 0;
let minComp = minComplexity;
do {
comp = 0;
stack.length = 0;
vertices.length = 0;
brillance.length = 0;
rule[start]([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0], t);
do {
const s = stack.shift();
if (s !== undefined && s[18] maxDepth) {
shapes[s[19]](s);
comp++;
}
} while (stack.length);
} while (comp < minComp--);
};
const singlerule = (i) => {
return (s, t) => {
s = transform(s, t);
if (s[17] === -1) return;
s[19] = i;
stack.push(s);
};
};
const randomrule = (totalWeight, weight, index, len) => {
return (s, t) => {
s = transform(s, t);
if (s[17] === -1) return;
let w = 0;
const r = random() * totalWeight;
for (let i = 0; i < len; i++) {
w += weight[i];
if (r w) {
s[19] = index[i];
stack.push(s);
return;
}
}
};
};
window.rule = {};
window.CUBE = cube;
window.SIZE = size;
if (setup.seed) seed = setup.seed;
else seed = Math.round(Math.random() * 1000);
const minSize = setup.minSize || 0;
const maxDepth = minSize === 0 ? setup.maxDepth || 100 : 1000000;
const minComplexity = setup.minComplexity || 0;
shapes.length = 0;
for (const namerule in rules) {
const r = rules[namerule];
if (Array.isArray(r)) {
let totalWeight = 0;
const weight = [];
const index = [];
for (let i = 0; i < r.length; i += 2) {
totalWeight += r[i];
shapes.push(r[i + 1]);
weight.push(r[i]);
index.push(shapes.length - 1);
}
rule[namerule] = randomrule(totalWeight, weight, index, index.length);
} else {
shapes.push(r);
rule[namerule] = singlerule(shapes.length - 1);
}
}
newStructure();
webgl.resize();
webgl.anim();
window.addEventListener("pointerdown", (e) => newStructure(), false);
};
下面写个配置相关的设置:
const setup = {
start: "start",
transform: { y: 0, s: 0.1 },
maxDepth: 20,
fov: 60,
camDist: 3,
lightPosition: [0, 0.2, -3],
ambientColor: [0.0, 0.0, 0.0],
specularColor: [1.5*0.2, 1.5*0.4, 1.5*0.6],
diffuseColor: [1.5*0.7, 1.5*0.35, 0]
};
下面定义下规则:
const rules = {
start(s) {
CUBE(s, {y: -5, s:[300, 0.1, 300], b: 1});
for (let x = -8; x < 8; x++) {
for (let z = -8; z < 8; z++) {
rule.R1(s, {x: 3 * x, z: 3 * z, s:[0.38, 0.5, 0.4]});
}
}
},
R1: [
100, (s) => {
CUBE(s);
rule.R1(s, { z: 0.4, rx: 90, s: 0.99});
},
100, (s) => {
CUBE(s);
rule.R1(s, { z: 0.4, rx: 90, ry: -90, s: 0.99 });
},
100, (s) => {
CUBE(s);
rule.R1(s, { z: 0.4, rx: 90, ry: 90, s: 0.99 });
},
30, (s) => {
rule.R1(s, { rx: 90, s: [4, 1, 1.3] });
rule.R1(s, { rz: 90, s: [1, 0.7, 1] });
},
40, (s) => {
rule.R1(s, { rx: 90, s: [0.5, 1, 1.3] });
rule.R1(s, { rz: -90, s: [1, 1, 0.7] });
},
]
};
最后我们调用下函数,完成绘制,并转入设置信息和规则信息。
structure(setup, rules);
在头部加上严格模式
"use strict";
为了每次都从新绘制,在头部加上清除控制台上所有信息代码:
console.clear();
现在我们可以看效果了。
是不是感觉像那么回事了?
如果不想要这么多的元素,可以修改下代码中的设置参数:
const setup = {
start: "start",
transform: { y: 0, s: 0.1 },
maxDepth: 10,
fov: 20,
camDist: 3,
lightPosition: [0, 0.2, -3],
ambientColor: [0.0, 0.0, 0.0],
specularColor: [1.5*0.2, 1.5*0.4, 1.5*0.6],
diffuseColor: [1.5*0.7, 1.5*0.35, 0]
};
或者可以再修改下设置的参数;
const setup = {
start: "start",
transform: { y: 0, s: 0.1 },
maxDepth: 10,
fov: 60,
camDist: 3,
lightPosition: [0, 0.2, -3],
ambientColor: [0.0, 0.0, 0.0],
specularColor: [1.5*0.2, 1.5*0.4, 1.5*0.6],
diffuseColor: [1.5*0.7, 1.5*0.35, 0]
};
总之,你如果想调整效果,可以尝试修改下面的代码就可以了。
const setup = {
start: "start",
transform: { y: 0, s: 0.1 },
maxDepth: 10,
fov: 60,
camDist: 1,
lightPosition: [0, 0.2, -3],
ambientColor: [0.0, 0.0, 0.0],
specularColor: [1.5*0.2, 1.5*0.4, 1.5*0.6],
diffuseColor: [1.5*0.7, 1.5*0.35, 0]
};
3d动态背景就到这了,下次我们就要使用这个背景做可视化大屏的背景。
Original: https://blog.csdn.net/huidaoli/article/details/121627910
Author: huidaoli
Title: Django开发数据可视化大屏-JS绘制大屏动态背景-(视图模板制作)
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/734215/
转载文章受原作者版权保护。转载请注明原作者出处!