渲染一个花瓶 - 通过软光栅实现 金牌收录

1. 简介

参考 OpenGL 的逻辑,实现了一个简单的软光栅渲染演示程序,单文件实现,去掉空格和注释 831 行。

模型和纹理无需外部加载,程序启动时自动生成。

模型由正弦函数旋转得到,颜色贴图使用一个改造过的简化版的噪声函数得到,法线贴图和位移贴图从颜色贴图里面计算。

特性:

  • 可编程的渲染管线(顶点着色器和片元着色器)
  • 环绕相机
  • BlinnPhong 着色
  • Gouraud 着色
  • Flat 着色
  • 颜色贴图
  • 法线贴图
  • 位移贴图
  • 线框模式
  • 背面剔除 

操作

  • 拖动鼠标左键:模型旋转
  • 滚轮:视野大小缩放 
  • 鼠标左键加滚轮:相机前进或后退(视觉效果和缩放视野差不多)
  • 空格键:切换三种光照模型
  • 按 C 键:颜色贴图
  • 按 N 键:法线贴图
  • 按 B 键:位移贴图

2. 效果图

3. 贴图效果

4. 三种光照模型的比对

从左到右依次为 BlinnPhong 着色,Gouraud 着色,Flat 着色。

1.6 万个三角形:BlinnPhong 着色效果最好,但是帧率最低,Flat 着色看起来有点生硬。

9 万个三角形:视觉效果上差别不大,帧率差别不大。

5. 源码实现

/////////////////////////////////////////////////////////////
// Rendering a Vase
// Platform : Visual Studio 2022 (v143), EasyX_20220116
// Author   : 872289455@qq.com
// Date     : 2022-02-21
//
#include <ctime>
#include <cassert>
#include <cfloat>
#include <cmath>
#include <vector>
#include <random>
#include <functional>
#include <algorithm>
#include <easyx.h>
using std::vector;

#define WIDTH                800
#define HEIGHT               600
#define Z_NEAR               (1.f)
#define Z_FAR                (50.f)
#define WORLD_UP             vec3(0.f, 1.f, 0.f)
#define MODE_FILL            0
#define MODE_LINE            1
#define COLOR_STRENGTH       0.85f
#define NORMAL_STRENGTH      3.f
#define DISPACE_STRENGTH     0.23f
#define RADIANS(degrees)     ((degrees) * 0.0174532925f)
#define DEGREES(radians)     ((radians) * 57.295779513f)
#define M_PI                 3.14159265358979323846f

///////////////////////////////////////////////////////////////////////////////////
// Math
///////////////////////////////////////////////////////////////////////////////////
struct vec2 {
    float x, y;
    vec2(float a = 0, float b =0) : x(a), y(b) {}
    vec2 operator*(float f) {
        return vec2(f * x, f * y);
    }
    vec2 operator-(vec2 b) {
        return vec2(x-b.x, y-b.y);
    }
    vec2 operator+(vec2 b) {
        return vec2(x + b.x, y + b.y);
    }
};

struct vec3 {
    float x, y, z;
    vec3(float a = 0, float b = 0, float c = 0) : x(a), y(b), z(c){}
    vec3 operator*(float f) {
        return vec3(f * x, f * y, f * z);
    }
    vec3 operator-() const {
        return vec3(-x, -y, -z);
    }
    vec3 operator-(float f) const {
        return vec3(x - f, y - f, z - f);
    }
    vec3 operator-(vec3 b) const {
        return vec3(x - b.x, y - b.y, z - b.z);
    }
    vec3 operator+(vec3 b) const {
        return vec3(x + b.x, y + b.y, z + b.z);
    }
    float length() {return sqrtf(x*x+y*y+z*z);}
};

struct vec4 {
    float x, y, z, w;
    vec4(float a = 0, float b = 0, float c = 0, float d = 0) : x(a), y(b), z(c), w(d) {}
    vec4(vec3 v, float f = 1.f) : x(v.x), y(v.y), z(v.z), w(f) {}
    vec4 operator*(float f) {
        return vec4(f * x, f * y, f * z, f * w);
    }
    vec3 to_vec3() {
        return vec3(x, y, z);
    }
};

vec2 operator*(float f, vec2 vec) {
    return vec * f;;
}

vec3 operator*(float f, vec3 vec) {
    return vec * f;;
}

vec4 operator*(float f, vec4& vec) {
    return vec * f;;
}

float dot(vec3 a, vec3 b) {
    return a.x * b.x + a.y * b.y + a.z * b.z;
}

vec3 cross(vec3 a, vec3 b) {
    return vec3(a.y * b.z - b.y * a.z,
                a.z * b.x - b.z * a.x,
                a.x * b.y - b.x * a.y
                );
}

vec2 normalize(vec2 v) {
    float len_sq = v.x * v.x + v.y * v.y;
    if (len_sq < FLT_EPSILON)
        return v;
    return (1.f / sqrt(len_sq)) * v;
}

vec3 normalize(vec3 v) {
    float len_sq = v.x * v.x + v.y * v.y + v.z * v.z;
    if (len_sq < FLT_EPSILON)
        return v;
    return (1.f / sqrt(len_sq)) * v;
}

