[OpenGL]使用glsl实现smallpt

一、简介

本文介绍了如何使用 OpenGL,使用 glsl 语言在 Fragment shader 中实现 smallpt。程序完成后可以得到以下渲染结果(samples per pixel, spp = 16)。在程序中按下A,W可以左右平移,按下W,S可以前后平移:

二、smallpt

0. smallpt 介绍

smallpt 是一个简易的路径跟踪程序,仅使用99行代码就实现了简单的路径跟踪。对 smallpt 代码的介绍可以查看[图形学]smallpt代码详解(上)[图形学]smallpt代码详解(中)[图形学]smallpt代码详解(下)

由于 smallpt 使用递归实现,然而 glsl 中并不支持函数的递归调用,因此需要先将基于递归的smallpt转为基于迭代的smallpt

1. 基于迭代的 smallpt

在基于递归的 smallpt 中,像素 (x,y) 处的渲染结果 L L L 等于:
L = L e 0 + L c 0 ∗ r a d i a n c e ( r a y 0 ) r a d i a n c e ( r a y 0 ) = L e 1 + L c 1 ∗ r a d i a n c e ( r a y 1 ) r a d i a n c e ( r a y 1 ) = L e 2 + L c 2 ∗ r a d i a n c e ( r a y 2 ) . . . L = L_{e0} + L_{c0}* radiance(ray_{0}) \\ radiance(ray_{0}) = L_{e1} + L_{c1}* radiance(ray_{1}) \\ radiance(ray_{1}) = L_{e2} + L_{c2}* radiance(ray_{2}) \\ ... L=Le0+Lc0∗radiance(ray0)radiance(ray0)=Le1+Lc1∗radiance(ray1)radiance(ray1)=Le2+Lc2∗radiance(ray2)...

其中 L e 0 L_{e0} Le0为光线第一次相交处图元的自发光项emit, L c 0 L_{c0} Lc0是光线第一次相交处图元的颜色项color。 L e 1 L_{e1} Le1为光线第二次相交处图元的自发光项emit, L c 1 L_{c1} Lc1是光线第二次相交处图元的颜色项color。以此类推。

根据上式可以得到:
L = L e 0 + L c 0 ∗ L e 1 + L c 0 ∗ L c 1 ∗ L e 2 + . . . L = L_{e0} + L_{c0}*L_{e1} + L_{c0}*L_{c1}*L_{e2} + ... L=Le0+Lc0∗Le1+Lc0∗Lc1∗Le2+...

我们可以使用迭代(循环)的结构计算上式,在循环中维护变量LLfL是最终的结果,Lf等于 L c 0 ∗ L c 1 . . . L c i L_{c0}*L_{c1}... L_{ci} Lc0∗Lc1...Lci,伪代码如下:

cpp 复制代码
for(i=0;i<max_depth; i++)
{
L = L + Lf * L_ei;
Lf = Lf * L_ci;
}

基于 迭代的smallpt 代码如下:

cpp 复制代码
#include <math.h>   // smallpt, a Path Tracer by Kevin Beason, 2008
#include <stdlib.h> // Make : g++ -O3 -fopenmp smallpt.cpp -o smallpt
#include <stdio.h>  //        Remove "-fopenmp" for g++ version < 4.2
#define double float
struct Vec {        // Usage: time ./smallpt 5000 && xv image.ppm
  double x, y, z;                  // position, also color (r,g,b)
  Vec(double x_=0, double y_=0, double z_=0){ x=x_; y=y_; z=z_; }
  Vec operator+(const Vec &b) const { return Vec(x+b.x,y+b.y,z+b.z); }
  Vec operator-(const Vec &b) const { return Vec(x-b.x,y-b.y,z-b.z); }
  Vec operator*(double b) const { return Vec(x*b,y*b,z*b); }
  Vec mult(const Vec &b) const { return Vec(x*b.x,y*b.y,z*b.z); }
  Vec& norm(){ return *this = *this * (1/sqrt(x*x+y*y+z*z)); }
  double dot(const Vec &b) const { return x*b.x+y*b.y+z*b.z; } // cross:
  Vec operator%(Vec&b){return Vec(y*b.z-z*b.y,z*b.x-x*b.z,x*b.y-y*b.x);}
};
struct Ray { Vec o, d; Ray(Vec o_, Vec d_) : o(o_), d(d_) {} };
enum Refl_t { DIFF, SPEC, REFR };  // material types, used in radiance()
struct Sphere {
  double rad;       // radius
  Vec p, e, c;      // position, emission, color
  Refl_t refl;      // reflection type (DIFFuse, SPECular, REFRactive)
  Sphere(double rad_, Vec p_, Vec e_, Vec c_, Refl_t refl_):
    rad(rad_), p(p_), e(e_), c(c_), refl(refl_) {}
  double intersect(const Ray &r) const { // returns distance, 0 if nohit
    Vec op = p-r.o; // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0
    double t, eps=1e-4, b=op.dot(r.d), det=b*b-op.dot(op)+rad*rad;
    if (det<0) return 0; else det=sqrt(det);
    return (t=b-det)>eps ? t : ((t=b+det)>eps ? t : 0);
  }
};
Sphere spheres[] = {//Scene: radius, position, emission, color, material
  Sphere(1e5, Vec( 1e5+1,40.8,81.6), Vec(),Vec(.75,.25,.25),DIFF),//Left
  Sphere(1e5, Vec(-1e5+99,40.8,81.6),Vec(),Vec(.25,.25,.75),DIFF),//Rght
  Sphere(1e5, Vec(50,40.8, 1e5),     Vec(),Vec(.75,.75,.75),DIFF),//Back
  Sphere(1e5, Vec(50,40.8,-1e5+170), Vec(),Vec(),           DIFF),//Frnt
  Sphere(1e5, Vec(50, 1e5, 81.6),    Vec(),Vec(.75,.75,.75),DIFF),//Botm
  Sphere(1e5, Vec(50,-1e5+81.6,81.6),Vec(),Vec(.75,.75,.75),DIFF),//Top
  Sphere(16.5,Vec(27,16.5,47),       Vec(),Vec(1,1,1)*.999, SPEC),//Mirr
  Sphere(16.5,Vec(73,16.5,78),       Vec(),Vec(1,1,1)*.999, REFR),//Glas
  Sphere(600, Vec(50,681.6-.27,81.6),Vec(12,12,12),  Vec(), DIFF) //Lite
};
inline double clamp(double x){ return x<0 ? 0 : x>1 ? 1 : x; }
inline int toInt(double x){ return int(pow(clamp(x),1/2.2)*255+.5); }
inline bool intersect(const Ray &r, double &t, int &id){
  double n=sizeof(spheres)/sizeof(Sphere), d, inf=t=1e20;
  for(int i=int(n);i--;) if((d=spheres[i].intersect(r))&&d<t){t=d;id=i;}
  return t<inf;
}
Vec radiance(const Ray &r_, int depth_, unsigned short *Xi){
  double t;                               // distance to intersection
  int id=0;                               // id of intersected object
  Ray r=r_;
  int depth=depth_;
  // L0 = Le0 + f0*(L1)
  //    = Le0 + f0*(Le1 + f1*L2)
  //    = Le0 + f0*(Le1 + f1*(Le2 + f2*(L3))
  //    = Le0 + f0*(Le1 + f1*(Le2 + f2*(Le3 + f3*(L4)))
  //    = ...
  //    = Le0 + f0*Le1 + f0*f1*Le2 + f0*f1*f2*Le3 + f0*f1*f2*f3*Le4 + ...
  // 
  // So:
  // F = 1
  // while (1){
  //   L += F*Lei
  //   F *= fi
  // }
  Vec cl(0,0,0);   // accumulated color
  Vec cf(1,1,1);  // accumulated reflectance
  while (1){
    if (!intersect(r, t, id)) return cl; // if miss, return black
    const Sphere &obj = spheres[id];        // the hit object
    Vec x=r.o+r.d*t, n=(x-obj.p).norm(), nl=n.dot(r.d)<0?n:n*-1, f=obj.c;
    double p = f.x>f.y && f.x>f.z ? f.x : f.y>f.z ? f.y : f.z; // max refl
    cl = cl + cf.mult(obj.e);
    if (++depth>5) if (erand48(Xi)<p) f=f*(1/p); else return cl; //R.R.
    cf = cf.mult(f);
    if (obj.refl == DIFF){                  // Ideal DIFFUSE reflection
      double r1=2*M_PI*erand48(Xi), r2=erand48(Xi), r2s=sqrt(r2);
      Vec w=nl, u=((fabs(w.x)>.1?Vec(0,1):Vec(1))%w).norm(), v=w%u;
      Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm();
      //return obj.e + f.mult(radiance(Ray(x,d),depth,Xi));
      r = Ray(x,d);
      continue;
    } else if (obj.refl == SPEC){           // Ideal SPECULAR reflection
      //return obj.e + f.mult(radiance(Ray(x,r.d-n*2*n.dot(r.d)),depth,Xi));
      r = Ray(x,r.d-n*2*n.dot(r.d));
      continue;
    }
    Ray reflRay(x, r.d-n*2*n.dot(r.d));     // Ideal dielectric REFRACTION
    bool into = n.dot(nl)>0;                // Ray from outside going in?
    double nc=1, nt=1.5, nnt=into?nc/nt:nt/nc, ddn=r.d.dot(nl), cos2t;
    if ((cos2t=1-nnt*nnt*(1-ddn*ddn))<0){    // Total internal reflection
      //return obj.e + f.mult(radiance(reflRay,depth,Xi));
      r = reflRay;
      continue;
    }
    Vec tdir = (r.d*nnt - n*((into?1:-1)*(ddn*nnt+sqrt(cos2t)))).norm();
    double a=nt-nc, b=nt+nc, R0=a*a/(b*b), c = 1-(into?-ddn:tdir.dot(n));
    double Re=R0+(1-R0)*c*c*c*c*c,Tr=1-Re,P=.25+.5*Re,RP=Re/P,TP=Tr/(1-P);
    // return obj.e + f.mult(erand48(Xi)<P ?
    //                       radiance(reflRay,    depth,Xi)*RP:
    //                       radiance(Ray(x,tdir),depth,Xi)*TP);
    if (erand48(Xi)<P){
      cf = cf*RP;
      r = reflRay;
    } else {
      cf = cf*TP;
      r = Ray(x,tdir);
    }
    continue;
  }
}
int main(int argc, char *argv[]){
  int w=512, h=384, samps = argc==2 ? atoi(argv[1])/4 : 1; // # samples
  Ray cam(Vec(50,52,295.6), Vec(0,-0.042612,-1).norm()); // cam pos, dir
  Vec cx=Vec(w*.5135/h), cy=(cx%cam.d).norm()*.5135, r, *c=new Vec[w*h];
#pragma omp parallel for schedule(dynamic, 1) private(r)       // OpenMP
  for (int y=0; y<h; y++){                       // Loop over image rows
    fprintf(stderr,"\rRendering (%d spp) %5.2f%%",samps*4,100.*y/(h-1));
    for (unsigned short x=0, Xi[3]={0,0,y*y*y}; x<w; x++)   // Loop cols
      for (int sy=0, i=(h-y-1)*w+x; sy<2; sy++)     // 2x2 subpixel rows
        for (int sx=0; sx<2; sx++, r=Vec()){        // 2x2 subpixel cols
          for (int s=0; s<samps; s++){
            double r1=2*erand48(Xi), dx=r1<1 ? sqrt(r1)-1: 1-sqrt(2-r1);
            double r2=2*erand48(Xi), dy=r2<1 ? sqrt(r2)-1: 1-sqrt(2-r2);
            Vec d = cx*( ( (sx+.5 + dx)/2 + x)/w - .5) +
                    cy*( ( (sy+.5 + dy)/2 + y)/h - .5) + cam.d;
            r = r + radiance(Ray(cam.o+d*140,d.norm()),0,Xi)*(1./samps);
          } // Camera rays are pushed ^^^^^ forward to start in interior
          c[i] = c[i] + Vec(clamp(r.x),clamp(r.y),clamp(r.z))*.25;
        }
  }
  FILE *f = fopen("image.ppm", "w");         // Write image to PPM file.
  fprintf(f, "P3\n%d %d\n%d\n", w, h, 255);
  for (int i=0; i<w*h; i++)
    fprintf(f,"%d %d %d ", toInt(c[i].x), toInt(c[i].y), toInt(c[i].z));
}

2. 使用 glsl 实现 smallpt

使用 glsl 实现 smallpt 时,在 Fragment shader 中实现上述的基于迭代的smallpt。在 Fragment shader 中,每个片段(片元)为一个像素点(x,y),在该像素点发射一根光线,调用函数radianc(Ray r)计算该像素处的着色值。

2.1. Vertex shader 代码

smallpt.vert

cpp 复制代码
#version 430
layout(location = 0) in vec3 position;
void main() { gl_Position = vec4(position, 1.0f); }

2.2. Fragment shader 代码

smallpt.frag

cpp 复制代码
#version 430
out vec4 frag_colour;
uniform ivec2 image_size;  // 窗口的宽、高
uniform vec3 cam_position; // 相机的位置
uniform int spp;           // samples per pixel

/* 用于生成随机数 */
int erand_i = 1;
int erand_j = 1;
double erand08() {
  int k;
  k = (erand_i + erand_j) % 16777216;
  erand_i = erand_j;
  erand_j = k;
  return double(erand_i) / 16777216.0;
}
uint hash(uint x) {
  x += (x << 10u);
  x ^= (x >> 6u);
  x += (x << 3u);
  x ^= (x >> 11u);
  x += (x << 15u);
  return x;
}
double floatConstruct(uint m) {
  const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask\n
  const uint ieeeOne = 0x3F800000u;      // 1.0 in IEEE binary32\n
  m &= ieeeMantissa; // Keep only mantissa bits (fractional part)\n
  m |= ieeeOne;      // Add fractional part to 1.0\n
  double f = double(uintBitsToFloat(m)); // Range [1:2]\n
  return f - 1.0;                        // Range [0:1]\n
}
double random(float x) { return floatConstruct(hash(floatBitsToUint(x))); }
/********************/

struct Ray {
  dvec3 o, d;
};
int DIFF = 0;
int SPEC = 1;
int REFR = 2;
struct Sphere {
  double rad;
  dvec3 p, e, c;
  int refl;
};
Sphere spheres[] = {
    // Scene: radius, position, emission, color, material\n
    Sphere(1e5, dvec3(1e5 + 1, 40.8, 81.6), dvec3(0, 0, 0),
           dvec3(.75, .25, .25),
           DIFF), // Left\n
    Sphere(1e5, dvec3(-1e5 + 99, 40.8, 81.6), dvec3(0, 0, 0),
           dvec3(.25, .25, .75),
           DIFF), // Rght\n
    Sphere(1e5, dvec3(50, 40.8, 1e5), dvec3(0, 0, 0), dvec3(.75, .75, .75),
           DIFF), // Back\n
    // Sphere(1e5, dvec3(50, 40.8, -1e5 + 170), dvec3(0, 0, 0), dvec3(0, 0, 0),
    //        DIFF), // Frnt\n
    Sphere(1e5, dvec3(50, 1e5, 81.6), dvec3(0, 0, 0), dvec3(.75, .75, .75),
           DIFF), // Botm\n
    Sphere(1e5, dvec3(50, -1e5 + 81.6, 81.6), dvec3(0, 0, 0),
           dvec3(.75, .75, .75),
           DIFF), // Top\n
    Sphere(16.5, dvec3(27, 16.5, 47), dvec3(0, 0, 0), dvec3(1, 1, 1) * .999,
           SPEC), // Mirr\n
    Sphere(16.5, dvec3(73, 16.5, 78), dvec3(0, 0, 0), dvec3(1, 1, 1) * .999,
           REFR), // Glas\n
    Sphere(600, dvec3(50, 681.6 - .27, 81.6), dvec3(12, 12, 12), dvec3(0, 0, 0),
           DIFF) // Lite\n
};
double intersect(Sphere s, Ray r) { // returns distance, 0 if nohit\n
  dvec3 op = s.p - r.o; // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0\n
  double t, eps = 4e-2, b = dot(op, r.d),
            det = b * b - dot(op, op) + s.rad * s.rad;
  if (det < 0)
    return 0;
  else
    det = sqrt(det);
  return (t = b - det) > eps ? t : ((t = b + det) > eps ? t : 0);
}
// int numSpheres = 9; // sizeof(spheres)/sizeof(Sphere);\n
int numSpheres = 8; // sizeof(spheres)/sizeof(Sphere);\n
struct TID {
  double t;
  int id;
} tid;
bool Intersect(Ray r) { // closest intersecting sphere\n
  double d, inf = tid.t = 1e20;
  int i;
  for (i = 0; i < numSpheres; i++) {
    d = intersect(spheres[i], r);
    if ((d > 0) && (d < tid.t)) {
      tid.t = d;
      tid.id = i;
    }
  }
  return tid.t < inf;
}
double M_PI = 3.14159265358979323846;
double M_1_PI = 0.31830988618379067154;
int depth;
dvec3 radiance(Ray r) {
  double t;                  // distance to intersection\n
  int id;                    // id of intersected object\n
  dvec3 cl = dvec3(0, 0, 0); // accumulated color\n
  dvec3 cf = dvec3(1, 1, 1); // accumulated reflectance\n
  for (depth = 0; depth < 5; depth++) {
    if (!Intersect(r))
      return cl; // if miss, return black\n
    t = tid.t;
    id = tid.id;
    Sphere obj = spheres[id];
    dvec3 x = r.o + r.d * t, n = normalize(x - obj.p),
          nl = ((dot(n, r.d) < 0) ? n : n * -1), f = obj.c;
    double p = (((f.x > f.y) && (f.x > f.z))
                    ? f.x
                    : ((f.y > f.z) ? f.y : f.z)); // max refl\n
    cl = cl + cf * obj.e;
    if (depth > 3) {
      if (erand08() < p)
        f = f * (1 / p);
      else
        return cl;
    } // R.R.\n
    cf = cf * f;
    if (obj.refl == DIFF) { // Ideal DIFFUSE reflection\n
      double r1 = 2 * M_PI * erand08(), r2 = erand08(), r2s = sqrt(r2);
      dvec3 w = nl,
            u = normalize(
                cross(((abs(w.x) > .1) ? dvec3(0, 1, 0) : dvec3(1, 0, 0)), w)),
            v = cross(w, u);
      dvec3 d = normalize(u * cos(float(r1)) * r2s + v * sin(float(r1)) * r2s +
                          w * sqrt(1 - r2));
      r = Ray(x, d);
      continue;
    } else if (obj.refl == SPEC) { // Ideal SPECULAR reflection\n
      r = Ray(x, r.d - n * 2 * dot(n, r.d));
      continue;
    }
    Ray reflRay =
        Ray(x, r.d - n * 2 * dot(n, r.d)); // Ideal dielectric REFRACTION\n
    bool into = (dot(n, nl) > 0);          // Ray from outside going in?\n
    double nc = 1, nt = 1.5, nnt = (into ? nc / nt : nt / nc),
           ddn = dot(r.d, nl), cos2t = 1 - nnt * nnt * (1 - ddn * ddn);
    if (cos2t < 0) { // Total internal reflection\n
      r = reflRay;
      continue;
    }
    dvec3 tdir = normalize(r.d * nnt -
                           n * ((into ? 1 : -1) * (ddn * nnt + sqrt(cos2t))));
    double a = nt - nc, b = nt + nc, R0 = a * a / (b * b),
           c = 1 - (into ? -ddn : dot(tdir, n));
    double Re = R0 + (1 - R0) * c * c * c * c * c, Tr = 1 - Re,
           P = .25 + .5 * Re, RP = Re / P, TP = Tr / (1 - P);
    if (erand08() < P) {
      cf = cf * RP;
      r = reflRay;
    } else {
      cf = cf * TP;
      r = Ray(x, tdir);
    }
  }
  return cl;
}
double clamp(double x) { return ((x < 0) ? 0 : ((x > 1) ? 1 : x)); }
void main() {
  int w = image_size.x, h = image_size.y;
  int samps = int(spp / 4 <= 0 ? 1 : spp / 4); // # samples\n

  Ray cam = Ray(dvec3(50, 52, 295.6),
                normalize(dvec3(0, -0.042612, -1))); // cam pos, dir\n
  cam = Ray(dvec3(cam_position),
            normalize(dvec3(0, -0.042612, -1))); // cam pos, dir\n
  dvec3 cx = dvec3(w * .5135 / h, 0, 0);
  dvec3 cy = normalize(cross(cx, cam.d)) * .5135;
  dvec3 r = dvec3(0, 0, 0);
  dvec3 c = dvec3(0, 0, 0);
  int x, y; // 屏幕像素位置
  /*
   *  屏幕像素坐标示意图:
   *   ^
   *  y|
   *  (0,h)......(w,h)
   *   |           |
   *   |           |
   *   |           |
   *  (0,0)......(w,0)---> x
   */

  x = int(gl_FragCoord[0]);
  // y = int(h - gl_FragCoord[1] - 1);
  y = int(gl_FragCoord[1]);

  erand_i = int(random(y * w + x) * 16777216);
  erand_j = int(random(x * h + y) * 16777216);
  int sx, sy, s;
  for (sy = 0; sy < 2; sy++) {   // 2x2 subpixel rows\n
    for (sx = 0; sx < 2; sx++) { // 2x2 subpixel cols\n
      for (s = 0; s < samps; s++) {
        double r1 = 2 * erand08(),
               dx = ((r1 < 1) ? sqrt(r1) - 1 : 1 - sqrt(2 - r1));
        double r2 = 2 * erand08(),
               dy = ((r2 < 1) ? sqrt(r2) - 1 : 1 - sqrt(2 - r2));
        dvec3 d = cx * (((sx + .5 + dx) / 2 + x) / w - .5) +
                  cy * (((sy + .5 + dy) / 2 + y) / h - .5) + cam.d;
        r = r + radiance(Ray(cam.o + d * 140, normalize(d))) * (1. / samps);
      } // Camera rays are pushed ^^^^^ forward to start in interior\n
      c = c + dvec3(clamp(r.x), clamp(r.y), clamp(r.z)) * .25;
      r.x = 0;
      r.y = 0;
      r.z = 0;
    }
  }
  // gamma 矫正
  c.x = pow(float(clamp(c.x)), 1 / 2.2);
  c.y = pow(float(clamp(c.y)), 1 / 2.2);
  c.z = pow(float(clamp(c.z)), 1 / 2.2);
  frag_colour = vec4(c.x, c.y, c.z, 1);
}

2.3. main.cpp 代码

main.cpp

cpp 复制代码
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "Shader.hpp"
#include <cstdint>
#include <iostream>
#include <iostream>

// 用于处理窗口大小改变的回调函数
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void window_close_callback(GLFWwindow *window);

// 用于处理用户输入的函数
void process_input_callback(GLFWwindow *window, int key, int scancode, int action, int mods);

// 指定窗口默认width和height像素大小
unsigned int SCR_WIDTH = 800;
unsigned int SCR_HEIGHT = 600;
// 相机位置
glm::vec3 camera_position = glm::vec3(50, 52, 295.6);
/************************************/

int main()
{
    /****** 1. 初始化 glfw, glad, 窗口 *******/
    // glfw 初始化 + 配置 glfw 参数
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // glfw 生成窗口
    GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        // 检查是否成功生成窗口,如果没有成功打印出错信息并且退出
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }

    // 设置窗口window的上下文
    glfwMakeContextCurrent(window);
    // 配置window变化时的回调函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    // 设置窗口关闭回调
    glfwSetWindowCloseCallback(window, window_close_callback);
    // 设置按键回调函数
    glfwSetKeyCallback(window, process_input_callback);

    // 使用 glad 加载 OpenGL 中的各种函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    /************************************/

    /****** 2. 初始化 shader ******/

    Shader smallptShader("../resources/smallpt.vert", "../resources/smallpt.frag");

    /************************************/

    /****** 3. 准备输入数据 ******/

    /*   vertices
     *   0 ----- 3
     *   | \     |
     *   |  \_   |
     *   |    \_ |
     *   1 ----- 2
     */
    float vertices[] = {
        // x,y,z
        -1.0, 1.0,  0.0, // 0
        -1.0, -1.0, 0.0, // 1
        1.0,  -1.0, 0.0, // 2
        1.0,  1.0,  0.0  // 3
    };

    unsigned int indices[] = {0, 1, 2, 2, 3, 0};

    GLuint VAO, VBO, EBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_READ);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)(0 * sizeof(float)));
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_READ);
    /************************************/

    /****** 4.开始渲染 ******/

    while (!glfwWindowShouldClose(window))
    {

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        smallptShader.use();
        smallptShader.setiVec2("image_size", glm::ivec2(SCR_WIDTH, SCR_HEIGHT));
        smallptShader.setInt("spp", 16);
        smallptShader.setVec3("cam_position", camera_position);

        glBindVertexArray(VAO); // 绑定VAO,指定当前绘制使用的VAO对象
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(window); // 在gfw中启用双缓冲,确保绘制的平滑和无缝切换
        glfwPollEvents(); // 用于处理所有挂起的事件,例如键盘输入、鼠标移动、窗口大小变化等事件
    }
    /************************************/

    /****** 5. 释放资源 ******/
    // glfw 释放 glfw使用的所有资源
    glfwTerminate();
    /************************************/
    return 0;
}

// 用于处理用户输入的函数
void process_input_callback(GLFWwindow *window, int key, int scancode, int action, int mods)
{

    switch (key)
    {
    case GLFW_KEY_ESCAPE:
        // 当按下 Esc 按键时调用 glfwSetWindowShouldClose() 函数,关闭窗口
        glfwSetWindowShouldClose(window, true);
        break;
    case GLFW_KEY_A:
        camera_position.x -= 1;
        break;
    case GLFW_KEY_D:
        camera_position.x += 1;
        break;
    case GLFW_KEY_W:
        camera_position.z -= 1;
        break;
    case GLFW_KEY_S:
        camera_position.z += 1;
        break;
    default:
        break;
    }
}

// 在使用 OpenGL 和 GLFW 库时,处理窗口大小改变的回调函数
// 当窗口大小发生变化时,确保 OpenGL 渲染的内容能够适应新的窗口大小,避免图像被拉伸、压缩或出现其他比例失真的问题
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    SCR_WIDTH = width;
    SCR_HEIGHT = height;
    glViewport(0, 0, width, height);
}
void window_close_callback(GLFWwindow *window)
{
    // 这里可以做一些额外的清理工作
    // 例如释放资源、记录日志等
    std::cout << "Window is closing..." << std::endl;
}

2.4. 运行结果

spp=16

spp=256

spp=1024(很耗时):

3. 全部代码

使用 glsl 实现 smallpt 的全部代码以及模型文件可以在 [OpenGL]使用glsl实现smallpt 中下载。

4. 改进思路

当spp比较大时,程序运行会比较卡顿。为了提高程序的流畅性,下面几条是可以用于优化程序的方向(如果有时间的话我以后可能会实现一下):

  1. 使用预计算的随机数纹理生成随机数,而不是使用 Fragment shader 中的函数 on the fly 地生成随机数;
  2. 基于前后两帧的连续性,实现时间上的插值;
  3. 基于空间的连续性,对结果进行滤波处理;

三、参考

[1.]smallpt

[2.]smallpt-glsl

相关推荐
一只搬砖的猹3 小时前
cjson——excel转json文件(python脚本转换)
c++·人工智能·python·单片机·物联网·json·excel
冀晓武4 小时前
C++ 设计模式:命令模式(Command Pattern)
c++·设计模式·命令模式
唐棣棣5 小时前
期末速成C++【知识点汇总完】
开发语言·c++
belldeep7 小时前
C++:Windows 多线程 简单示例
c++·多线程·thread
捕鲸叉8 小时前
C++软件设计模式之中介者模式
c++·设计模式·中介者模式
一线灵8 小时前
跨平台游戏引擎 Axmol-2.3.0 发布
c++·游戏引擎·wasm·cocos2d·axmol
捕鲸叉8 小时前
C++软件设计模式之模板方法模式
c++·设计模式
爱干饭的boy8 小时前
教师管理系统
java·开发语言·c++·windows·python·青少年编程
OTWOL9 小时前
【单链表】 OJ 练习题精选
c语言·开发语言·数据结构·c++·算法
是wzoi的一名用户啊~9 小时前
[wzoi]Help Bubu
开发语言·c++·算法