首页 > 解决方案 > 如何在我的 3d raymarched 世界中正确平移/旋转我的相机

问题描述

我想要达到的目标

所以我是一个分形爱好者,并决定使用 raymarching 在 WebGL 中构建一个 2D/3D 分形生成器,使用 Typescript 作为脚本语言。我多年来一直是 C#/Typescript 开发人员,但对 3D 编程的经验为零,我使用 Michael Walczyk 的博客作为起点。我在这里使用的一些代码来自他的教程。

我添加了可以使用 WASDQEZC 键在对象中移动的功能。WS = 前后扫射,AD = 左右扫射,QE = 上下扫射,ZC = 左右滚动。我将它与鼠标外观功能结合起来,该功能在鼠标指针位于渲染画布上的方向上移动。所以我想要的是完全自由的移动,就像在太空模拟中一样。为此,我使用了一个单独的相机旋转矩阵和平移值,并将它们发送到着色器,如下所示:

  setCameraMatrix(): void {
    let cameraRotationMatrixLocation: WebGLUniformLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_cameraRotation");
    let cameraTranslationLocation: WebGLUniformLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_cameraTranslation");
    let foVLocation: WebGLUniformLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_foV");

    //add point of camera rotation at beginning
    let cameraRotationMatrix: Array<number> = Matrix3D.identity();

    //set camera rotation and translation, Z-axis (heading) first, then X-axis (pitch), then Y-axis (roll)
    cameraRotationMatrix = Matrix3D.multiply(cameraRotationMatrix, Matrix3D.rotateZ(this.cameraRotateZ * Math.PI / 180));
    cameraRotationMatrix = Matrix3D.multiply(cameraRotationMatrix, Matrix3D.rotateX(this.cameraRotateX * Math.PI / 180));
    cameraRotationMatrix = Matrix3D.multiply(cameraRotationMatrix, Matrix3D.rotateY(this.cameraRotateY * Math.PI / 180));
    //cameraRotationMatrix = Matrix3D.multiply(cameraRotationMatrix, Matrix3D.translate(this.cameraTranslateX, this.cameraTranslateY, this.cameraTranslateZ));
    cameraRotationMatrix = Matrix3D.inverse(cameraRotationMatrix);

    let cameraPosition: Array<number> = [
      this.cameraTranslateX,
      this.cameraTranslateY,
      -this.cameraTranslateZ,
    ];

    this.currentContext.uniformMatrix4fv(cameraRotationMatrixLocation, false, cameraRotationMatrix);
    this.currentContext.uniform3fv(cameraTranslationLocation, cameraPosition);
    this.currentContext.uniform1f(foVLocation, this.foV);
  }

我尝试将相机平移值添加到相机矩阵,但没有奏效。我得到了奇怪的失真效果并且无法正确处理,因此我将那条线注释掉并暂时将其留在那里以便清楚起见。我这样做的原因是因为我的 GLSL 代码的构造方式:

来自片段着色器的主函数与对 ray_march 函数的调用。v_position是一个 vec2,x,y 坐标来自顶点着色器。:

    void main() {
      outColor = vec4(ray_march(u_cameraTranslation, u_cameraRotation * vec4(rayDirection(u_foV,v_position),1), u_world, vec3(u_light * vec4(0,0,0,1)).xyz ).xyz,1);
    }

我正在使用的 ray_march 函数。这源自 Michael Walczyk 博客中的示例代码。

    vec3 ray_march(in vec3 ro, in vec4 rd, in mat4 wm, in vec3 lightPosition) //ro = ray origin, rd = ray direction gt = geometry position after matrix multiplication
    {
        float total_distance_traveled = 0.0;
        const int NUMBER_OF_STEPS = 1024;

        float MINIMUM_HIT_DISTANCE = 0.001 * min_hit_distance_correction;

        const float MAXIMUM_TRACE_DISTANCE = 1000.0;

        for (int i = 0; i < NUMBER_OF_STEPS; i++)
        {
            vec3 current_position = ro + total_distance_traveled * vec3(rd);

            float distance_to_closest = map(current_position, wm);

            if (distance_to_closest < MINIMUM_HIT_DISTANCE) 
            {
              vec3 normal = calculate_normal(current_position, wm);
              vec3 outColor = vec3(1.0,0,0);
              vec3 v_surfaceToLight = lightPosition - current_position;
              vec3 v_surfaceToView = ro - current_position;

              //insert lighting code below this line

              return outColor;
            }

            if (total_distance_traveled > MAXIMUM_TRACE_DISTANCE)
            {
              break;
            }

            total_distance_traveled += distance_to_closest;
        }

        return vec3(0.25);//gray background
    }