struct quat {
    union {
        struct { float x, y, z, w; };
        struct { vec3 vec; float scalar; };
    };
    quat(float angle, vec3 &axis) {
        float s = 0.f;
        float c = 0.f;
        float rads = RADIANS(angle);
        axis = normalize(axis);
        s = sinf(rads * 0.5f);
        c = cosf(rads * 0.5f);
        x = s * axis.x;
        y = s * axis.y;
        z = s * axis.z;
        w = c;
    }
    vec3 rotate(vec3 from) {
        vec3 a = 2.f * dot(vec, from) * vec;
        vec3 b = (w * w - dot(vec, vec)) * from;
        vec3 c = 2.f * w * cross(vec, from);
        return a + b + c;
    }
};

struct mat3 {
    vec3 col[3];
    mat3(vec3 c0, vec3 c1, vec3 c2) {
        col[0] = c0;
        col[1] = c1;
        col[2] = c2;
    }
    vec3 operator*(vec3 v) {
        return vec3(
            col[0].x * v.x + col[1].x * v.y + col[2].x * v.z,
            col[0].y * v.x + col[1].y * v.y + col[2].y * v.z,
            col[0].z * v.x + col[1].z * v.y + col[2].z * v.z
        );
    }
};

struct mat4 {
    union {
        float v[16];
        struct { vec4 col[4]; };
    };
    mat4(vec4 c0 = vec4(1.f, 0.f, 0.f, 0.f),
         vec4 c1 = vec4(0.f, 1.f, 0.f, 0.f),
         vec4 c2 = vec4(0.f, 0.f, 1.f, 0.f),
         vec4 c3 = vec4(0.f, 0.f, 0.f, 1.f)
        ){
        col[0] = c0;
        col[1] = c1;
        col[2] = c2;
        col[3] = c3;
    }
    vec4& operator[](int i) { return col[i]; }
    mat4  operator*(float f) {
        return mat4(f * col[0], f * col[1], f * col[2], f * col[3]);
    }
    vec4 operator*(vec4 v) {
        return vec4(
            col[0].x * v.x + col[1].x * v.y + col[2].x * v.z + col[3].x * v.w,
            col[0].y * v.x + col[1].y * v.y + col[2].y * v.z + col[3].y * v.w,
            col[0].z * v.x + col[1].z * v.y + col[2].z * v.z + col[3].z * v.w,
            col[0].w * v.x + col[1].w * v.y + col[2].w * v.z + col[3].w * v.w
        );
    }
    mat4 operator*(mat4 &m) {
        return mat4(
            *this * m.col[0],
            *this * m.col[1],
            *this * m.col[2],
            *this * m.col[3]
        );
    }
};

mat4 transposed(mat4 m) {
    return mat4(vec4(m[0].x, m[1].x, m[2].x, m[3].x),
                vec4(m[0].y, m[1].y, m[2].y, m[3].y),
                vec4(m[0].z, m[1].z, m[2].z, m[3].z),
                vec4(m[0].w, m[1].w, m[2].w, m[3].w)
                );
}

#define M4_3X3MINOR(c0, c1, c2, r0, r1, r2) \
    (m.v[c0 * 4 + r0] * (m.v[c1 * 4 + r1] * m.v[c2 * 4 + r2] - m.v[c1 * 4 + r2] * m.v[c2 * 4 + r1]) - \
     m.v[c1 * 4 + r0] * (m.v[c0 * 4 + r1] * m.v[c2 * 4 + r2] - m.v[c0 * 4 + r2] * m.v[c2 * 4 + r1]) + \
     m.v[c2 * 4 + r0] * (m.v[c0 * 4 + r1] * m.v[c1 * 4 + r2] - m.v[c0 * 4 + r2] * m.v[c1 * 4 + r1]))

float determinant(mat4 &m) {
    return  m.v[0] * M4_3X3MINOR(1, 2, 3, 1, 2, 3)
          - m.v[4] * M4_3X3MINOR(0, 2, 3, 1, 2, 3)
          + m.v[8] * M4_3X3MINOR(0, 1, 3, 1, 2, 3)
          - m.v[12]* M4_3X3MINOR(0, 1, 2, 1, 2, 3);
}

mat4 adjugate(mat4 &m) {
    mat4 ret;
    float* cv = &ret.v[0];
    cv[0] = M4_3X3MINOR(1, 2, 3, 1, 2, 3);
    cv[1] =-M4_3X3MINOR(1, 2, 3, 0, 2, 3);
    cv[2] = M4_3X3MINOR(1, 2, 3, 0, 1, 3);
    cv[3] =-M4_3X3MINOR(1, 2, 3, 0, 1, 2);
    cv[4] =-M4_3X3MINOR(0, 2, 3, 1, 2, 3);
    cv[5] = M4_3X3MINOR(0, 2, 3, 0, 2, 3);
    cv[6] =-M4_3X3MINOR(0, 2, 3, 0, 1, 3);
    cv[7] = M4_3X3MINOR(0, 2, 3, 0, 1, 2);
    cv[8] = M4_3X3MINOR(0, 1, 3, 1, 2, 3);
    cv[9] =-M4_3X3MINOR(0, 1, 3, 0, 2, 3);
    cv[10]= M4_3X3MINOR(0, 1, 3, 0, 1, 3);
    cv[11]=-M4_3X3MINOR(0, 1, 3, 0, 1, 2);
    cv[12]=-M4_3X3MINOR(0, 1, 2, 1, 2, 3);
    cv[13]= M4_3X3MINOR(0, 1, 2, 0, 2, 3);
    cv[14]=-M4_3X3MINOR(0, 1, 2, 0, 1, 3);
    cv[15]= M4_3X3MINOR(0, 1, 2, 0, 1, 2);
    return transposed(ret);
}

