Просмотр исходного кода

feat: 添加 重心 和 透视矫正 的一些计算解释

NiceTry12138 4 месяцев назад
Родитель
Сommit
f1569cd004

BIN
图形学/读TinyRenderer/Image/001.jpg


BIN
图形学/读TinyRenderer/Image/002.jpg


BIN
图形学/读TinyRenderer/Image/003.jpg


BIN
图形学/读TinyRenderer/Image/004.jpg


BIN
图形学/读TinyRenderer/Image/005.jpg


+ 95 - 0
图形学/读TinyRenderer/README.md

@@ -0,0 +1,95 @@
+# TinyRenderer
+
+[参考项目 tinyrenderer](https://github.com/ssloy/tinyrenderer)
+
+## 光栅化
+
+### 求重心
+
+重心坐标 (α, β, γ) 表示点 P 可以表示为三角形三个顶点A、B、C的加权和:P = αA + βB + γC,其中 α + β + γ = 1
+
+如果点P在三角形内部,则满足:0 ≤ α, β, γ ≤ 1
+
+通过重心坐标,即可知道某个点是否在三角形内部
+
+```
+α·A_x + β·B_x + γ·C_x = P_x
+α·A_y + β·B_y + γ·C_y = P_y
+α + β + γ = 1
+```
+
+对应的矩阵表现形式是 
+
+```
+| A_x  B_x  C_x |   | α |   | P_x |
+| A_y  B_y  C_y | × | β | = | P_y |
+|  1    1    1  |   | γ |   |  1  |
+```
+
+设矩阵 M 为
+
+```
+    | A_x  B_x  C_x |
+M = | A_y  B_y  C_y |
+    |  1    1    1  |
+```
+
+需要求解的是向量 [α, β, γ]ᵀ ,使得: M × [α, β, γ]ᵀ = [P_x, P_y, 1]ᵀ
+
+解为:[α, β, γ]ᵀ = M⁻¹ × [P_x, P_y, 1]ᵀ
+
+```cpp
+vec3 barycentric(const vec2 tri[3], const vec2 P) {
+    mat<3,3> ABC = {{ {tri[0].x, tri[0].y, 1.}, {tri[1].x, tri[1].y, 1.}, {tri[2].x, tri[2].y, 1.} }};
+    if (ABC.det()<1) return {-1,1,1}; 
+    return ABC.invert_transpose() * vec3{P.x, P.y, 1.};
+}
+```
+
+### 透视矫正
+
+| 参考文章 | 链接 |
+| --- | --- |
+| 透视矫正插值 | https://zhuanlan.zhihu.com/p/403259571 |
+| Perspective Correct Interpolation | https://www.cs.ucr.edu/~craigs/courses/2018-fall-cs-130/lectures/perspective-correct-interpolation.pdf |
+
+| 贴图 | 渲染效果 |
+| --- | --- |
+| ![](Image/001.jpg) | ![](Image/002.jpg) |
+
+示意图
+
+![](Image/003.jpg)
+
+![](Image/004.jpg)
+
+由于透视的问题,明明 Q 点是 AC 中点;但是由于透视的存在,看起来 P 点才像是 AC 中点
+
+在坐标点通过 MVP 矩阵变化到屏幕空间中,会将 P 点的 UV 坐标设置为 `(1/2, 1/2)`,最终导致显示问题
+
+可以通过 **顶点的世界坐标** 和 **目标点的屏幕坐标**,计算得到目标点对应平面上的 **世界坐标**
+
+首先考虑 **二维** 的情况,由简入繁
+
+![](Image/005.jpg)
+
+通过相似计算
+
+$$
+\frac{n}{1-n} = \frac{|AG|}{|BK|} = \frac{|A'P'| * \frac{Z_1}{c}}{|B'P'|\frac{Z_2}{c}} = \frac{m*Z_1}{(1-m)*Z_2} 
+$$
+
+对左右两边取倒
+
+$$
+\frac{1}{n} - 1 = \frac{(1-m)*Z_2}{m*Z_1} \\
+n = \frac{m*Z_1}{(1-m)*Z_2 + m*Z_1}
+$$
+
+由于 A'、B'、P' 都是已知的屏幕坐标点,所以可以很轻松的计算得到 m 的值,进而计算得到 n 的值
+
+P 的坐标可以通过 P = (1-n) * A + n * B 得到,进而得到 P 的坐标计算公式
+
+$$
+
+$$

+ 29 - 0
图形学/读TinyRenderer/src/CMakeLists.txt

@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 3.12...3.26)
+
+get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(NOT is_multi_config AND NOT (CMAKE_BUILD_TYPE OR DEFINED ENV{CMAKE_BUILD_TYPE}))
+  set(CMAKE_BUILD_TYPE Release CACHE STRING "Release default")
+endif()
+
+project(tinyrenderer LANGUAGES CXX)
+
+set(CMAKE_CXX_STANDARD 20)
+
+option(iwyu "Run include-what-you-use")
+if(iwyu)
+  find_program(IWYU_EXE NAMES include-what-you-use REQUIRED)
+  set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_EXE})
+endif()
+
+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU|Intel")
+  add_compile_options(-Wall)
+endif()
+
+find_package(OpenMP COMPONENTS CXX)
+
+set(SOURCES main.cpp model.cpp our_gl.cpp tgaimage.cpp)
+
+add_executable(${PROJECT_NAME} ${SOURCES})
+target_link_libraries(${PROJECT_NAME} PRIVATE $<$<BOOL:${OpenMP_CXX_FOUND}>:OpenMP::OpenMP_CXX>)
+
+file(GENERATE OUTPUT .gitignore CONTENT "*")

