Skip to content

Latest commit

 

History

History
232 lines (182 loc) · 8.78 KB

3d.md

File metadata and controls

232 lines (182 loc) · 8.78 KB
title date tags
软光栅器MicroRenderer(二) 进入三维
2022-05-27 16:38:00 -0700
MicroRenderer

软光栅器MicroRenderer(二) 进入三维

代码见:https://github.com/LJHG/MicroRenderer

本次主要讨论的内容是 mvp变换,三角形的裁切以及相机控制。

1. mvp变换

具体的mvp矩阵推导这里就不细讲了,关于mvp变换我基本上是参照了games101课程里的内容,可以看一下这里:Assignment1 · GitBook (ljhblog.top) 。其中我的view matrix 和 projection matrix 是完全按照课程上的描述实现的,不过最后的我的视图矩阵和透视矩阵好像实现出来好像都有点问题。。。所以目前我的这两个矩阵都是抄的别人的,暂时还没啥问题。

一旦你会计算mvp矩阵了,剩下的事情就很简单了,只需要去继承Shader然后实现一个新的类,并且把vertex shader重新实现为三维版本就可以了,在三维空间的shader里,vertex shader的职责就是把顶点进行mvp变换。

// 3d shader, implement mvp transformation for vertex shader, fragment shader just pass color
class ThreeDShader: public Shader{
public:
    ThreeDShader(glm::mat4 _modelMatrix, glm::mat4 _viewMatrix, glm::mat4 _projectionMatrix);
    VertexOutData vertexShader(VertexData &v) override;
    glm::vec4 fragmentShader(VertexOutData &v) override;
};
VertexOutData ThreeDShader::vertexShader(VertexData &v) {
    VertexOutData vod;
    vod.position = glm::vec4(v.position,1.0f);
    vod.position = projectionMatrix * viewMatrix * modelMatrix * vod.position; //mvp transformation
    vod.color= v.color;
    vod.normal = v.normal;
    return vod;
}

glm::vec4 ThreeDShader::fragmentShader(VertexOutData &v) {
    glm::vec4 result;
    result = v.color;
    return result;
}

因为不涉及具体的着色,所以这里我们的 fragment shader仍然传递颜色即可。

2. 三角形的裁切

究极野路子,谨慎参考

关于三角形的裁切这个东西,一般来说应该是放在光栅化之前来做:渲染管线中,背面剔除和齐次空间的裁剪哪个先进行?为什么? - 知乎 (zhihu.com)。裁切有很多很复杂的算法。一般来说,大家会把三角形裁切和背面剔除放在一起来讨论。

不过我这里就比较拉了,为了实现最简单的方法,我把裁切放在了光栅化的时候来做。具体的原理就是,通过对经过了视口变换的坐标进行判断,也就是说如果x坐标不在 [0,width] 内,y坐标不在 [0,height]内,z坐标不在[-1,1]内,那么就不去画它。。。

具体的代码就是这样:

void ShadingPipeline::fillRasterization(VertexOutData &v1, VertexOutData &v2, VertexOutData &v3) {
   // I don't know name for this algorithm
   // ref: GAMES101 assignment2: https://www.ljhblog.top/CG/GAMES101/assignment2.html
   float x1 = v1.position[0]; float y1 = v1.position[1]; float z1 = v1.position[2];
   float x2 = v2.position[0]; float y2 = v2.position[1]; float z2 = v2.position[2];
   float x3 = v3.position[0]; float y3 = v3.position[1]; float z3 = v3.position[2];

   /**
    * 关于clip是否应该在光栅化时做的问题
    * 一般来说,clip好像应该在做光栅化前来做,但是如果直接在光栅化时做会十分方便...
    * 这里我在光栅化时作了两个clip:
    * 1. 一个判断z,如果z不在[-1,1]内,那么就说明不再视线范围内,直接不画
    * 2. 另一个对left right bottom top 做一个裁剪,不去管屏幕空间外的像素了
    */
   // clip1
   if(z1<-1.0f || z1 >1.0f || z2<-1.0f || z2 > 1.0f || z3<-1.0f || z3>1.0f){
       //out of depth range, clip
       return;
   }

   // get bounding box of the triangle
   int left = static_cast<int>(std::min(std::min(x1,x2),x3));
   int bottom = static_cast<int>(std::min(std::min(y1,y2),y3));
   int right = static_cast<int>(std::max(std::max(x1,x2),x3)) + 1; //ceil
   int top = static_cast<int>(std::max(std::max(y1,y2),y3)) + 1; //ceil

   // clip2
   left = std::max(left,0);
   bottom = std::max(bottom,0);
   right = std::min(right,width-1); // need to minus 1, or will draw at the other edge...
   top = std::min(top,height-1); // need to minus 1, or will draw at the other edge...

   for(int x = left;x<=right;x++){
       for(int y=bottom;y<=top;y++){
           if(MathUtils::insideTriangle(static_cast<float>(x),static_cast<float>(y),x1,y1,x2,y2,x3,y3)){
               VertexOutData tmp = MathUtils::barycentricLerp(x,y,v1,v2,v3);
               int index = y*width+x;
               if(tmp.position[2] < zBuffer[index]){
                   zBuffer[index] = tmp.position[2];
                   //fragment shader
                   glm::vec4 color = shader->fragmentShader(tmp);
                   int colorIndex =  index*3; //multiply channel
                   image[colorIndex +0] = static_cast<int>(color[0]);
                   image[colorIndex +1] = static_cast<int>(color[1]);
                   image[colorIndex +2] = static_cast<int>(color[2]);
               }
           }

       }
   }
}