mat4 inverse(mat4 m) {
    float det = determinant(m);
    if (fabs(det) < FLT_EPSILON)
        return mat4();
    return adjugate(m) * (1.f / det);
}

///////////////////////////////////////////////////////////////////////////////////
// Modeling a vase : x = 2 + sin(y)
///////////////////////////////////////////////////////////////////////////////////
struct Vase {
    vector<vec3> pos;
    vector<vec3> normal;
    vector<vec3> tangent;
    vector<vec2> uv;
    vector<int> index;

    Vase(int y_edges) {
        int x_edges = 2 * y_edges;
        float dx = 2.f * M_PI / x_edges;
        float dy = 2.f * M_PI / y_edges;
        for (int y = 0; y <= y_edges; y++) {
            float v = y * dy;
            float cv = cos(v);
            float sv = sin(v);
            float r = 2 + sv;
            for (int x = 0; x <= x_edges; x++) {
                float u = x * dx;
                float cu = cos(u);
                float su = sin(u);
                vec3 tang(cu * cv, 1, su * cv);
                vec3 biTangent(-su, 0, cu);
                pos.push_back(vec3(r * cu, v, r * su));
                tangent.push_back(tang);
                normal.push_back(normalize(cross(tang, biTangent)));
                uv.push_back(vec2(u / (2 * M_PI), v / (2 * M_PI)));
            }
        }
        for (int y = 0; y < y_edges; y++) {
            int start = y * (x_edges + 1);
            int next_start = (y + 1) * (x_edges + 1);
            for (int x = 0; x < x_edges; x++) {
                int next_x = x + 1;
                index.push_back(start + x);
                index.push_back(next_start + x);
                index.push_back(start + next_x);
                index.push_back(start + next_x);
                index.push_back(next_start + x);
                index.push_back(next_start + next_x);
            }
        }
        pos.push_back(vec3());
        tangent.push_back(vec3(1.f, 0.f, 0.f));
        normal.push_back(vec3(0.f, -1.f, 0.f));
        uv.push_back(vec2(0.5, 0.5));
        for (int x = 0; x < x_edges; x++) {
            index.push_back((int)pos.size() - 1);
            index.push_back(x);
            index.push_back(x + 1);
            normal[x] = vec3(0.f, -1.f, 0.f);
        }
        normal[x_edges] = vec3(0.f, -1.f, 0.f);
    }
};

///////////////////////////////////////////////////////////////////////////////////
// Procedural Texturing
///////////////////////////////////////////////////////////////////////////////////
class Noise {
    const uint32_t TABLE_SIZE = 0X100U;
    const uint32_t TABLE_MASK = 0XFFU;
    vector<vector<vec2>> grid;
public:
    Noise() {
        std::mt19937 gen(time(nullptr));
        std::uniform_real_distribution<float> distrFloat(-1.f, 1.f);
        auto randFloat = std::bind(distrFloat, gen);
        grid = vector<vector<vec2>> (TABLE_SIZE, vector<vec2>(TABLE_SIZE));
        for (uint32_t row = 0; row < TABLE_SIZE; row++)
            for (uint32_t col = 0; col < TABLE_SIZE; col++) 
                grid[row][col] = normalize(vec2(randFloat(), randFloat()));
    }

    vec3 eval(vec2 p) {
        uint32_t x = (uint32_t)std::floor(p.x);
        uint32_t y = (uint32_t)std::floor(p.y);
        float u = fade(p.x - x);
        float v = fade(p.y - y);
        uint32_t x0 = x & TABLE_MASK;
        uint32_t x1 = (x0 + 1) & TABLE_MASK;
        uint32_t y0 = y & TABLE_MASK;
        uint32_t y1 = (y0 + 1) & TABLE_MASK;
        vec2 vec = lerp(lerp(grid[x0][y0], grid[x1][y0], u), lerp(grid[x0][y1], grid[x1][y1], u), v);
        return vec3(vec.x, vec.y, COLOR_STRENGTH);
    }
private:
    vec2 lerp(vec2 lo, vec2 hi, float t) {
        return lo * (1 - t) + hi * t;
    }
    float fade(float t) {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }
};

struct Image {
    int height;
    int width;
    int channels;
    vector<uint8_t> data;
    Image(int h = 0, int w = 0, int c = 0, vector<uint8_t> raw = vector<uint8_t>()) : height(h), width(w), channels(c), data(raw) {}
};

vec3 getPixelAt(const Image& map, int x, int y) {
    if (x < 0) x = map.width - 1;
    if (x >= map.width) x = 0;
    if (y < 0) y = map.height - 1;;
    if (y >= map.height) y = 0;

    float r, g, b;
    int ofs = map.channels * (y * map.width + x);
    r = g = b = map.channels > 0 ? map.data[ofs + 0] : 0.f;
    g = b = map.channels > 1 ? map.data[ofs + 1] : r;
    b = map.channels > 2 ? map.data[ofs + 2] : g;
    return (1.f / 255.f) * vec3(r, g, b);
}