+ 6 - 0
图形学/读TinyRenderer/src/Dockerfile

@@ -0,0 +1,6 @@
+FROM gitpod/workspace-full
+
+USER root
+# add your tools here
+RUN apt-get update && apt-get install -y \
+  imagemagick

+ 13 - 0
图形学/读TinyRenderer/src/LICENSE.txt

@@ -0,0 +1,13 @@
+Tiny Renderer, https://github.com/ssloy/tinyrenderer
+Copyright Dmitry V. Sokolov
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it freely,
+subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+

+ 187 - 0
图形学/读TinyRenderer/src/geometry.h

@@ -0,0 +1,187 @@
+#pragma once
+#include <cmath>
+#include <cassert>
+#include <iostream>
+
+template<int n> struct vec {
+    double data[n] = {0};
+    double& operator[](const int i)       { assert(i>=0 && i<n); return data[i]; }
+    double  operator[](const int i) const { assert(i>=0 && i<n); return data[i]; }
+};
+
+template<int n> double operator*(const vec<n>& lhs, const vec<n>& rhs) {
+    double ret = 0;                         // N.B. Do not ever, ever use such for loops! They are highly confusing.
+    for (int i=n; i--; ret+=lhs[i]*rhs[i]); // Here I used them as a tribute to old-school game programmers fighting for every CPU cycle.
+    return ret;                             // Once upon a time reverse loops were faster than the normal ones, it is not the case anymore.
+}
+
+template<int n> vec<n> operator+(const vec<n>& lhs, const vec<n>& rhs) {
+    vec<n> ret = lhs;
+    for (int i=n; i--; ret[i]+=rhs[i]);
+    return ret;
+}
+
+template<int n> vec<n> operator-(const vec<n>& lhs, const vec<n>& rhs) {
+    vec<n> ret = lhs;
+    for (int i=n; i--; ret[i]-=rhs[i]);
+    return ret;
+}
+
+template<int n> vec<n> operator*(const vec<n>& lhs, const double& rhs) {
+    vec<n> ret = lhs;
+    for (int i=n; i--; ret[i]*=rhs);
+    return ret;
+}
+
+template<int n> vec<n> operator*(const double& lhs, const vec<n> &rhs) {
+    return rhs * lhs;
+}
+
+template<int n> vec<n> operator/(const vec<n>& lhs, const double& rhs) {
+    vec<n> ret = lhs;
+    for (int i=n; i--; ret[i]/=rhs);
+    return ret;
+}
+
+template<int n> std::ostream& operator<<(std::ostream& out, const vec<n>& v) {
+    for (int i=0; i<n; i++) out << v[i] << " ";
+    return out;
+}
+
+template<> struct vec<2> {
+    double x = 0, y = 0;
+    double& operator[](const int i)       { assert(i>=0 && i<2); return i ? y : x; }
+    double  operator[](const int i) const { assert(i>=0 && i<2); return i ? y : x; }
+};
+
+template<> struct vec<3> {
+    double x = 0, y = 0, z = 0;
+    double& operator[](const int i)       { assert(i>=0 && i<3); return i ? (1==i ? y : z) : x; }
+    double  operator[](const int i) const { assert(i>=0 && i<3); return i ? (1==i ? y : z) : x; }
+};
+
+template<> struct vec<4> {
+    double x = 0, y = 0, z = 0, w = 0;
+    double& operator[](const int i)       { assert(i>=0 && i<4); return i<2 ? (i ? y : x) : (2==i ? z : w); }
+    double  operator[](const int i) const { assert(i>=0 && i<4); return i<2 ? (i ? y : x) : (2==i ? z : w); }
+    vec<2> xy()  const { return {x, y};    }
+    vec<3> xyz() const { return {x, y, z}; }
+};
+
+typedef vec<2> vec2;
+typedef vec<3> vec3;
+typedef vec<4> vec4;
+
+template<int n> double norm(const vec<n>& v) {
+    return std::sqrt(v*v);
+}
+
+template<int n> vec<n> normalized(const vec<n>& v) {
+    return v / norm(v);
+}
+
+inline vec3 cross(const vec3 &v1, const vec3 &v2) {
+    return {v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x};
+}
+
+template<int n> struct dt;
+
+template<int nrows,int ncols> struct mat {
+    vec<ncols> rows[nrows] = {{}};
+
+          vec<ncols>& operator[] (const int idx)       { assert(idx>=0 && idx<nrows); return rows[idx]; }
+    const vec<ncols>& operator[] (const int idx) const { assert(idx>=0 && idx<nrows); return rows[idx]; }
+
+    double det() const {
+        return dt<ncols>::det(*this);
+    }
+
+    double cofactor(const int row, const int col) const {
+        mat<nrows-1,ncols-1> submatrix;
+        for (int i=nrows-1; i--; )
+            for (int j=ncols-1;j--; submatrix[i][j]=rows[i+int(i>=row)][j+int(j>=col)]);
+        return submatrix.det() * ((row+col)%2 ? -1 : 1);
+    }
+
+    mat<nrows,ncols> invert_transpose() const {
+        mat<nrows,ncols> adjugate_transpose; // transpose to ease determinant computation, check the last line
+        for (int i=nrows; i--; )
+            for (int j=ncols; j--; adjugate_transpose[i][j]=cofactor(i,j));
+        return adjugate_transpose/(adjugate_transpose[0]*rows[0]);
+    }
+
+    mat<nrows,ncols> invert() const {
+        return invert_transpose().transpose();
+    }
+
+    mat<ncols,nrows> transpose() const {
+        mat<ncols,nrows> ret;
+        for (int i=ncols; i--; )
+            for (int j=nrows; j--; ret[i][j]=rows[j][i]);
+        return ret;
+    }
+};
+
+template<int nrows,int ncols> vec<ncols> operator*(const vec<nrows>& lhs, const mat<nrows,ncols>& rhs) {
+    return (mat<1,nrows>{{lhs}}*rhs)[0];
+}
+
+template<int nrows,int ncols> vec<nrows> operator*(const mat<nrows,ncols>& lhs, const vec<ncols>& rhs) {
+    vec<nrows> ret;
+    for (int i=nrows; i--; ret[i]=lhs[i]*rhs);
+    return ret;
+}
+
+template<int R1,int C1,int C2>mat<R1,C2> operator*(const mat<R1,C1>& lhs, const mat<C1,C2>& rhs) {
+    mat<R1,C2> result;
+    for (int i=R1; i--; )
+        for (int j=C2; j--; )
+            for (int k=C1; k--; result[i][j]+=lhs[i][k]*rhs[k][j]);
+    return result;
+}
+
+template<int nrows,int ncols>mat<nrows,ncols> operator*(const mat<nrows,ncols>& lhs, const double& val) {
+    mat<nrows,ncols> result;
+    for (int i=nrows; i--; result[i] = lhs[i]*val);
+    return result;
+}
+
+template<int nrows,int ncols>mat<nrows,ncols> operator/(const mat<nrows,ncols>& lhs, const double& val) {
+    mat<nrows,ncols> result;
+    for (int i=nrows; i--; result[i] = lhs[i]/val);
+    return result;
+}
+
+template<int nrows,int ncols>mat<nrows,ncols> operator+(const mat<nrows,ncols>& lhs, const mat<nrows,ncols>& rhs) {
+    mat<nrows,ncols> result;
+    for (int i=nrows; i--; )
+        for (int j=ncols; j--; result[i][j]=lhs[i][j]+rhs[i][j]);
+    return result;
+}
+
+template<int nrows,int ncols>mat<nrows,ncols> operator-(const mat<nrows,ncols>& lhs, const mat<nrows,ncols>& rhs) {
+    mat<nrows,ncols> result;
+    for (int i=nrows; i--; )
+        for (int j=ncols; j--; result[i][j]=lhs[i][j]-rhs[i][j]);
+    return result;
+}
+
+template<int nrows,int ncols> std::ostream& operator<<(std::ostream& out, const mat<nrows,ncols>& m) {
+    for (int i=0; i<nrows; i++) out << m[i] << std::endl;
+    return out;
+}
+
+template<int n> struct dt { // template metaprogramming to compute the determinant recursively
+    static double det(const mat<n,n>& src) {
+        double ret = 0;
+        for (int i=n; i--; ret += src[0][i] * src.cofactor(0,i));
+        return ret;
+    }
+};
+
+template<> struct dt<1> {   // template specialization to stop the recursion
+    static double det(const mat<1,1>& src) {
+        return src[0][0];
+    }
+};
+

