Форум программистов, компьютерный форум, киберфорум
8Observer8
Войти
Регистрация
Восстановить пароль

Загрузка 3D-моделей из Blender из формата dae без текстур на WebGL 1.0. Физический движок Cannon.js для падения кубиков

Запись от 8Observer8 размещена 08.01.2022 в 18:14
Показов 1482 Комментарии 0

Содержание блога

В примере загружаются стандартные объекты Blender из формата dae без текстур. Цвет каждого объекта задаётся в самой программе - цвет через uniform-переменную передаётся в фрагментный шейдер. Библиотека glMatrix - для математики матриц и векторов. Подключил физический движок Cannon.js. Кубики падают с небольшим смещением по оси Z по направлению к наблюдателю, поэтому башенка заваливается в сторону наблюдателя и падает.

Замечу, что я вместо углов Эйлера использую кватернионы, потому что далее, если делать анимации, например, как эта анимация экспортированная из Blender из формата dae:



то в glMatrix есть удобная функция quat.slerp() для интерполяции кватернионов между двумя key frames. По умолчанию Cannon.js использует ось Z направленную вверх, а в WebGL ось Y направлена вверх, а ось Z в WebGL направлена на наблюдателя, поэтому нужно направить гравитацию вниз по Y:

JavaScript
1
world.gravity.set(0, -9.82, 0);
А все объекты с коллайдерами повернуть на -90 градусов вокруг оси X:

JavaScript
1
glMatrix.quat.fromValues(-0.707, 0, 0, 0.707);
Переводить градусы в кватернионы можно в Blender, переключая на N-панели "XYZ Euler" в "Quaternion WXYZ".



index.html

PHP/HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!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>Colored Objects from DAE. Cannon.js, WebGL, JavaScript</title>
 
    <script src="https://cdn.jsdelivr.net/npm/gl-matrix@3.4.3/gl-matrix-min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script>
 
    <style>
        html,
        body {
            overflow: hidden;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }
 
        #renderCanvas {
            width: 100%;
            height: 100%;
        }
    </style>
</head>
 
<body>
    <canvas id="renderCanvas" width="500" height="500"></canvas>
 
    <script src="js/webgl-context.js"></script>
    <script src="js/shader-program.js"></script>
    <script src="js/vertex-buffers.js"></script>
    <script src="js/object-for-graphics.js"></script>
    <script src="js/object-for-physics.js"></script>
    <script src="js/main.js"></script> 
</body>
 
</html>
js/main.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
let ground, sphere, cone, monkey;
let cubes = [];
 
const projMatrix = glMatrix.mat4.create();
const viewMatrix = glMatrix.mat4.create();
const projViewMatrix = glMatrix.mat4.create();
 
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
 
initVertexBuffers(["assets/cube.dae", "assets/sphere.dae", "assets/cone.dae", "assets/monkey.dae"],
    (vertPosVBOs, normalVBOs, amounts) =>
    {
        const groundSize = [5, 5, 0.25];
        const groundShape = new CANNON.Box(new CANNON.Vec3(groundSize[0], groundSize[1], groundSize[2]));
        ground = new ObjectForPhysics(program, [0, -2, 0], glMatrix.quat.fromValues(-0.707, 0, 0, 0.707), groundSize,
            [0.584, 0.774, 0.474], amounts[0], vertPosVBOs[0], normalVBOs[0], null, null, world, groundShape, true);
 
        const cubeSize = [0.5, 0.5, 0.5];
        const cubeShape = new CANNON.Box(new CANNON.Vec3(cubeSize[0], cubeSize[1], cubeSize[2]));
        for (let i = 0; i < 5; ++i)
        {
            const cube = new ObjectForPhysics(program, [-2, i * 2, i * 0.11], glMatrix.quat.fromValues(-0.707, 0, 0, 0.707), cubeSize,
                [0.784, 0.274, 0.474], amounts[0], vertPosVBOs[0], normalVBOs[0], null, null, world, cubeShape, false);
            cubes.push(cube);
        }
 
        sphere = new ObjectForGraphics(program, [2, 1, 0], [0.796, 0.403, 0.101], amounts[1], vertPosVBOs[1], normalVBOs[1], null, null,);
        cone = new ObjectForGraphics(program, [0, 2, 0], [0.101, 0.556, 0.796], amounts[2], vertPosVBOs[2], normalVBOs[2], null, null,);
        monkey = new ObjectForGraphics(program, [0, 0, 0], [0.321, 0.796, 0.101], amounts[3], vertPosVBOs[3], normalVBOs[3], null, null,);
        init();
    });
 