我正在使用的 rayDirection 函数。

  vec3 rayDirection(float fieldOfView, vec2 p) {
    float z = 1.0 / (tan(radians(fieldOfView) / 2.0));
    return normalize(vec3(p.xy, -z));
  }

我的问题

我在 3d 世界中正确移动和旋转相机时遇到问题。我通过应用一些三角函数来使运动正确来做到这一点。例如,当我向前移动时,就是 Z 轴。但是当我向右转 90 度时,X 轴现在变成了 Z 轴。我正在使用三角函数来纠正这个问题,实际上得到了一些工作,但现在我陷入了三角函数的泥潭,看不到尽头,我觉得必须有一种更好、更简单的方法。看看我在说什么,这里是'move'函数的代码:

  move(event: KeyboardEvent): void {
    
    //strafe forward-back
    let tXForwardBack: number = (Math.sin(this.cameraRotateY * Math.PI / 180) * Math.cos(this.cameraRotateX * Math.PI / 180)) * this.clipSpaceFactor * this.speed;
    let tYForwardBack: number = Math.sin(this.cameraRotateX * Math.PI / 180) * this.speed;
    let tZForwardBack: number = (Math.cos(this.cameraRotateY * Math.PI / 180) * Math.cos(this.cameraRotateX * Math.PI / 180)) * this.clipSpaceFactor * this.speed;

    //strafe up-down
    let tXUpDown: number = ((Math.sin(this.cameraRotateX * Math.PI / 180) * Math.sin(this.cameraRotateY * Math.PI / 180)) * this.clipSpaceFactor * this.speed);
    let tYUpDown: number = Math.cos(this.cameraRotateX * Math.PI / 180) * this.speed;
    let tZUpDown: number = Math.sin(this.cameraRotateX * Math.PI / 180) * Math.cos(this.cameraRotateY * Math.PI / 180) * this.clipSpaceFactor * this.speed;

    //strafe left-right without roll. TODO: implement roll
    let tXLeftRight: number = Math.cos(this.cameraRotateY * Math.PI / 180) * this.clipSpaceFactor * this.speed;
    let tYLeftRight: number = 0;
    let tZLeftRight: number = Math.sin(this.cameraRotateY * Math.PI / 180) * this.clipSpaceFactor * this.speed;

    switch (event.key) {
      case "w": { //strafe forward
        this.cameraTranslateX = this.cameraTranslateX + tXForwardBack;
        this.cameraTranslateY = this.cameraTranslateY - tYForwardBack;
        this.cameraTranslateZ = this.cameraTranslateZ + tZForwardBack;
        //this.cameraTranslateZ = this.cameraTranslateZ + (this.clipSpaceFactor * this.speed);
        break;
      }
      case "s": { //strafe back
        this.cameraTranslateX = this.cameraTranslateX - tXForwardBack;
        this.cameraTranslateY = this.cameraTranslateY + tYForwardBack;
        this.cameraTranslateZ = this.cameraTranslateZ - tZForwardBack;
        break;
      }
      case "a": {//strafe left
        this.cameraTranslateX = this.cameraTranslateX - tXLeftRight;
        this.cameraTranslateY = this.cameraTranslateY + tYLeftRight;
        this.cameraTranslateZ = this.cameraTranslateZ + tZLeftRight;
        break;
      }
      case "d": { //strafe right
        this.cameraTranslateX = this.cameraTranslateX + tXLeftRight;
        this.cameraTranslateY = this.cameraTranslateY - tYLeftRight;
        this.cameraTranslateZ = this.cameraTranslateZ - tZLeftRight;
        break;
      }
      case "q": { //strafe up
        this.cameraTranslateX = this.cameraTranslateX + tXUpDown;
        this.cameraTranslateY = this.cameraTranslateY + tYUpDown;
        this.cameraTranslateZ = this.cameraTranslateZ + tZUpDown;
        break;
      }
      case "e": { //strafe down
        this.cameraTranslateX = this.cameraTranslateX - tXUpDown;
        this.cameraTranslateY = this.cameraTranslateY - tYUpDown;
        this.cameraTranslateZ = this.cameraTranslateZ - tZUpDown;
        break;
      }
      case "z": { //roll left
        this.cameraRotateZ = (this.cameraRotateZ + (this.sensitivity * this.speed)) % 360;
        break;
      }
      case "c": { //roll right
        this.cameraRotateZ = (this.cameraRotateZ - (this.sensitivity * this.speed)) % 360;
        break;
      }
    }

它实际上在某种程度上有效,但你可以看到这是怎么回事:(另外,当我沿着 Y 轴上下查看时,我得到一个“死区”。我发现这个线程似乎描述了我的问题并说“诀窍是将平移应用到 z 轴,但在相机的局部坐标系中。”

但是我如何用我现有的代码做到这一点?我尝试将世界矩阵乘以u_world旋转矩阵,u_rotationMatrix但随后照明也会发生变化,它只是对象旋转而不是单独的相机旋转。在我发布的线程中,没有照明,因此将相机矩阵与世界矩阵相乘对他们有用。但它不适合我,因为我实施了照明。此外,我似乎无法以这种方式单独应用法线,因此我只将法线应用到世界矩阵而不是相机旋转矩阵,这样当我旋转/平移相机时,照明就会保持在原位。

我可以获得世界矩阵和单独相机矩阵的正确法线的唯一方法是将rotationMatrix与rayDirection相乘,如下所示u_cameraRotation * vec4(rayDirection(u_foV,v_position),1)。但是当我这样做时,我必须应用所有这些可怕的、部分工作的三角函数来得到一些像样的东西。我想要的是让它像“诀窍是将平移应用到 z 轴但在相机的局部坐标系中”一样工作。

但我不知道怎么做。我尝试了各种各样的事情,但我目前陷入困境。任何帮助将不胜感激。我想我已经足够充分地概述了我的问题,如果您遗漏了什么,请告诉我。提前致谢。

标签: typescriptglslwebglrotational-matricesraymarching

解决方案


看来我自己找到了答案。我从这个问题中应用了 Adisak 的部分答案,这与我的类似。我EulerAnglesToMatrix用旋转顺序 ZXY 应用了他的函数,然后像这样提取 x、y 和 z 轴:

    let mx: Array<number> = Matrix3D.eulerAnglesToMatrix(pitch, yaw, roll, "ZXY");

    let xAxis: Array<number> = mx.slice(0, 3); //x,y,z
    let yAxis: Array<number> = mx.slice(3, 6); //x,y,z
    let zAxis: Array<number> = mx.slice(6, 9); //x,y,z

然后我像这样应用翻译,将 vec3 设置为片段着色器[this.cameraTranslateX,this.cameraTranslateY,this.cameraTranslateZ]的统一 vec3u_cameraTranslation变量:

    switch (event.key) {
      case "w": { //strafe forward
        this.cameraTranslateX = this.cameraTranslateX - ((zAxis[0]) * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY - ((zAxis[1]  ) * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ + ((zAxis[2] ) * this.clipSpaceFactor * this.speed);
        break;
      }
      case "s": { //strafe back
        this.cameraTranslateX = this.cameraTranslateX + ((zAxis[0] ) * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY + ((zAxis[1] ) * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ - ((zAxis[2] ) * this.clipSpaceFactor * this.speed);
        break;
      }
      case "a": {//strafe left
        this.cameraTranslateX = this.cameraTranslateX - (xAxis[0] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY - (xAxis[1] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ + (xAxis[2] * this.clipSpaceFactor * this.speed);
        break;
      }
      case "d": { //strafe right
        this.cameraTranslateX = this.cameraTranslateX + (xAxis[0] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY + (xAxis[1] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ - (xAxis[2] * this.clipSpaceFactor * this.speed);
        break;
      }
      case "q": { //strafe up
        this.cameraTranslateX = this.cameraTranslateX + (yAxis[0] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY + (yAxis[1] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ - (yAxis[2] * this.clipSpaceFactor * this.speed);
        break;
      }
      case "e": { //strafe down
        this.cameraTranslateX = this.cameraTranslateX - (yAxis[0] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY - (yAxis[1] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ + (yAxis[2] * this.clipSpaceFactor * this.speed);
        break;
      }
      case "z": { //roll left
        this.cameraRotateZ = (this.cameraRotateZ + (this.sensitivity * this.speed)) % 360;
        break;
      }
      case "c": { //roll right
        this.cameraRotateZ = (this.cameraRotateZ - (this.sensitivity * this.speed)) % 360;
        break;
      }
    }

我完好无损地保留了光线追踪功能。这正是我想要的。


推荐阅读