+ 85 - 0
图形学/读TinyRenderer/src/main.cpp

@@ -0,0 +1,85 @@
+#include <limits>
+#include "model.h"
+#include "our_gl.h"
+
+extern mat<4,4> ModelView; // "OpenGL" state matrices
+extern mat<4,4> Projection;
+
+struct Shader : IShader {
+    const Model &model;
+    vec3 uniform_l;       // light direction in view coordinates
+    mat<3,2> varying_uv;  // triangle uv coordinates, written by the vertex shader, read by the fragment shader
+    mat<3,3> varying_nrm; // normal per vertex to be interpolated by FS
+    mat<3,3> view_tri;    // triangle in view coordinates
+
+    Shader(const vec3 l, const Model &m) : model(m) {
+        uniform_l = normalized((ModelView*vec4{l.x, l.y, l.z, 0.}).xyz()); // transform the light vector to view coordinates
+    }
+
+    virtual void vertex(const int iface, const int nthvert, vec4& gl_Position) {
+        vec3 n = model.normal(iface, nthvert);
+        vec3 v = model.vert(iface, nthvert);
+        gl_Position = ModelView * vec4{v.x, v.y, v.z, 1.};
+        varying_uv[nthvert]  = model.uv(iface, nthvert);
+        varying_nrm[nthvert] = (ModelView.invert_transpose() * vec4{n.x, n.y, n.z, 0.}).xyz();
+        view_tri[nthvert]    = gl_Position.xyz();
+        gl_Position = Projection * gl_Position;
+    }
+
+    virtual bool fragment(const vec3 bar, TGAColor &gl_FragColor) const {
+        vec3 bn = normalized(bar * varying_nrm); // per-vertex normal interpolation
+        vec2 uv = bar * varying_uv;              // tex coord interpolation
+
+        mat<3,3> AI = mat<3,3>{ {view_tri[1] - view_tri[0], view_tri[2] - view_tri[0], bn} }.invert(); // for the math refer to the tangent space normal mapping lecture
+        vec3 i = AI * vec3{varying_uv[1].x - varying_uv[0].x, varying_uv[2].x - varying_uv[0].x, 0};   // https://github.com/ssloy/tinyrenderer/wiki/Lesson-6bis-tangent-space-normal-mapping
+        vec3 j = AI * vec3{varying_uv[1].y - varying_uv[0].y, varying_uv[2].y - varying_uv[0].y, 0};
+        mat<3,3> B = mat<3,3>{ { normalized(i), normalized(j), bn } }.transpose();
+
+        vec3 n = normalized(B * model.normal(uv));          // transform the normal from the texture to the tangent space
+        vec3 r = normalized(n * (n * uniform_l)*2 - uniform_l); // reflected light direction, specular mapping is described here: https://github.com/ssloy/tinyrenderer/wiki/Lesson-6-Shaders-for-the-software-renderer
+        double diff = std::max(0., n * uniform_l);                                       // diffuse light intensity
+        double spec = std::pow(std::max(-r.z, 0.), 5+sample2D(model.specular(), uv)[0]); // specular intensity, note that the camera lies on the z-axis (in view), therefore simple -r.z
+
+        TGAColor c = sample2D(model.diffuse(), uv);
+        for (int i : {0,1,2})
+            gl_FragColor[i] = std::min<int>(10 + c[i]*(diff + spec), 255); // (a bit of ambient light, diff + spec), clamp the result
+        return false; // do not discard the pixel
+    }
+};
+
+int main(int argc, char** argv) {
+    //if (2>argc) {
+    //    std::cerr << "Usage: " << argv[0] << " obj/model.obj" << std::endl;
+    //    return 1;
+    //}
+
+    //argc = 2;
+    std::vector<std::string> inputPaths = {"../obj/diablo3_pose/diablo3_pose.obj", "../obj/floor.obj"};
+
+    constexpr int width  = 800;      // output image size
+    constexpr int height = 800;
+    constexpr vec3 light_dir{1,1,1}; // light source
+    constexpr vec3       eye{1,1,3}; // camera position
+    constexpr vec3    center{0,0,0}; // camera direction
+    constexpr vec3        up{0,1,0}; // camera up vector
+
+    lookat(eye, center, up);                            // build the ModelView matrix
+    viewport(width/8, height/8, width*3/4, height*3/4); // build the Viewport matrix
+    projection(norm(eye-center));                       // build the Projection matrix
+    std::vector<double> zbuffer(width*height, std::numeric_limits<double>::max());
+
+    TGAImage framebuffer(width, height, TGAImage::RGB); // the output image
+    for (auto inPath : inputPaths) { // iterate through all input objects
+        Model model(inPath);
+        Shader shader(light_dir, model);
+        for (int t=0; t<model.nfaces(); t++) { // for every triangle
+            vec4 clip_vert[3]; // triangle coordinates (clip coordinates), written by VS, read by FS
+            for (int v : {0,1,2})
+                shader.vertex(t, v, clip_vert[v]);              // call the vertex shader for each triangle vertex
+            rasterize(clip_vert, shader, framebuffer, zbuffer); // actual rasterization routine call
+        }
+    }
+    framebuffer.write_tga_file("framebuffer.tga");
+    return 0;
+}
+

+ 80 - 0
图形学/读TinyRenderer/src/model.cpp

@@ -0,0 +1,80 @@
+#include <sstream>
+#include "model.h"
+
+Model::Model(const std::string filename) {
+    std::ifstream in;
+    in.open(filename, std::ifstream::in);
+    if (in.fail()) return;
+    std::string line;
+    while (!in.eof()) {
+        std::getline(in, line);
+        std::istringstream iss(line.c_str());
+        char trash;
+        if (!line.compare(0, 2, "v ")) {
+            iss >> trash;
+            vec3 v;
+            for (int i : {0,1,2}) iss >> v[i];
+            verts.push_back(v);
+        } else if (!line.compare(0, 3, "vn ")) {
+            iss >> trash >> trash;
+            vec3 n;
+            for (int i : {0,1,2}) iss >> n[i];
+            norms.push_back(normalized(n));
+        } else if (!line.compare(0, 3, "vt ")) {
+            iss >> trash >> trash;
+            vec2 uv;
+            for (int i : {0,1}) iss >> uv[i];
+            tex.push_back({uv.x, 1-uv.y});
+        }  else if (!line.compare(0, 2, "f ")) {
+            int f,t,n, cnt = 0;
+            iss >> trash;
+            while (iss >> f >> trash >> t >> trash >> n) {
+                facet_vrt.push_back(--f);
+                facet_tex.push_back(--t);
+                facet_nrm.push_back(--n);
+                cnt++;
+            }
+            if (3!=cnt) {
+                std::cerr << "Error: the obj file is supposed to be triangulated" << std::endl;
+                return;
+            }
+        }
+    }
+    std::cerr << "# v# " << nverts() << " f# "  << nfaces() << " vt# " << tex.size() << " vn# " << norms.size() << std::endl;
+    auto load_texture = [&filename](const std::string suffix, TGAImage &img) {
+        size_t dot = filename.find_last_of(".");
+        if (dot==std::string::npos) return;
+        std::string texfile = filename.substr(0,dot) + suffix;
+        std::cerr << "texture file " << texfile << " loading " << (img.read_tga_file(texfile.c_str()) ? "ok" : "failed") << std::endl;
+    };
+    load_texture("_diffuse.tga",    diffusemap );
+    load_texture("_nm_tangent.tga", normalmap  );
+    load_texture("_spec.tga",       specularmap);
+}
+
+const TGAImage& Model::diffuse()  const { return diffusemap;  }
+const TGAImage& Model::specular() const { return specularmap; }
+int Model::nverts() const { return verts.size(); }
+int Model::nfaces() const { return facet_vrt.size()/3; }
+
+vec3 Model::vert(const int i) const {
+    return verts[i];
+}
+
+vec3 Model::vert(const int iface, const int nthvert) const {
+    return verts[facet_vrt[iface*3+nthvert]];
+}
+
+vec3 Model::normal(const vec2 &uvf) const {
+    TGAColor c = normalmap.get(uvf[0]*normalmap.width(), uvf[1]*normalmap.height());
+    return vec3{(double)c[2],(double)c[1],(double)c[0]}*2./255. - vec3{1,1,1};
+}
+
+vec2 Model::uv(const int iface, const int nthvert) const {
+    return tex[facet_tex[iface*3+nthvert]];
+}
+
+vec3 Model::normal(const int iface, const int nthvert) const {
+    return norms[facet_nrm[iface*3+nthvert]];
+}
+