function init()
{
    glMatrix.mat4.perspective(projMatrix, 55 * Math.PI / 180, 1, 0.1, 500);
    glMatrix.mat4.lookAt(viewMatrix, [3, 2, 12], [0, 0, 0], [0, 1, 0]);
 
    const lightPosition = glMatrix.vec3.fromValues(5, 7, 9);
    const uLightPositionLocation = gl.getUniformLocation(program, "uLightPosition");
    gl.uniform3fv(uLightPositionLocation, lightPosition);
 
    window.onresize = () =>
    {
        const w = canvas.clientWidth;
        const h = canvas.clientHeight;
        gl.canvas.width = w;
        gl.canvas.height = h;
        gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
        glMatrix.mat4.perspective(projMatrix, 55 * Math.PI / 180, w / h, 0.1, 500);
        simulationLoop();
    };
    window.onresize(null);
}
 
const fixedTimeStep = 0.015;
const maxSubSteps = 3;
let lastTime, dt;
 
function simulationLoop(time)
{
    requestAnimationFrame(simulationLoop);
    if (lastTime !== undefined)
    {
        dt = (time - lastTime) / 1000;
        world.step(fixedTimeStep, dt, maxSubSteps);
        for (let i = 0; i < 5; ++i)
        {
            cubes[i].update();
        }
        draw();
    }
    lastTime = time;
}
 
function draw()
{
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    glMatrix.mat4.mul(projViewMatrix, projMatrix, viewMatrix);
 
    ground.draw(projViewMatrix);
    for (let i = 0; i < 5; ++i)
    {
        cubes[i].draw(projViewMatrix);
    }
    sphere.draw(projViewMatrix);
    cone.draw(projViewMatrix);
    monkey.draw(projViewMatrix);
}
js/object-for-graphics.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class ObjectForGraphics
{
    constructor(program, position, color, amountOfVertices, vertPosBuffer, normalBuffer, texCoordBuffer, texture)
    {
        this.position = position;
        this.rotation = glMatrix.quat.create();
        this.scale = [1, 1, 1];
        this.color = color;
 
        this.amountOfVertices = amountOfVertices;
        this.texture = texture;
        this.vertPosBuffer = vertPosBuffer;
        this.normalBuffer = normalBuffer;
        this.texCoordBuffer = texCoordBuffer;
 
        this.mvpMatrix = glMatrix.mat4.create();
        this.modelMatrix = glMatrix.mat4.create();
        this.normalMatrix = glMatrix.mat4.create();
 
        gl.useProgram(program);
        this.uMvpMatrixLocation = gl.getUniformLocation(program, "uMvpMatrix");
        this.uModelMatrixLocation = gl.getUniformLocation(program, "uModelMatrix");
        this.uNormalMatrixLocation = gl.getUniformLocation(program, "uNormalMatrix");
        this.uColorLocation = gl.getUniformLocation(program, "uColor");
        this.program = program;
    }
 
    bind()
    {
        gl.bindBuffer(gl.ARRAY_BUFFER, this.vertPosBuffer);
        gl.vertexAttribPointer(0, 3, gl.FLOAT, false, this.posStride, this.posAttribByteOffset);
        gl.enableVertexAttribArray(0);
 
        gl.bindBuffer(gl.ARRAY_BUFFER, this.normalBuffer);
        gl.vertexAttribPointer(1, 3, gl.FLOAT, false, this.normalStride, this.normalAttribByteOffset);
        gl.enableVertexAttribArray(1);
 
        if (this.texCoordBuffer !== null)
        {
            gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
            gl.vertexAttribPointer(2, 2, gl.FLOAT, false, this.texCoordStride, this.texCoordAttribByteOffset);
            gl.enableVertexAttribArray(2);
        }
 
        if (this.texture !== null)
        {
            gl.bindTexture(gl.TEXTURE_2D, this.texture);
        }
    }
 
    draw(projViewMatrix)
    {
        gl.useProgram(this.program);
 
        glMatrix.mat4.fromRotationTranslationScale(this.modelMatrix, this.rotation, this.position, this.scale);
        glMatrix.mat4.mul(this.mvpMatrix, projViewMatrix, this.modelMatrix);
        gl.uniformMatrix4fv(this.uMvpMatrixLocation, false, this.mvpMatrix);
        gl.uniformMatrix4fv(this.uModelMatrixLocation, false, this.modelMatrix);
 
        glMatrix.mat4.invert(this.normalMatrix, this.modelMatrix);
        glMatrix.mat4.transpose(this.normalMatrix, this.normalMatrix);
        gl.uniformMatrix4fv(this.uNormalMatrixLocation, false, this.normalMatrix);
 
        gl.uniform3fv(this.uColorLocation, this.color);
 
        this.bind();
        gl.drawArrays(gl.TRIANGLES, 0, this.amountOfVertices);
    }
}
js/object-for-physics.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class ObjectForPhysics extends ObjectForGraphics
{
    constructor(program, position, rotation, scale, color, amountOfVertices, vertPosBuffer, normalBuffer,
        texCoordBuffer, texture, world, shape, isStatic)
    {
        super(program, position, color, amountOfVertices, vertPosBuffer, normalBuffer, texCoordBuffer, texture);
        this.rotation = rotation;
        this.scale = scale;
 
        if (isStatic)
        {
            this.body = new CANNON.Body({ mass: 0 });
        }
        else
        {
            this.body = new CANNON.Body({ mass: 10 });
        }
 
        this.body.addShape(shape);
        this.body.position = new CANNON.Vec3(position[0], position[1], position[2]);
        this.body.quaternion = new CANNON.Quaternion(this.rotation[0], this.rotation[1], this.rotation[2], this.rotation[3]);
        world.addBody(this.body);
    }
 
    update()
    {
        this.position[0] = this.body.position.x;
        this.position[1] = this.body.position.y;
        this.position[2] = this.body.position.z;
 
        this.rotation[0] = this.body.quaternion.x;
        this.rotation[1] = this.body.quaternion.y;
        this.rotation[2] = this.body.quaternion.z;
        this.rotation[3] = this.body.quaternion.w;
    }
}
js/shader-program.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const vertShaderSource =
    `attribute vec4 aPosition;
    attribute vec4 aNormal;
    uniform mat4 uMvpMatrix;
    uniform mat4 uModelMatrix;
    uniform mat4 uNormalMatrix;
    varying vec3 vPosition;
    varying vec3 vNormal;
    void main()
    {
        gl_Position = uMvpMatrix * aPosition;
        vPosition = vec3(uModelMatrix * aPosition);
        vNormal = normalize(vec3(uNormalMatrix * aNormal));
    }`;
 