同时要注意的是,裁切非常的重要,不仅仅是性能的问题,还有显示的问题。如果你不对z方向进行裁切,会发生非常诡异的结果(明明不在视线内的东西还会画出来,所以其实最开始我是没发现要裁切的,直到我发现了这个诡异的bug)。

3. 相机控制

关于相机的控制,主要的实现流程就是控制相机,得到相机变换后的 位置,看的地方,然后直接算视图矩阵就行了。

这里要稍微注意一下,那就是在计算视图矩阵时:

glm::mat4 MathUtils::calViewMatrix(glm::vec3 cameraPos, glm::vec3 target, glm::vec3 worldUp)

这个 target 实际上是一个位置。一般来说,在创建相机时,我们需要一个变量来保存相机的朝向,例如:

class Camera {
public:
    Camera(float _width, float _height);
    glm::vec3 cameraPos; // camera position
    glm::vec3 target; // camera focus position, target =  cameraPos + cameraFront
    glm::vec3 worldUp; // (0,1,0)
    float fov;
    float aspectRatio;
    float zNear;
    float zFar;
    float width;
    float height;
    float cameraMoveSpeed;
    float cameraTurnSpeed;
    glm::vec3 cameraFront; // camera direction
};

由于相机看的地方肯定是在自己的朝向的前面,所以就可以使用 target = cameraPos + cameraFront 来计算相机看的地方了。

这部分具体的东西就是一些SDL2相关的操作了,贴下代码吧:

if(event.type == SDL_KEYDOWN){
    switch (event.key.keysym.sym) {
        case 'w':
        {
            camera.cameraPos += camera.cameraFront * camera.cameraMoveSpeed;
            break;
        }
        case 'a':
        {
            camera.cameraPos += glm::normalize(glm::cross(camera.cameraFront, camera.worldUp)) * camera.cameraMoveSpeed;
            break;
        }
        case 's':
        {
            camera.cameraPos -= camera.cameraFront * camera.cameraMoveSpeed;
            break;
        }
        case 'd':
        {
            camera.cameraPos -= glm::normalize(glm::cross(camera.cameraFront, camera.worldUp)) * camera.cameraMoveSpeed;
            break;
        }
        case SDLK_UP:
        {
            camera.cameraFront = glm::normalize(camera.cameraFront + camera.worldUp * camera.cameraTurnSpeed);
            break;
        }
        case SDLK_DOWN:
        {
            camera.cameraFront = glm::normalize(camera.cameraFront - camera.worldUp * camera.cameraTurnSpeed);
            break;
        }
        case SDLK_LEFT:
        {
            camera.cameraFront = glm::normalize(camera.cameraFront + glm::cross(camera.cameraFront, camera.worldUp) * camera.cameraTurnSpeed);
            break;
        }
        case SDLK_RIGHT:
        {
            camera.cameraFront = glm::normalize(camera.cameraFront - glm::cross(camera.cameraFront, camera.worldUp) * camera.cameraTurnSpeed);
            break;
        }
    }
    camera.target = camera.cameraPos + camera.cameraFront;
}
if(event.type == SDL_MOUSEBUTTONDOWN){
    mouseClick = true;
    lastMouseX = event.button.x;
    lastMouseY = event.button.y;

}
if(event.type == SDL_MOUSEBUTTONUP){
    mouseClick = false;
}
if(mouseClick){
    if(event.type == SDL_MOUSEMOTION){
        glm::vec3 front  = getCameraFront(camera,event.button.x,event.button.y);
        camera.cameraFront =  glm::normalize(camera.cameraFront + front * camera.cameraTurnSpeed * mouseDragSpeed);
        camera.target = camera.cameraPos + camera.cameraFront;
    }
}

4. 效果展示

最后就可以得到这样的效果:

camera_mvp