+ 26 - 0
图形学/读TinyRenderer/src/model.h

@@ -0,0 +1,26 @@
+#include "geometry.h"
+#include "tgaimage.h"
+
+class Model {
+    std::vector<vec3> verts = {};    // array of vertices        ┐ generally speaking, these arrays
+    std::vector<vec3> norms = {};    // array of normal vectors  │ do not have the same size
+    std::vector<vec2> tex = {};      // array of tex coords      ┘ check the logs of the Model() constructor
+    std::vector<int> facet_vrt = {}; //  ┐ per-triangle indices in the above arrays,
+    std::vector<int> facet_nrm = {}; //  │ the size is supposed to be
+    std::vector<int> facet_tex = {}; //  ┘ nfaces()*3
+    TGAImage diffusemap  = {};       // diffuse color texture
+    TGAImage normalmap   = {};       // normal map texture
+    TGAImage specularmap = {};       // specular texture
+public:
+    Model(const std::string filename);
+    int nverts() const; // number of vertices
+    int nfaces() const; // number of triangles
+    vec3 vert(const int i) const;                          // 0 <= i < nverts()
+    vec3 vert(const int iface, const int nthvert) const;   // 0 <= iface <= nfaces(), 0 <= nthvert < 3
+    vec3 normal(const int iface, const int nthvert) const; // normal coming from the "vn x y z" entries in the .obj file
+    vec3 normal(const vec2 &uv) const;                     // normal vector from the normal map texture
+    vec2 uv(const int iface, const int nthvert) const;     // uv coordinates of triangle corners
+    const TGAImage& diffuse() const;
+    const TGAImage& specular() const;
+};
+

+ 52 - 0
图形学/读TinyRenderer/src/our_gl.cpp