const fragShaderSource =
    `precision mediump float;
    const vec3 lightColor = vec3(1.0, 1.0, 1.0);
    const float ambient = 0.3;
    uniform vec3 uLightPosition;
    uniform vec3 uColor;
    varying vec3 vPosition;
    varying vec3 vNormal;
    void main()
    {
        vec4 color = vec4(0.5, 1.0, 0.5, 1.0);
        vec3 normal = normalize(vNormal);
        vec3 lightDirection = normalize(uLightPosition - vPosition);
        float nDotL = max(dot(lightDirection, normal), ambient);
        vec3 diffuse = lightColor * uColor * nDotL;
        gl_FragColor = vec4(diffuse, 1.0);
    }`;
 
const vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, vertShaderSource);
gl.compileShader(vShader);
let ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
if (!ok) { console.log("vert: " + gl.getShaderInfoLog(vShader)); };
 
const fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, fragShaderSource);
gl.compileShader(fShader);
ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
if (!ok) { console.log("frag: " + gl.getShaderInfoLog(fShader)); };
 
const program = gl.createProgram();
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
gl.bindAttribLocation(program, 0, "aPosition");
gl.bindAttribLocation(program, 1, "aNormal");
gl.linkProgram(program);
ok = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!ok) { console.log("link: " + gl.getProgramInfoLog(program)); };
gl.useProgram(program);
js/vertex-buffers.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
async function initVertexBuffers(modelPaths, callback)
{
    const vertPosBuffers = [];
    const normalBuffers = [];
    const amounts = [];
 
    for (let i = 0; i < modelPaths.length; ++i)
    {
        const contentResponse = await fetch(modelPaths[i]);
        const content = await contentResponse.text();
 
        const parser = new DOMParser();
        const xmlDoc = parser.parseFromString(content, "text/xml");
 
        const expForIndexes = "//*[local-name() = 'p']/text()";
        let nodes = xmlDoc.evaluate(expForIndexes, xmlDoc, null, XPathResult.ANY_TYPE, null);
        let result = nodes.iterateNext();
        const order = result.textContent.trim().split(" ").map((value) => { return parseInt(value); });
        // console.log(order);
 
        const partOfPositionId = "mesh-positions-array";
        const expForPositions = `//*[local-name() = 'float_array'][substring(@id, string-length(@id) -` +
            `string-length('${partOfPositionId}') + 1) = '${partOfPositionId}']`;
        nodes = xmlDoc.evaluate(expForPositions, xmlDoc, null, XPathResult.ANY_TYPE, null);
        result = nodes.iterateNext();
        const positions = result.textContent.trim().split(" ").map((value) => { return parseFloat(value); });
        // console.log(positions);
 
        const partOfNormalId = "mesh-normals-array";
        const expForNormals = `//*[local-name() = 'float_array'][substring(@id, string-length(@id) -` +
            `string-length('${partOfNormalId}') + 1) = '${partOfNormalId}']`;
        nodes = xmlDoc.evaluate(expForNormals, xmlDoc, null, XPathResult.ANY_TYPE, null);
        result = nodes.iterateNext();
        const normals = result.textContent.trim().split(" ").map((value) => { return parseFloat(value); });
        // console.log(normals);
 
        const correctionMatrix = glMatrix.mat4.create();
        glMatrix.mat4.fromXRotation(correctionMatrix, -Math.PI / 2);
 
        const vertPosResult = [];
        const normalsResult = [];
 
        const amountOfTriangles = order.length / 6;
        for (let i = 0; i < amountOfTriangles; ++i)
        {
            for (let j = 0; j < 6; ++j)
            {
                if ((i * 6 + j) % 2 === 0)
                {
                    const vx = positions[order[i * 6 + j] * 3 + 0];
                    const vy = positions[order[i * 6 + j] * 3 + 1];
                    const vz = positions[order[i * 6 + j] * 3 + 2];
                    const oldPos = glMatrix.vec3.fromValues(vx, vy, vz);
                    const newPos = glMatrix.vec3.create();
                    glMatrix.vec3.transformMat4(newPos, oldPos, correctionMatrix);
                    vertPosResult.push(newPos[0]);
                    vertPosResult.push(newPos[1]);
                    vertPosResult.push(newPos[2]);
                }
                else
                {
                    const nx = normals[order[i * 6 + j] * 3 + 0];
                    const ny = normals[order[i * 6 + j] * 3 + 1];
                    const nz = normals[order[i * 6 + j] * 3 + 2];
                    const oldNormal = glMatrix.vec3.fromValues(nx, ny, nz);
                    const newNormal = glMatrix.vec3.create();
                    glMatrix.vec3.transformMat4(newNormal, oldNormal, correctionMatrix);
                    normalsResult.push(newNormal[0]);
                    normalsResult.push(newNormal[1]);
                    normalsResult.push(newNormal[2]);
                }
            }
        }
        // console.log(vertPosResult);
 
        const vertPosBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, vertPosBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertPosResult), gl.STATIC_DRAW);
 
        const normalBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normalsResult), gl.STATIC_DRAW);
 
        vertPosBuffers.push(vertPosBuffer);
        normalBuffers.push(normalBuffer);
        amounts.push(vertPosResult.length / 3);
    }
 
    callback(vertPosBuffers, normalBuffers, amounts);
}
js/webgl-context.js

JavaScript
1
2
3
4
const canvas = document.getElementById("renderCanvas");
const gl = canvas.getContext("webgl");
gl.enable(gl.DEPTH_TEST);
gl.clearColor(0.5, 0.6, 0.8, 1.0);
Миниатюры
Нажмите на изображение для увеличения
Название: transform-animation-from-dae.gif
Просмотров: 460
Размер:	316.7 Кб
ID:	7368   Нажмите на изображение для увеличения
Название: colored-objects-from-dae-and-cannonjs.gif
Просмотров: 506
Размер:	595.0 Кб
ID:	7369  
Вложения
Тип файла: zip colored-objects-from-dae-cannonjs-webgl-js.zip (357.3 Кб, 503 просмотров)
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Новые блоги и статьи
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
[EasyBuilder Pro] Памятка по разработке для панелей Weintek
ФедосеевПавел 26.08.2026
Памятка по разработке для панелей Weintek ВВЕДЕНИЕ Ранее, при реализации проектов основное внимание уделял разработке управляющей программы для контроллера, а панели оператора доставалось время. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru