一、简介
本文介绍了如何使用 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+...
我们可以使用迭代(循环)的结构计算上式,在循环中维护变量L
和Lf
。L
是最终的结果,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比较大时,程序运行会比较卡顿。为了提高程序的流畅性,下面几条是可以用于优化程序的方向(如果有时间的话我以后可能会实现一下):
- 使用预计算的随机数纹理生成随机数,而不是使用 Fragment shader 中的函数 on the fly 地生成随机数;
- 基于前后两帧的连续性,实现时间上的插值;
- 基于空间的连续性,对结果进行滤波处理;
三、参考
[1.]smallpt
[2.]smallpt-glsl