@@ -0,0 +1,52 @@
+#include "our_gl.h"
+
+mat<4,4> ModelView;
+mat<4,4> Viewport;
+mat<4,4> Projection;
+
+void viewport(const int x, const int y, const int w, const int h) {
+    Viewport = {{{w/2., 0, 0, x+w/2.}, {0, h/2., 0, y+h/2.}, {0,0,1,0}, {0,0,0,1}}};
+}
+
+void projection(const double f) { // check https://en.wikipedia.org/wiki/Camera_matrix
+    Projection = {{{1,0,0,0}, {0,-1,0,0}, {0,0,1,0}, {0,0,-1/f,0}}};
+}
+
+void lookat(const vec3 eye, const vec3 center, const vec3 up) { // check https://github.com/ssloy/tinyrenderer/wiki/Lesson-5-Moving-the-camera
+    vec3 z = normalized(center-eye);
+    vec3 x = normalized(cross(up,z));
+    vec3 y = normalized(cross(z, x));
+    ModelView = mat<4,4>{{{x.x,x.y,x.z,0}, {y.x,y.y,y.z,0}, {z.x,z.y,z.z,0}, {0,0,0,1}}} *
+                mat<4,4>{{{1,0,0,-eye.x},  {0,1,0,-eye.y},  {0,0,1,-eye.z},  {0,0,0,1}}};
+}
+
+vec3 barycentric(const vec2 tri[3], const vec2 P) {
+    mat<3,3> ABC = {{ {tri[0].x, tri[0].y, 1.}, {tri[1].x, tri[1].y, 1.}, {tri[2].x, tri[2].y, 1.} }};
+    if (ABC.det()<1) return {-1,1,1}; // for a degenerate triangle generate negative coordinates, it will be thrown away by the rasterizator
+    return ABC.invert_transpose() * vec3{P.x, P.y, 1.};
+}
+
+void rasterize(const vec4 clip_verts[3], const IShader &shader, TGAImage &image, std::vector<double> &zbuffer) {
+    vec4 pts [3] = { Viewport*clip_verts[0], Viewport*clip_verts[1], Viewport*clip_verts[2] }; // screen coordinates before persp. division
+    vec2 pts2[3] = { (pts[0]/pts[0].w).xy(), (pts[1]/pts[1].w).xy(), (pts[2]/pts[2].w).xy() }; // screen coordinates after  perps. division
+
+    int bbminx = std::max(0, static_cast<int>(std::min(std::min(pts2[0].x, pts2[1].x), pts2[2].x))); // bounding box for the triangle
+    int bbminy = std::max(0, static_cast<int>(std::min(std::min(pts2[0].y, pts2[1].y), pts2[2].y))); // clipped by the screen
+    int bbmaxx = std::min(image.width() -1, static_cast<int>(std::max(std::max(pts2[0].x, pts2[1].x), pts2[2].x)));
+    int bbmaxy = std::min(image.height()-1, static_cast<int>(std::max(std::max(pts2[0].y, pts2[1].y), pts2[2].y)));
+#pragma omp parallel for
+    for (int x=bbminx; x<=bbmaxx; x++) { // rasterize the bounding box
+        for (int y=bbminy; y<=bbmaxy; y++) {
+            vec3 bc_screen = barycentric(pts2, {static_cast<double>(x), static_cast<double>(y)});
+            vec3 bc_clip   = { bc_screen.x/pts[0].w, bc_screen.y/pts[1].w, bc_screen.z/pts[2].w };     // check https://github.com/ssloy/tinyrenderer/wiki/Technical-difficulties-linear-interpolation-with-perspective-deformations
+            bc_clip = bc_clip / (bc_clip.x + bc_clip.y + bc_clip.z);
+            double frag_depth = bc_clip * vec3{ clip_verts[0].z, clip_verts[1].z, clip_verts[2].z };
+            if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0 || frag_depth > zbuffer[x+y*image.width()]) continue;
+            TGAColor color;
+            if (shader.fragment(bc_clip, color)) continue; // fragment shader can discard current fragment
+            zbuffer[x+y*image.width()] = frag_depth;
+            image.set(x, y, color);
+        }
+    }
+}
+

+ 16 - 0
图形学/读TinyRenderer/src/our_gl.h

@@ -0,0 +1,16 @@
+#include "tgaimage.h"
+#include "geometry.h"
+
+void viewport(const int x, const int y, const int w, const int h);
+void projection(const double coeff=0); // coeff = -1/c
+void lookat(const vec3 eye, const vec3 center, const vec3 up);
+
+struct IShader {
+    static TGAColor sample2D(const TGAImage &img, const vec2 &uvf) {
+        return img.get(uvf[0] * img.width(), uvf[1] * img.height());
+    }
+    virtual bool fragment(const vec3 bar, TGAColor &color) const = 0;
+};
+
+void rasterize(const vec4 clip_verts[3], const IShader &shader, TGAImage &image, std::vector<double> &zbuffer);
+

+ 202 - 0
图形学/读TinyRenderer/src/tgaimage.cpp