// vec3 returned in range 0.0 - 1.0
vec3 texture(const Image& map, const vec2& texCoord) {
    vec2 uv = texCoord;
    int x = int(map.width * uv.x);
    int y = int(map.height * uv.y);
    float u = map.width * uv.x - x;
    float v = map.height * uv.y - y;

    vec3 c00 = getPixelAt(map, x, y);
    vec3 c10 = getPixelAt(map, x + 1, y);
    vec3 c01 = getPixelAt(map, x, y + 1);
    vec3 c11 = getPixelAt(map, x + 1, y + 1);
    return (1 - u) * (1 - v) * c00 + u * (1 - v) * c10 + (1 - u) * v * c01 + u * v * c11;
}

Image buildColorTexture(void) {
    Noise noise;
    float baseFreq = 12;
    int level = 6;
    float amplitudeMult = 0.5f;
    float frequencyMult = 1.9f;
    int width = 1024;
    int height = 1024;

    vector<uint8_t> pixels;
    pixels.resize(width * height * 3);

    float dx = 1.0f / (width - 1);
    float dy = 1.0f / (height - 1);
    for (int row = 0; row < height; row++) {
        for (int col = 0; col < width / 2; col++) {
            float x = dx * col;
            float y = dy * row;
            float freq = baseFreq;
            float amplitude = 1.f;
            vec3 sumVec;
            for (int oct = 0; oct < level; oct++) {
                sumVec = sumVec + amplitude * noise.eval(freq * vec2(x, y));
                freq *= frequencyMult;
                amplitude *= amplitudeMult;
            }
            sumVec = normalize(sumVec);
            float turb = sin(sumVec.y);
            int lofs = 3 * (row * width + col);
            int rofs = 3 * (row * width + width - 1 - col);
            pixels[lofs + 0] = pixels[rofs + 0] = static_cast<uint8_t>((turb * 1.0 + 1.0) * 255.f * 0.5);
            pixels[lofs + 1] = pixels[rofs + 1] = static_cast<uint8_t>((turb * 0.7 + 1.0) * 255.f * 0.5);
            pixels[lofs + 2] = pixels[rofs + 2] = static_cast<uint8_t>((turb * 0.8 + 1.0) * 255.f * 0.5);
        }
    }
    return Image(width, height, 3, pixels);
}

Image buildNormalTextureFrom(const Image& src) {
    vector<uint8_t> pixels;
    pixels.reserve(3 * src.width * src.height);
    for (int y = 0; y < src.height; y++) {
        for (int x = 0; x < src.width; x++) {
            float tl = getPixelAt(src, x - 1, y - 1).length();
            float t = getPixelAt(src, x - 1, y).length();
            float tr = getPixelAt(src, x - 1, y + 1).length();
            float r = getPixelAt(src, x, y + 1).length();
            float br = getPixelAt(src, x + 1, y + 1).length();
            float b = getPixelAt(src, x + 1, y).length();
            float bl = getPixelAt(src, x + 1, y - 1).length();
            float l = getPixelAt(src, x, y - 1).length();
            float dx = float(tr + 2.f * r + br) - float(tl + 2.f * l + bl);
            float dy = float(bl + 2.f * b + br) - float(tl + 2.f * t + tr);
            float dz = 1.f / NORMAL_STRENGTH;
            vec3 n = normalize(vec3(dx, dy, dz));
            pixels.push_back(static_cast<uint8_t>((n.x + 1.0) * 255.f * 0.5));
            pixels.push_back(static_cast<uint8_t>((n.y + 1.0) * 255.f * 0.5));
            pixels.push_back(static_cast<uint8_t>((n.z + 1.0) * 255.f * 0.5));
        }
    }
    return Image(src.width, src.height, 3, pixels);
}


///////////////////////////////////////////////////////////////////////////////////
// Orbit Camera
///////////////////////////////////////////////////////////////////////////////////
uint8_t activeColorTexture = 1u;
uint8_t activeNormalTexture = 0u;
uint8_t activeDispTexture = 0u;
uint8_t activeLineMode = 0u;
uint8_t cullFace = 0u;
uint8_t selectShader = 0u;

class Camera {
    vec3  pos, target;
    float zoom, aspect;
    mat4  projection, view;
public:
    Camera(vec3 position, vec3 center, float asp) : pos(position), target(center), zoom(25.f), aspect(asp) {
        updateProjection();
        updateView();
    }
    void processInput() {
        ExMessage msg;
        static bool firstClick = true;
        static int lastx, lasty;
        while (peekmessage(&msg, EM_MOUSE | EM_KEY)) {
            // Drag Left Button: Rotate
            if (WM_MOUSEMOVE == msg.message) {
                if (false == msg.lbutton) {
                    firstClick = true;
                }
                else if (firstClick) {
                    firstClick = false;
                    lastx = msg.x;
                    lasty = msg.y;
                }
                else {
                    float xoff = 0.5f * (msg.x - lastx);
                    float yoff = 0.5f * (msg.y - lasty);
                    lastx = msg.x;
                    lasty = msg.y;
                    mouseMove(xoff, yoff);
                }
            }
            else if (WM_MOUSEWHEEL == msg.message) {
                if (msg.lbutton)    // Left Button + Scroll: Camera Forward or Backward, have a bug
                    srcollPosition(-0.01f * msg.wheel);
                else                // Scroll: field of view
                    scrollZoom(0.01f * msg.wheel);
            }
            else if (WM_KEYDOWN == msg.message) {
                switch (msg.vkcode) {
                case VK_SPACE: selectShader = (selectShader + 1) % 3;    break;
                case 'C':      activeColorTexture ^= 1u;                 break;
                case 'N':      activeNormalTexture ^= 1u;                break;
                case 'B':      activeDispTexture ^= 1u;                  break;
                case 'L':      activeLineMode ^= 1u;                     break;
                case 'K':      cullFace ^= 1u;                           break;
                default:                                                 break;
                }
            }
        }
    }
    mat4& viewMatrix()        { return view; }
    mat4& projectionMatrix()  { return projection;}
    vec3& position()          { return pos; }
    float fov()               { return zoom; }
private:
    void updateProjection() {
        float t = Z_NEAR * tan(RADIANS(0.5f * zoom));
        float b = -t;
        float r = t * aspect;
        float l = -r;
        projection[0] = vec4(2 * Z_NEAR / (r - l), 0.f, 0.f, 0.f);
        projection[1] = vec4(0.f, 2 * Z_NEAR / (t - b), 0.f, 0.f);
        projection[2] = vec4((r + l) / (r - l), (t + b) / (t - b), -(Z_FAR - Z_NEAR) / (Z_FAR - Z_NEAR), -1.f);
        projection[3] = vec4(0.f, 0.f, -2 * Z_FAR * Z_NEAR / (Z_FAR - Z_NEAR), 0.f);
    }
    void updateView() {
        vec3 z_axis = normalize(pos - target);
        vec3 x_axis = normalize(cross(WORLD_UP, z_axis));
        vec3 y_axis = normalize(cross(z_axis, x_axis));

        view[0] = vec4(x_axis.x, y_axis.x, z_axis.x, 0.f);
        view[1] = vec4(x_axis.y, y_axis.y, z_axis.y, 0.f);
        view[2] = vec4(x_axis.z, y_axis.z, z_axis.z, 0.f);
        view[3] = vec4(-dot(x_axis, pos), -dot(y_axis, pos), -dot(z_axis, pos), 1.f);
    }

    void mouseMove(float xoff, float yoff) {
        vec3 front = normalize(target - pos);
        vec3 right = normalize(cross(front, WORLD_UP));
        vec3 up = normalize(cross(right, front));
        float angle = DEGREES(acos(dot(front, WORLD_UP)));
        if ((yoff < 0 && (angle + yoff < 20)) || (yoff > 0 && (angle + yoff > 160)))
            return;

        quat qx(-xoff, up);
        pos = qx.rotate(pos - target) + target;
        front = normalize(target - pos);
        right = normalize(cross(front, up));

        quat qy(-yoff, right);
        pos = qy.rotate(pos - target) + target;
        updateView();
    }

    void srcollPosition(float yoff) {
        vec3 front = normalize(target - pos);
        pos = pos + yoff * front;
        updateView();
    }
    
    void scrollZoom(float yoff) {
        if (zoom + yoff > 120 || zoom + yoff < 10)
            return;
        zoom += yoff;
        updateProjection();
    }
};

///////////////////////////////////////////////////////////////////////////////////
// Rendering
///////////////////////////////////////////////////////////////////////////////////
struct Vertex {
    vec3 pos, normal, tangent, color;
    vec2 uv;
};

typedef void (*VERTEX_SHADER)(const Vertex& in, Vertex& out, vec4& mvpPos);
typedef vec3(*FRAGMENT_SHADER)(const Vertex& in);

struct Program {
    bool              flatHint;
    VERTEX_SHADER     vertexShader;
    FRAGMENT_SHADER   fragmentShader;
    Program(VERTEX_SHADER vert = nullptr, FRAGMENT_SHADER frag = nullptr, bool isFlat = false) : vertexShader(vert), fragmentShader(frag), flatHint(isFlat) {}
};

struct Light {
    vec3  dir;      // Light direction in eye coords.
    float La;       // Ambient light intensity
    float Ld;       // Diffuse light intensity
    float Ls;       // Specular light intensity
    Light(vec3 d = vec3(0.f, 1.f, 1.f), float la = 0.03f, float ld = 0.2f, float ls = 0.8f) : dir(d), La(la), Ld(ld), Ls(ls) {}
};

// Sharing of uniform data between shader programs.
struct perFrameData {
    mat4 modelViewMatrix;
    mat4 projectionMatrix;
    mat4 normalMatrix;
    mat4 viewportMatrix;
    mat4 MVP;

    Image colorMap;
    Image normalMap;
    Image dispMap;

    Light light[4] = {
        Light(vec3( 1.0f,  1.f, 1.f)), 
        Light(vec3(-2.0f, -2.f, 1.f)), 
        Light(vec3(-0.5f,  0.f, 1.f)), 
        Light(vec3(-1.0f,  0.f, 0.f)) 
    };
} uniform;

void setMatrixUniform(mat4 &v, mat4 &proj) {
    float w2 = WIDTH / 2.f;
    float h2 = HEIGHT / 2.f;
    uniform.modelViewMatrix = v;
    uniform.projectionMatrix = proj;
    uniform.normalMatrix = transposed(inverse(v));
    uniform.viewportMatrix = mat4(vec4(w2, 0.f, 0.f, 0.f), vec4(0.f, h2, 0.f, 0.f), vec4(0.f, 0.f, 1.f, 0.f), vec4(w2 - 0.5f, h2 - 0.5f, 0.f, 1.f));
    uniform.MVP = proj * v;
}

class Render {
    int             vertices;
    DWORD*          framebuf;
    VERTEX_SHADER   vertexShader;
    FRAGMENT_SHADER fragmentShader;
    vector<Vertex>  VertexIn;
    vector<Vertex>  VertexOut;
    vector<vec4>    mvpPos;
    vector<int>     index;
    vector<float>   zbuf;
    int             drawMode;
    bool            flatHint;

public:
    Render() : vertices(0), vertexShader(nullptr), fragmentShader(nullptr), framebuf(GetImageBuffer()), drawMode(MODE_FILL), flatHint(false) { assert(framebuf); }

    void createBuffersWith(Vase *vase) {
        assert(vase);
        index = vase->index;
        vertices = (int)vase->pos.size();
        mvpPos.resize(vertices);
        VertexIn.resize(vertices);
        VertexOut.resize(vertices);
        zbuf.resize(WIDTH * HEIGHT);
        for (int i = 0; i < vertices; i++) {
            VertexIn[i].pos = vase->pos[i];
            VertexIn[i].normal = vase->normal[i];
            VertexIn[i].tangent = vase->tangent[i];
            VertexIn[i].uv = vase->uv[i];
        }
    }

    void useProgram(Program& shader) {
        flatHint = shader.flatHint;
        vertexShader = shader.vertexShader;
        fragmentShader = shader.fragmentShader;
    }

    void drawElements(void) {
        std::fill_n(zbuf.begin(), zbuf.size(), FLT_MAX);
        #pragma omp parallel for
        // 1. Vertex Shader
        for (int i = 0; i < vertices; i++)
            vertexShader(VertexIn[i], VertexOut[i], mvpPos[i]);
        
        #pragma omp parallel for
        for (int i = 0; i < (int)index.size(); i += 3) {
            // 2. Shape Assembly
            Vertex vert[3] = { VertexOut[index[i]], VertexOut[index[i + 1]], VertexOut[index[i + 2]] };
            vec4 v[3] = {mvpPos[index[i]], mvpPos[index[i + 1]], mvpPos[index[i + 2]] };
            // 3. Rasterization
            for (auto& p : v) {    
                p = uniform.viewportMatrix * p;
                p = p * (1.f / p.w);
            }
            
            if (cullFace && cross(v[1].to_vec3() - v[0].to_vec3(), v[2].to_vec3() - v[0].to_vec3()).z <= 0) 
                continue;
            
            if (activeLineMode) {
                drawLine(v[0], v[1]);
                drawLine(v[0], v[2]);
                drawLine(v[1], v[2]);
                continue;
            }
            
            int left = (int)min(v[0].x, min(v[1].x, v[2].x)) - 1;
            int right = (int)max(v[0].x, max(v[1].x, v[2].x)) + 1;
            int bottom = (int)min(v[0].y, min(v[1].y, v[2].y)) - 1;
            int top = (int)max(v[0].y, max(v[1].y, v[2].y)) + 1;

            for (int x = left; x <= right; x++) {
                if (x >= WIDTH ||  x <= 0) 
                    continue;
                for (int y = bottom; y <= top; y++) {
                    if (y >= HEIGHT || y <= 0)
                        continue;
                    vec3 centric = barycentric(x, y, v);
                    if (insideTriangle(centric)) {
                        if (flatHint) {
                            float z = v[1].z;
                            if (z < zbuf[indexOf(x, y)]) {
                                zbuf[indexOf(x, y)] = z;
                                // 4. Fragment Shader, flat shading use the second point of triangle
                                setpixel(x, y, fragmentShader(vert[1]));
                            }
                        }
                        else {
                            float a = centric.x, b = centric.y, c = centric.z;
                            float z = a * v[0].z + b * v[1].z + c * v[2].z;
                            if (z < zbuf[indexOf(x, y)]) {
                                Vertex fragIn;
                                zbuf[indexOf(x, y)] = z;
                                fragIn.pos = a * vert[0].pos + b * vert[1].pos + c * vert[2].pos;
                                fragIn.normal = a * vert[0].normal + b * vert[1].normal + c * vert[2].normal;
                                fragIn.tangent = a * vert[0].tangent + b * vert[1].tangent + c * vert[2].tangent;
                                fragIn.color = a * vert[0].color + b * vert[1].color + c * vert[2].color;
                                fragIn.uv = a * vert[0].uv + b * vert[1].uv + c * vert[2].uv;
                                // 4. Fragment Shader
                                setpixel(x, y, fragmentShader(fragIn));
                            }
                        }

                    }
                }
            }

        }
    }
    void bindTexture(const Image &colorMap, const Image &normalMap, const Image & dispMap) {
        uniform.colorMap = colorMap;
        uniform.normalMap= normalMap;
        uniform.dispMap  = dispMap;
    }
    void polygonMode(int drawhint) {
        drawMode = drawhint;
    }

private:
    int indexOf(int x, int y) {
        return (HEIGHT - y) * WIDTH + x;
    }

    void setpixel(int x, int y, vec3 fragColor) {
        uint8_t r = (uint8_t)max(0.f, min(255.f, 255.f * fragColor.x));
        uint8_t g = (uint8_t)max(0.f, min(255.f, 255.f * fragColor.y));
        uint8_t b = (uint8_t)max(0.f, min(255.f, 255.f * fragColor.z));
        framebuf[indexOf(x, y)] = 0Xffu << 24 | r << 16 | g << 8 | b; // BGRA
    }

    bool insideTriangle(vec3& centric) {
        return  centric.x >= 0.f && centric.x <= 1.f && \
                centric.y >= 0.f && centric.y <= 1.f && \
                centric.z >= 0.f && centric.z <= 1.f;
    }
     
