Я пытался реализовать щелчок в моем приложении webgl в течение последних 6 часов, и я не могу найти ничего достаточно ясного по этому вопросу.

До сих пор я придумал в псевдокоде:

screenSpace = mousePosition;
normalizedScreenSpace = (screenSpace.x/screen.width, screenSpace.y/screen.height);

camSpace = invertProjectionMatrix * normalizedScreenSpace;

worldSpace = invertViewMatrix * camSpace;

Распечатываем координаты worldSpace, и они не соответствуют другим объектам в сцене. Что я делаю неправильно?

0
Henri Latreille 23 Фев 2016 в 20:09
Ответы на этот вопрос неудовлетворительны. Один ссылается на его реализацию с ужасно плохой номенклатурой и комментариями, что делает ее бессмысленной, если вы ее еще не понимаете. Или документ с обзором предмета на очень высоком уровне. И версия Three.js, которая вообще не отвечает на вопрос.
 – 
Henri Latreille
23 Фев 2016 в 20:31
Принятый ответ ссылается на реализацию glu unproject, которая, как мне кажется, настолько информативна, насколько это возможно. Ваша нормализация находится в диапазоне 0-1, тогда как она должна быть -1, + 1 в дополнение к тому, что вы, похоже, не применяете разделение перспективы.
 – 
LJᛃ
23 Фев 2016 в 20:50

1 ответ

Лучший ответ

Матрица viewProjection переносит vec3 из мирового пространства в пространство отсечения, и поэтому его инверсия делает обратное, отсекая пространство в мировом пространстве. Чего не хватает, так это разделения перспективы, которое gpu обрабатывает за вас за капотом, поэтому вы также должны это учитывать. Добавьте ширину и высоту экрана, и ваш экран появится в мире:

screenToWorld: function(invViewProjection, screenWidth, screenHeight){
    // expects this[2] (z value) to be -1 if want position at zNear and +1 at zFar

    var x = 2*this[0]/screenWidth - 1.0;
    var y = 1.0 - (2*this[1]/screenHeight); // note: Y axis oriented top -> down in screen space
    var z = this[2];
    this.setXYZ(x,y,z);
    this.applyMat4(invViewProjection);
    var m = invViewProjection;
    var w = m[3] * x + m[7] * y + m[11] * z + m[15]; // required for perspective divide
    if (w !== 0){
        var invW = 1.0/w;
        this[0] *= invW;
        this[1] *= invW;
        this[2] *= invW;
    }

    return this;
},

И обратный расчет:

worldToScreen: function(viewProjectionMatrix, screenWidth, screenHeight){
    var m = viewProjectionMatrix;
    var w = m[3] * this[0] + m[7] * this[1] + m[11] * this[2] + m[15]; // required for perspective divide
    this.applyMat4(viewProjectionMatrix);
    if (w!==0){ // do perspective divide and NDC -> screen conversion
        var invW = 1.0/w;
        this[0] = (this[0]*invW + 1) / 2 * screenWidth;
        this[1] = (1-this[1]*invW) / 2 * screenHeight; // screen space Y goes from top to bottom
        this[2] *= invW;
    } 
    return this;
},
2
WacławJasper 24 Фев 2016 в 20:27
Это отличный качественный ответ. У меня есть еще 2 вопроса: когда вы умножаете исходный XYZ на матрицу проекции перевернутого вида, каково значение W? Предполагается, что он равен 0?
 – 
Henri Latreille
24 Фев 2016 в 17:15
Мой второй вопрос: если бы я хотел получить мировую координату для экранной координаты, как это делает шейдер, но в процессоре, я полагаю, что я бы сделал modelViewProjMatrix * vec4 (vector, 1). Но тогда как рассчитывается перспективное деление? (Я попытался разделить полученный вектор на w, но мои числа отключились)
 – 
Henri Latreille
24 Фев 2016 в 17:21
1
Предполагается, что W равно 1. Что должно произойти, так это то, что я сначала конвертирую в Vec4 (x, y, z, 1), а затем выполняю applyMat4 на vec4. Обратите внимание, что var w = m[3] * x + m[7] * y + m[11] * z + m[15] должно быть +W*m[15], но W неявно предполагается равным 1. Я обновил ответ, чтобы мир отобразил его.
 – 
WacławJasper
24 Фев 2016 в 20:29
У меня вопрос по поводу raycasting screenToWorld. Мне нужны точки из znear и zfar, чтобы создать луч и проверить его на треугольниках в сцене, но что, если все мои треугольники находятся в точке z = 0? Можно ли вызвать screenToWorld один раз, чтобы получить позицию в мировом пространстве при z = 0, или мне нужно получить луч и перевести его на z = 0, чтобы получить координату? Спасибо
 – 
Henri Latreille
25 Фев 2016 в 18:54
Я думаю, вы кое-что путаете. Что вы хотите сделать, так это получить 2 точки сегмента луча в мировом положении. Это можно сделать, установив для vec3 значение (mouse.x, mouse.y, 1), затем screenToWorld для точки A и (mouse.x, mouse.y, -1) для точки B. Теперь у вас есть сегмент луча AB в мире. позиция. Затем поместите свой треугольник и луч в одно и то же координатное пространство и запустите тест лучевого треугольника.
 – 
WacławJasper
26 Фев 2016 в 05:31