@@ -0,0 +1,202 @@
+#include <iostream>
+#include <cstring>
+#include "tgaimage.h"
+
+TGAImage::TGAImage(const int w, const int h, const int bpp) : w(w), h(h), bpp(bpp), data(w*h*bpp, 0) {}
+
+bool TGAImage::read_tga_file(const std::string filename) {
+    std::ifstream in;
+    in.open(filename, std::ios::binary);
+    if (!in.is_open()) {
+        std::cerr << "can't open file " << filename << "\n";
+        return false;
+    }
+    TGAHeader header;
+    in.read(reinterpret_cast<char *>(&header), sizeof(header));
+    if (!in.good()) {
+        std::cerr << "an error occured while reading the header\n";
+        return false;
+    }
+    w   = header.width;
+    h   = header.height;
+    bpp = header.bitsperpixel>>3;
+    if (w<=0 || h<=0 || (bpp!=GRAYSCALE && bpp!=RGB && bpp!=RGBA)) {
+        std::cerr << "bad bpp (or width/height) value\n";
+        return false;
+    }
+    size_t nbytes = bpp*w*h;
+    data = std::vector<std::uint8_t>(nbytes, 0);
+    if (3==header.datatypecode || 2==header.datatypecode) {
+        in.read(reinterpret_cast<char *>(data.data()), nbytes);
+        if (!in.good()) {
+            std::cerr << "an error occured while reading the data\n";
+            return false;
+        }
+    } else if (10==header.datatypecode||11==header.datatypecode) {
+        if (!load_rle_data(in)) {
+            std::cerr << "an error occured while reading the data\n";
+            return false;
+        }
+    } else {
+        std::cerr << "unknown file format " << (int)header.datatypecode << "\n";
+        return false;
+    }
+    if (!(header.imagedescriptor & 0x20))
+        flip_vertically();
+    if (header.imagedescriptor & 0x10)
+        flip_horizontally();
+    std::cerr << w << "x" << h << "/" << bpp*8 << "\n";
+    return true;
+}
+
+bool TGAImage::load_rle_data(std::ifstream &in) {
+    size_t pixelcount = w*h;
+    size_t currentpixel = 0;
+    size_t currentbyte  = 0;
+    TGAColor colorbuffer;
+    do {
+        std::uint8_t chunkheader = 0;
+        chunkheader = in.get();
+        if (!in.good()) {
+            std::cerr << "an error occured while reading the data\n";
+            return false;
+        }
+        if (chunkheader<128) {
+            chunkheader++;
+            for (int i=0; i<chunkheader; i++) {
+                in.read(reinterpret_cast<char *>(colorbuffer.bgra), bpp);
+                if (!in.good()) {
+                    std::cerr << "an error occured while reading the header\n";
+                    return false;
+                }
+                for (int t=0; t<bpp; t++)
+                    data[currentbyte++] = colorbuffer.bgra[t];
+                currentpixel++;
+                if (currentpixel>pixelcount) {
+                    std::cerr << "Too many pixels read\n";
+                    return false;
+                }
+            }
+        } else {
+            chunkheader -= 127;
+            in.read(reinterpret_cast<char *>(colorbuffer.bgra), bpp);
+            if (!in.good()) {
+                std::cerr << "an error occured while reading the header\n";
+                return false;
+            }
+            for (int i=0; i<chunkheader; i++) {
+                for (int t=0; t<bpp; t++)
+                    data[currentbyte++] = colorbuffer.bgra[t];
+                currentpixel++;
+                if (currentpixel>pixelcount) {
+                    std::cerr << "Too many pixels read\n";
+                    return false;
+                }
+            }
+        }
+    } while (currentpixel < pixelcount);
+    return true;
+}
+
+bool TGAImage::write_tga_file(const std::string filename, const bool vflip, const bool rle) const {
+    constexpr std::uint8_t developer_area_ref[4] = {0, 0, 0, 0};
+    constexpr std::uint8_t extension_area_ref[4] = {0, 0, 0, 0};
+    constexpr std::uint8_t footer[18] = {'T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.','\0'};
+    std::ofstream out;
+    out.open(filename, std::ios::binary);
+    if (!out.is_open()) {
+        std::cerr << "can't open file " << filename << "\n";
+        return false;
+    }
+    TGAHeader header = {};
+    header.bitsperpixel = bpp<<3;
+    header.width  = w;
+    header.height = h;
+    header.datatypecode = (bpp==GRAYSCALE ? (rle?11:3) : (rle?10:2));
+    header.imagedescriptor = vflip ? 0x00 : 0x20; // top-left or bottom-left origin
+    out.write(reinterpret_cast<const char *>(&header), sizeof(header));
+    if (!out.good()) goto err;
+    if (!rle) {
+        out.write(reinterpret_cast<const char *>(data.data()), w*h*bpp);
+        if (!out.good()) goto err;
+    } else if (!unload_rle_data(out)) goto err;
+    out.write(reinterpret_cast<const char *>(developer_area_ref), sizeof(developer_area_ref));
+    if (!out.good()) goto err;
+    out.write(reinterpret_cast<const char *>(extension_area_ref), sizeof(extension_area_ref));
+    if (!out.good()) goto err;
+    out.write(reinterpret_cast<const char *>(footer), sizeof(footer));
+    if (!out.good()) goto err;
+    return true;
+err:
+    std::cerr << "can't dump the tga file\n";
+    return false;
+}
+
+bool TGAImage::unload_rle_data(std::ofstream &out) const {
+    const std::uint8_t max_chunk_length = 128;
+    size_t npixels = w*h;
+    size_t curpix = 0;
+    while (curpix<npixels) {
+        size_t chunkstart = curpix*bpp;
+        size_t curbyte = curpix*bpp;
+        std::uint8_t run_length = 1;
+        bool raw = true;
+        while (curpix+run_length<npixels && run_length<max_chunk_length) {
+            bool succ_eq = true;
+            for (int t=0; succ_eq && t<bpp; t++)
+                succ_eq = (data[curbyte+t]==data[curbyte+t+bpp]);
+            curbyte += bpp;
+            if (1==run_length)
+                raw = !succ_eq;
+            if (raw && succ_eq) {
+                run_length--;
+                break;
+            }
+            if (!raw && !succ_eq)
+                break;
+            run_length++;
+        }
+        curpix += run_length;
+        out.put(raw ? run_length-1 : run_length+127);
+        if (!out.good()) return false;
+        out.write(reinterpret_cast<const char *>(data.data()+chunkstart), (raw?run_length*bpp:bpp));
+        if (!out.good()) return false;
+    }
+    return true;
+}
+
+TGAColor TGAImage::get(const int x, const int y) const {
+    if (!data.size() || x<0 || y<0 || x>=w || y>=h) return {};
+    TGAColor ret = {0, 0, 0, 0, bpp};
+    const std::uint8_t *p = data.data()+(x+y*w)*bpp;
+    for (int i=bpp; i--; ret.bgra[i] = p[i]);
+    return ret;
+}
+
+void TGAImage::set(int x, int y, const TGAColor &c) {
+    if (!data.size() || x<0 || y<0 || x>=w || y>=h) return;
+    memcpy(data.data()+(x+y*w)*bpp, c.bgra, bpp);
+}
+
+void TGAImage::flip_horizontally() {
+    for (int i=0; i<w/2; i++)
+        for (int j=0; j<h; j++)
+            for (int b=0; b<bpp; b++)
+                std::swap(data[(i+j*w)*bpp+b], data[(w-1-i+j*w)*bpp+b]);
+}
+
+void TGAImage::flip_vertically() {
+    for (int i=0; i<w; i++)
+        for (int j=0; j<h/2; j++)
+            for (int b=0; b<bpp; b++)
+                std::swap(data[(i+j*w)*bpp+b], data[(i+(h-1-j)*w)*bpp+b]);
+}
+
+int TGAImage::width() const {
+    return w;
+}
+
+int TGAImage::height() const {
+    return h;
+}
+