    vec3 barycentric(int x, int y, const vec4* _v) {
        vec2 p(static_cast<float>(x), static_cast<float>(y));
        vec2 a(_v[0].x, _v[0].y), b(_v[1].x, _v[1].y), c(_v[2].x, _v[2].y);
        vec2 v0 = b - a, v1 = c - a, v2 = p - a;
        float div = 1.f / (v0.x * v1.y - v1.x * v0.y);
        float v = div * (v2.x * v1.y - v1.x * v2.y);
        float w = div * (v0.x * v2.y - v2.x * v0.y);
        float u = 1.f - v - w;
        return vec3(u,v,w);
    }

    void drawLine(vec4 begin, vec4 end) {
        vec3 lineColor(0.45F, 0.73F, 0.19F);
        int x0 = int(begin.x);
        int y0 = int(begin.y);
        int x1 = int(end.x);
        int y1 = int(end.y);
        if (x0 < 0 || x0 >= WIDTH || y0 < 0 || y0 >= HEIGHT || x1 < 0 || x1 >= WIDTH || y1 < 0 || y1 >= HEIGHT)
            return;
        int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
        int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
        int err = (dx > dy ? dx : -dy) / 2;
        while (setpixel(x0, y0, lineColor), x0 != x1 || y0 != y1) {
            int e2 = err;
            if (e2 > -dx) { err -= dy; x0 += sx; }
            if (e2 < dy) { err += dx; y0 += sy; }
        }
    }
};


///////////////////////////////////////////////////////////////////////////////////
// Shaders
///////////////////////////////////////////////////////////////////////////////////
// Common functions for all shaders
vec3 updatePosition(const vec3& old, const vec3& normal, const vec2& uv) {
    vec3 out = old;
    vec3 h = texture(uniform.dispMap, uv);
    float hs = (h.x + h.y + h.z) / 3.f;
    float t = 2 * hs - 1;
    return old - DISPACE_STRENGTH * t * normal;
}

vec3 updateNormal(vec3 Normal, vec3 Tangent, vec2 uv) {
    vec3 N = normalize(Normal);
    vec3 T = normalize(Tangent - N * dot(Tangent, N));
    vec3 B = normalize(cross(N, T)); 
    mat3 TBN(T, B, N);
    vec3 normal = 2.0 * texture(uniform.normalMap, uv) - 1.f;
    return normalize(TBN * normal);
}

vec3 blinnPhongColor(const vec3 &pos, const vec3 &n, const vec2 &uv) {
    vec3 color(0.3f, 0.3f, 0.3f);
    if (activeColorTexture)
        color = texture(uniform.colorMap, uv);
    float la = 0.f;
    vec3 ambient, diffuse, specular;
    for (auto &l : uniform.light) {
        ambient = ambient + l.La * color;
        vec3 s = l.dir;
        float sDotN = max(dot(s, n), 0.f);
        diffuse = diffuse + l.Ld * sDotN * color;
        if (sDotN > 0.0) {
            vec3 v = normalize(-pos);
            vec3 h = normalize(v + s);
            specular = specular + l.Ls * powf(max(dot(h, n), 0), 128) * vec3(0.8f, 0.8f, 0.8f);
        }
    }
    return ambient + diffuse + specular;
}

///////////////////////////////////////////////////////////////////////////////////
// 1. Blinn-Phong Shading: shade each pixel
///////////////////////////////////////////////////////////////////////////////////
void phongVertShader(const Vertex& in, Vertex& out, vec4& mvpPos) {
    vec3 pos = in.pos;
    if (activeDispTexture)
        pos = updatePosition(in.pos, in.normal, in.uv);
    out.pos = (uniform.modelViewMatrix * vec4(pos)).to_vec3();
    out.normal = normalize((uniform.normalMatrix * vec4(in.normal, 0.f)).to_vec3());
    out.tangent = normalize((uniform.normalMatrix * vec4(in.tangent, 0.f)).to_vec3());
    out.uv = in.uv;
    mvpPos = uniform.MVP * vec4(pos);
}

vec3 phongFragShader(const Vertex& in) {
    vec3 N = normalize(in.normal);
    if (activeNormalTexture)
        N = updateNormal(N, in.tangent, in.uv);
    // two-sided shading
    vec3 view = normalize(-in.pos);
    float vDotN = dot(view, N);
    if (vDotN <= 0)
       N = -N;
    return blinnPhongColor(in.pos, N, in.uv);
}


///////////////////////////////////////////////////////////////////////////////////
// 2. Gouraud Shading: shade each vertext
///////////////////////////////////////////////////////////////////////////////////
void gouraudVertShader(const Vertex& in, Vertex& out, vec4& mvpPos) {
    vec3 pos = in.pos;
    if (activeDispTexture)
        pos = updatePosition(in.pos, in.normal, in.uv);
    pos = (uniform.modelViewMatrix * vec4(pos)).to_vec3();
    vec3 N = normalize((uniform.normalMatrix * vec4(in.normal, 0.f)).to_vec3());
    vec3 T = normalize((uniform.normalMatrix * vec4(in.tangent, 0.f)).to_vec3());
    if (activeNormalTexture)
        N = updateNormal(N, T, in.uv);
    vec3 view = normalize(-pos);
    float vDotN = dot(view, N);
    if (vDotN < 0)
       N = -N;
    out.color = blinnPhongColor(pos, N, in.uv);
    mvpPos = uniform.projectionMatrix * vec4(pos);
}

