как видеть анимацию а не статическую картинку: #define  NOMINMAX
#include <windows.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <cmath>
#include <chrono>
#include <limits>
#include "PngImage.h"
#ifndef M_PI
#define M_PI 3.14159265359
#endif
using namespace std;
const int w = 600, h = 600;
uint32_t image[w][h];
const int depth = 255;
template <class t> struct Vec2 {
    t x, y;
    Vec2<t>() : x(t()), y(t()) {}
    Vec2<t>(t _x, t _y) : x(_x), y(_y) {}
    Vec2<t>(const Vec2<t>& v) : x(t()), y(t()) { *this = v; }
    Vec2<t>& operator =(const Vec2<t>& v) {
        if (this != &v) {
            x = v.x;
            y = v.y;
        }
        return *this;
    }
    Vec2<t> operator +(const Vec2<t>& V) const { return Vec2<t>(x + V.x, y + V.y); }
    Vec2<t> operator -(const Vec2<t>& V) const { return Vec2<t>(x - V.x, y - V.y); }
    Vec2<t> operator *(float f)          const { return Vec2<t>(x * f, y * f); }
    t& operator[](const int i) { return i == 0 ? x : y; }
    template <class > friend ostream& operator<<(ostream& s, Vec2<t>& v);
};
template <class t> struct Vec3 {
    t x, y, z;
    Vec3<t>() : x(t()), y(t()), z(t()) {}
    Vec3<t>(t _x, t _y, t _z) : x(_x), y(_y), z(_z) {}
    Vec3<t>(const Vec3<t>& v) : x(t()), y(t()), z(t()) { *this = v; }
    template <class u> Vec3(const Vec3<u>& v);
    Vec3<t>& operator =(const Vec3<t>& v) {
        if (this != &v) {
            x = v.x;
            y = v.y;
            z = v.z;
        }
        return *this;
    }
    Vec3<t> operator ^(const Vec3<t>& v) const {
        return Vec3<t>(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x);
    }
    Vec3<t> operator +(const Vec3<t>& v) const { return Vec3<t>(x + v.x, y + v.y, z + v.z); }
    Vec3<t> operator -(const Vec3<t>& v) const { return Vec3<t>(x - v.x, y - v.y, z - v.z); }
    Vec3<t> operator *(float f)          const { return Vec3<t>(x * f, y * f, z * f); }
    t       operator *(const Vec3<t>& v) const { return x * v.x + y * v.y + z * v.z; }
    float norm() const { return sqrt(x * x + y * y + z * z); }
    Vec3<t>& normalize(t l = 1) { *this = (*this) * (l / norm()); return *this; }
    t& operator[](const int i) { if (i == 0) return x; else if (i == 1) return y; else return z; }
    template <class > friend ostream& operator<<(ostream& s, Vec3<t>& v);
};
template <class t>
template <class u>
Vec3<t>::Vec3(const Vec3<u>& v)
    : x(static_cast<t>(v.x)), y(static_cast<t>(v.y)), z(static_cast<t>(v.z)) {
}
typedef Vec2<float> Vec2f;
typedef Vec2<int>   Vec2i;
typedef Vec3<float> Vec3f;
typedef Vec3<int>   Vec3i;
template <class t> ostream& operator<<(ostream& s, Vec2<t>& v) {
    s << "(" << v.x << ", " << v.y << ")";
    return s;
}
template <class t> ostream& operator<<(ostream& s, Vec3<t>& v) {
    s << "(" << v.x << ", " << v.y << ", " << v.z << ")";
    return s;
}
class Model
{
private:
    vector<Vec3f> verts_;
    vector<vector<Vec3i>> faces_;
    vector<Vec3f> norms_;
    vector<Vec2f> uv_;
    png::PngImage diffusemap_;
    void load_texture(const char* filename) {
        diffusemap_.read(filename);
        diffusemap_.flipVertically();
    }
public:
    Model(const string& buffer, const char* diffuseTexName) : verts_(), faces_() {
        istringstream in(buffer);
        string line;
        while (getline(in, line)) {
            istringstream iss(line);
            char trash;
            if (!line.compare(0, 2, "v ")) {
                iss >> trash;
                Vec3f v;
                for (int i = 0; i < 3; i++) iss >> v[i];
                verts_.push_back(v);
            }
            else if (!line.compare(0, 3, "vn ")) {
                iss >> trash >> trash;
                Vec3f n;
                for (int i = 0; i < 3; i++) iss >> n[i];
                norms_.push_back(n);
            }
            else if (!line.compare(0, 3, "vt ")) {
                iss >> trash >> trash;
                Vec2f uv;
                for (int i = 0; i < 2; i++) iss >> uv[i];
                uv_.push_back(uv);
            }
            else if (!line.compare(0, 2, "f ")) {
                vector<Vec3i> f;
                Vec3i tmp;
                iss >> trash;
                while (iss >> tmp[0] >> trash >> tmp[1] >> trash >> tmp[2]) {
                    for (int i = 0; i < 3; i++) tmp[i]--;
                    f.push_back(tmp);
                }
                faces_.push_back(f);
            }
        }
        load_texture(diffuseTexName);
    }
    ~Model() {
    }
    int nverts() const {
        return (int)verts_.size();
    }
    int nfaces() const {
        return (int)faces_.size();
    }
    Vec2f uv(int iface, int nvert) {
        int idx = faces_[iface][nvert][1];
        return uv_[idx];
    }
    Vec3f normal(int iface, int nvert) {
        int idx = faces_[iface][nvert][2];
        return norms_[idx];
    }
    uint32_t diffuse(Vec2f uvf) {
        return diffusemap_.getPixel(
            uvf.x * (diffusemap_.getImage().width - 1),
            uvf.y * (diffusemap_.getImage().height - 1));
    }
    vector<int> face(int idx) {
        vector<int> face;
        for (int i = 0; i < (int)faces_[idx].size(); i++) face.push_back(faces_[idx][i][0]);
        return face;
    }
    Vec3f vert(int i) const {
        return verts_[i];
    }
};
void triangle(Vec3f* pts, Vec2f* uvs, Vec3f* norms, uint32_t(&image)[w][h], int* zbuffer, Vec3f light_dir, Model* model) {
    Vec3i t0(int((pts[0].x + 1.) * w / 2.), int((pts[0].y + 1.) * h / 2.), int((pts[0].z + 1.) * depth / 2.));
    Vec3i t1(int((pts[1].x + 1.) * w / 2.), int((pts[1].y + 1.) * h / 2.), int((pts[1].z + 1.) * depth / 2.));
    Vec3i t2(int((pts[2].x + 1.) * w / 2.), int((pts[2].y + 1.) * h / 2.), int((pts[2].z + 1.) * depth / 2.));
    if (t0.y == t1.y && t0.y == t2.y) return;
    if (t0.y > t1.y) { swap(t0, t1); swap(uvs[0], uvs[1]); swap(norms[0], norms[1]); }
    if (t0.y > t2.y) { swap(t0, t2); swap(uvs[0], uvs[2]); swap(norms[0], norms[2]); }
    if (t1.y > t2.y) { swap(t1, t2); swap(uvs[1], uvs[2]); swap(norms[1], norms[2]); }
    int total_height = t2.y - t0.y;
    for (int i = 0; i <= total_height; i++) {
        bool second_half = i > t1.y - t0.y || t1.y == t0.y;
        int segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;
        float alpha = total_height == 0 ? 0. : (float)i / total_height;
        float beta = segment_height == 0 ? 0. : (float)(i - (second_half ? t1.y - t0.y : 0)) / segment_height;
        Vec3i A = t0 + Vec3i(t2 - t0) * alpha;
        Vec3i B = second_half ? t1 + Vec3i(t2 - t1) * beta : t0 + Vec3i(t1 - t0) * beta;
        Vec2f uvA = uvs[0] + (uvs[2] - uvs[0]) * alpha;
        Vec2f uvB = second_half ? uvs[1] + (uvs[2] - uvs[1]) * beta : uvs[0] + (uvs[1] - uvs[0]) * beta;
        Vec3f nA = norms[0] + (norms[2] - norms[0]) * alpha;
        Vec3f nB = second_half ? norms[1] + (norms[2] - norms[1]) * beta : norms[0] + (norms[1] - norms[0]) * beta;
        if (A.x > B.x) { swap(A, B); swap(uvA, uvB); swap(nA, nB); }
        for (int j = A.x; j <= B.x; j++) {
            float phi = B.x == A.x ? 1.0f : (float)(j - A.x) / (float)(B.x - A.x);
            Vec3i P = A + Vec3i(B - A) * phi;
            Vec2f uvP = uvA + (uvB - uvA) * phi;
            Vec3f nP = nA + (nB - nA) * phi;
            P.x = j; P.y = t0.y + i;
            int idx = P.x + P.y * w;
            if (P.x >= w || P.y >= h || P.x < 0 || P.y < 0) continue;
            if (zbuffer[idx] < P.z) {
                zbuffer[idx] = P.z;
                nP.normalize();
                float intensity = max(0.f, nP * light_dir);
                uint32_t packed = model->diffuse(uvP);
                image[P.y][P.x] =
                    (uint8_t(packed) & 0xFF << 24) |  // A
                    (uint8_t(((packed >> 24) & 0xFF) * intensity) << 16) |  // R
                    (uint8_t(((packed >> 16) & 0xFF) * intensity) << 8) |  // G
                    (uint8_t(((packed >> 8) & 0xFF) * intensity) << 0);      // B
            }
        }
    }
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// Добавляем функцию для поворота вектора вокруг оси Y
Vec3f rotateY(const Vec3f& v, float angle) {
    float rad = angle * M_PI / 180.0f;
    float cosA = cos(rad);
    float sinA = sin(rad);
    return Vec3f(
        v.x * cosA + v.z * sinA,
        v.y,
        -v.x * sinA + v.z * cosA
    );
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"SimpleWindowClass";
    RegisterClass(&wc);
    HWND hwnd = CreateWindowEx(
        0,
        L"SimpleWindowClass",
        L"Image",
        WS_MINIMIZEBOX | WS_SYSMENU,
        CW_USEDEFAULT, CW_USEDEFAULT, w, h,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if (hwnd == NULL) {
        return 0;
    }
    // Инициализируем буфер глубины и изображение
    memset(image, 0, sizeof(image));
    int* zbuffer = new int[w * h];
    for (int i = 0; i < w * h; i++) {
        zbuffer[i] = numeric_limits<int>::min();
    }
    Vec3f light_dir(0, 0, 1);
    ifstream in("african_head.obj");
    string buffer((istreambuf_iterator<char>(in)), istreambuf_iterator<char>());
    in.close();
    Model* model = new Model(buffer, "african_head_diffuse.png");
    // Устанавливаем угол поворота (в градусах)
    float angle = 0.1f; // Можете изменить угол для другого поворота
    // Применяем поворот к каждой вершине модели
    vector<Vec3f> rotated_verts(model->nverts());
    for (int i = 0; i < model->nverts(); i++) {
        rotated_verts[i] = rotateY(model->vert(i), angle);
    }
    for (int i = 0; i < model->nfaces(); i++) {
        Vec3f pts[3];
        Vec2f uvs[3];
        Vec3f norms[3];
        for (int j = 0; j < 3; j++) {
            int idx = model->face(i)[j];
            pts[j] = rotated_verts[idx];
            uvs[j] = model->uv(i, j);
            norms[j] = model->normal(i, j);
            // Так же вращаем нормали
            norms[j] = rotateY(norms[j], angle);
            norms[j].normalize();
        }
        triangle(pts, uvs, norms, image, zbuffer, light_dir, model);
    }
    ShowWindow(hwnd, nCmdShow);
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        angle += 0.01;
        DispatchMessage(&msg);
    }
    delete model;
    delete[] zbuffer;
    return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        BITMAPINFO bmi = {};
        bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bmi.bmiHeader.biWidth = w;
        bmi.bmiHeader.biHeight = h;
        bmi.bmiHeader.biPlanes = 1;
        bmi.bmiHeader.biBitCount = 32;
        bmi.bmiHeader.biCompression = BI_RGB;
        StretchDIBits(
            hdc,
            0, 0, w, h,
            0, 0, w, h,
            image,
            &bmi,
            DIB_RGB_COLORS,
            SRCCOPY
        );
        EndPaint(hwnd, &ps);
    }
                 return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}