+ 48 - 0
图形学/读TinyRenderer/src/tgaimage.h

@@ -0,0 +1,48 @@
+#pragma once
+#include <cstdint>
+#include <fstream>
+#include <vector>
+
+#pragma pack(push,1)
+struct TGAHeader {
+    std::uint8_t  idlength = 0;
+    std::uint8_t  colormaptype = 0;
+    std::uint8_t  datatypecode = 0;
+    std::uint16_t colormaporigin = 0;
+    std::uint16_t colormaplength = 0;
+    std::uint8_t  colormapdepth = 0;
+    std::uint16_t x_origin = 0;
+    std::uint16_t y_origin = 0;
+    std::uint16_t width = 0;
+    std::uint16_t height = 0;
+    std::uint8_t  bitsperpixel = 0;
+    std::uint8_t  imagedescriptor = 0;
+};
+#pragma pack(pop)
+
+struct TGAColor {
+    std::uint8_t bgra[4] = {0,0,0,0};
+    std::uint8_t bytespp = 4;
+    std::uint8_t& operator[](const int i) { return bgra[i]; }
+};
+
+struct TGAImage {
+    enum Format { GRAYSCALE=1, RGB=3, RGBA=4 };
+    TGAImage() = default;
+    TGAImage(const int w, const int h, const int bpp);
+    bool  read_tga_file(const std::string filename);
+    bool write_tga_file(const std::string filename, const bool vflip=true, const bool rle=true) const;
+    void flip_horizontally();
+    void flip_vertically();
+    TGAColor get(const int x, const int y) const;
+    void set(const int x, const int y, const TGAColor &c);
+    int width()  const;
+    int height() const;
+private:
+    bool   load_rle_data(std::ifstream &in);
+    bool unload_rle_data(std::ofstream &out) const;
+    int w = 0, h = 0;
+    std::uint8_t bpp = 0;
+    std::vector<std::uint8_t> data = {};
+};
+