vec3 gouraudFragShader(const Vertex& in) {
    return in.color;
}

///////////////////////////////////////////////////////////////////////////////////
// 3. Flat Shading: shade each triangle
///////////////////////////////////////////////////////////////////////////////////
void flatVertShader(const Vertex& in, Vertex& out, vec4& mvpPos) {
    return gouraudVertShader(in, out, mvpPos);
}
vec3 flatFragShader(const Vertex& in) {
    return gouraudFragShader(in);
}

int main() {
    TCHAR  buf[128];
    size_t vertices, triangles;
    double deltaTime = 0.f;
    double lastTime = 0.f;
    double currentTime = 0.f;
    uint64_t start, now, frequency;

    initgraph(WIDTH, HEIGHT);

    Render render;
    Camera camera(vec3(0.f, 9.f, 19.f), vec3(0.f, M_PI, 0.f), (float)WIDTH / (float)HEIGHT);

    Vase *vase = new Vase(64);
    render.createBuffersWith(vase);
    vertices = vase->pos.size();
    triangles = vase->index.size() / 3;
    delete vase;

    Program blinnPhong(phongVertShader, phongFragShader);
    Program gouraud(gouraudVertShader, gouraudFragShader);
    Program flat(flatVertShader, flatFragShader, true);
    Program shaders[3] = {blinnPhong, gouraud, flat};
    const TCHAR  *shaderName[3] = {_T("blinnPhong"), _T("gouraud"), _T("flat")};

    Image colorMap = buildColorTexture();
    Image normalMap = buildNormalTextureFrom(colorMap);
    render.bindTexture(colorMap, normalMap, colorMap);

    QueryPerformanceCounter((LARGE_INTEGER*)&start);
    QueryPerformanceFrequency((LARGE_INTEGER*)&frequency);
    BeginBatchDraw();
    while (1) {
        QueryPerformanceCounter((LARGE_INTEGER*)&now);
        currentTime = (double)(now - start) / frequency;
        deltaTime = currentTime - lastTime;
        lastTime = currentTime;

        camera.processInput();
        setMatrixUniform(camera.viewMatrix(), camera.projectionMatrix());

        if (activeLineMode) render.polygonMode(MODE_LINE);
        else                render.polygonMode(MODE_FILL);

        cleardevice();
        render.useProgram(shaders[selectShader]);
        render.drawElements();

        outtextxy(WIDTH / 2, 10, shaderName[selectShader]);
        swprintf_s(buf, _T("  vertices   : %zu"), vertices);
        outtextxy(0, 0, buf);
        swprintf_s(buf, _T("  triangles  : %zu"), triangles);
        outtextxy(0, 16, buf);
        outtextxy(0, 32, L"  Scroll       : fov");
        outtextxy(0, 48, L"  Drag LButton    : Rotate");
        outtextxy(0, 64, L"  LButton + Scroll: Forward / Backward");
        outtextxy(0, 80, L"  Press [C]: Color texture");
        outtextxy(0, 96, L"  Press [N]: Normal texture");
        outtextxy(0, 112, L"  Press [B]: Bump texture");
        outtextxy(0, 128, L"  Press [L]: Line Mode");
        outtextxy(0, 144, L"  Press [K]: Cull Face");
        outtextxy(0, 160, L"  Press [Space] : Shaders");
        swprintf_s(buf, _T("  Camera Position: (%.3f, %.3f, %.3f)"), camera.position().x, camera.position().y, camera.position().z);
        outtextxy(0, 176, buf);
        swprintf_s(buf, _T("  Camera Fov: %.3f"), camera.fov());
        outtextxy(0, 192, buf);
        swprintf_s(buf, _T("fps  : %d"), (int)(1.f / deltaTime));
        outtextxy(WIDTH - 96, 0, buf);
        swprintf_s(buf, _T("time: %.1fms"), 1000 * deltaTime);
        outtextxy(WIDTH - 96, 16, buf);
        FlushBatchDraw();
    }
    return 0;
}

评论 (3) -

  • 我这里有个编译好的二进制版本:
    链接:https://pan.baidu.com/s/1mfPstWuUWFiIG8urFWezdg
    提取码:ghek
  • 为什么鼠标拖动以后一秒钟才画出来,这是正常速度还是说可以改快一点。
    • 你好,可以尝试以下前两步,之后帧率应该能至少提高到 8 帧(Blin-phong) / 30 帧(Gouraud 和 Flat).(这是我在一款老式奔腾处理器上测试的结果,电脑不太老的话应该能流畅到25fps)。
      第一,使用Release编译。
      第二,开启下OpenMP:右键项目属性-->C/C++-->所有选项-->Openmp支持改为 是。之后CPU会全部拉满,帧率会有很可观的提升。
      第三,可以试试其他优化,比如采样改为最近邻、函数尽量修改成引用传递、注释掉 outtextxy 部分,这些优化极为有限。
      第四,Vase *vase = new Vase(64); 这句代码可以试试传入 32 或 16,以减少三角形数量,不过这样模型可能就会有棱角,位移贴图也会不太美观。
      最主要的是我这程序很粗糙,有的地方算法用的很蛮力。目前只保证了程序能跑起来,而且还有几处BUG遗留,,,
      有时间我再优化,,,

添加评论